什麼是 MyBatis ?
MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。
常見風險
狀況一:
/* mapper設置 */
<select id="getUserbyId" resultType="model.Student">
select * from Studentinfo where id = #{id}
</select>
/* 測試代碼 */
Student id = new Student();
id.setId(1);
Student student = session.selectOne("getUserbyId", id);
/* 分析 */
此時setId()的參數只能爲Student對應原始的數據類型數據,即只能爲正數,不能傳入"1"等
mapper處使用#{}、${}方式結果相同
/* 結果 */
無注入風險
狀況二:
/* mapper設置 */
<select id="getUserbyId" resultType="model.Student">
select * from Studentinfo where id = #{id}
</select>
/* 測試代碼 */
Student student = session.selectOne("getUserbyId", 1);
/* 分析 */
mysql特性,selectOne()傳入任何數據,都只會取最前面合法的參數去執行語句,無合法數據將返回null
/* 結果 */
無注入風險
狀況三:
/* mapper設置 */
<select id="getUserbyId" resultType="model.Student">
select * from Studentinfo where id = ${id}
</select>
/* 測試代碼 */
Student student = session.selectOne("getUserbyId", 1);
selectOne()傳入INT,STRING類型數據,都會產生報錯提示以下
There is no getter for property named 'id' in 'class java.lang.Integer'
/* 分析 */
${}表示mybatis將傳入的對象原樣經過get方法獲取其值後代入sql語句執行
而如上傳入的簡單數據類型做爲object非對象無get方法,故執行報錯
/* 結果 */
無注入風險
狀況四:
/* mapper設置 */
<select id="getUserbyId" resultType="model.Student">
select * from Studentinfo where id = ${id}
</select>
/* 測試代碼 */
Map params = new HashMap();
params.put("id", 1);
Student student = session.selectOne("getUserbyId", params);
/* 分析 */
selectOne()傳入map對象,${}經過get方式獲取id屬性值原樣代入sql語句執行
/* 結果 */
存在注入風險
/* 利用 */
Map params = new HashMap();
params.put("id", "1 or 1=1 limit 0,2");
Student student = session.selectOne("getUserbyId", params);
/* 修復方案 */
#{}方式處理傳入的參數
狀況五:
/* mapper設置 */
<select id="getUserbyId" resultType="model.Student">
select * from Studentinfo order by ${ordername}
</select>
/* 測試代碼 */
Map params = new HashMap();
params.put("ordername", "name");
Student student = session.selectOne("getUserbyId", params);
/* 分析 */
/* 結果 */
存在注入風險
/* 利用 */
Map params = new HashMap();
params.put("ordername", "'");
Student student = session.selectOne("getUserbyId", params);
/* 修復方案 */
#{}方式處理傳入的參數
select * from Studentinfo order by #{ordername}
sortname方式相同
狀況六:
/* mapper設置 */
<select id="getUserbyId" parameterType="map" resultType="model.Student">
select * from Studentinfo
<if test="ordername != ''">
order by
<if test="ordername == 'name' ">
name
</if>
<if test="ordername == 'id' ">
id
</if>
</if>
</select>
/* 測試代碼 */
Map params = new HashMap();
params.put("ordername", "name");
Student student = session.selectOne("getUserbyId", params);
/* 分析 */
傳入不存在的odername便可注入
/* 結果 */
存在注入風險
/* 利用 */
Map params = new HashMap();
params.put("ordername", "'");
Student student = session.selectOne("getUserbyId", params);
/* 修復方案 */
方案一:
#{}方式處理傳入的參數
select * from Studentinfo order by #{ordername}
方案二:
白名單形式判斷傳入的ordername是否屬於合法
<if test="ordername != '' and ordername in ('name', 'id') ">
狀況七:
/* mapper設置 */
<select id="getUserbyId" parameterType="map" resultType="model.Student">
select * from Studentinfo where name like '%${name}%'
</select>
/* 測試代碼 */
Map params = new HashMap();
params.put("name", "he");
Student student = session.selectOne("getUserbyId", params);
/* 分析 */
/* 結果 */
存在注入風險
/* 利用 */
Map params = new HashMap();
params.put("name", "'");
Student student = session.selectOne("getUserbyId", params);
/* 修復方案 */
方案一:
使用concat()函數鏈接
select * from Studentinfo where name like CONCAT('%',#{name},'%')
方案二:
使用官方推薦的bind()函數完成參數綁定
<bind name="pattern" value="'%' + name + '%'" />
select * from Studentinfo where name like #{pattern}
狀況八:
/**
* in方式查詢相似,可以使用foreach方式構建
* 詳見官方文檔:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html
*/
參考連接
http://www.mybatis.org/mybatis-3/zh/index.html
http://blog.csdn.net/kucoll/article/details/51679371
https://www.google.com.hk
https://www.baidu.com/
最後
此文僅爲對mybatis的一些初步認識,稍後再遇到相關項目再更新...
20171022更新
京東安全應急響應中心這篇文章不錯,更多深刻分析見
http://mp.weixin.qq.com/s?__biz=MjM5OTk2MTMxOQ==&mid=2727827368&idx=1&sn=765d0835f0069b5145523c31e8229850&mpshare=1&scene=1&srcid=0926iDLkz6CKTO9IUh5fqt3o#rd
漏洞場景
1. 模糊查詢like
還以第一節中提到的新聞詳情頁面爲例,按照新聞標題對新聞進行模糊查詢,若是考慮安全編碼規範問題,其對應的SQL語句以下:
Select * from news where title like ‘%#{title}%’,
但因爲這樣寫程序會報錯,研發人員將SQL查詢語句修改以下:
Select * from news where title like ‘%${title}%’,
在這種狀況下咱們發現程序再也不報錯,可是此時產生了SQL語句拼接問題,若是java代碼層面沒有對用戶輸入的內容作處理勢必會產生SQL注入漏洞。
2. in以後的參數
在對新聞進行同條件多值查詢的時候,如當用戶輸入1001,1002,1003…100N時,若是考慮安全編碼規範問題,其對應的SQL語句以下:
Select * from news where id in (#{id}),
但因爲這樣寫程序會報錯,研發人員將SQL查詢語句修改以下:
Select * from news where id in (${id}),
修改SQL語句以後,程序中止報錯,可是卻引入了SQL語句拼接的問題,若是研發人員沒有對用戶輸入的內容作過濾,勢必會產生SQL注入漏洞。
3. order by以後
當根據發佈時間、點擊量等信息對新聞進行排序的時候,若是考慮安全編碼規範問題,其對應的SQL語句以下:
Select * from news where title =‘京東’ order by #{time} asc,
但因爲發佈時間time不是用戶輸入的參數,沒法使用預編譯。研發人員將SQL查詢語句修改以下:
Select * from news where title =‘京東’ order by ${time} asc,
修改以後,程序經過預編譯,可是產生了SQL語句拼接問題,極有可能引起SQL注入漏洞。
修復方案
1. 模糊查詢like SQL注入修復建議
按照新聞標題對新聞進行模糊查詢,可將SQL查詢語句設計以下:
select * from news where tile like concat(‘%’,#{title}, ‘%’),
採用預編譯機制,避免了SQL語句拼接的問題,從根源上防止了SQL注入漏洞的產生。
2. in以後的參數SQL注入修復建議
在對新聞進行同條件多值查詢的時候,可以使用Mybatis自帶循環指令解決SQL語句動態拼接的問題:
select * from news where id in
<foreach collection="ids" item="item" open="("separator="," close=")">#{item} </foreach>
3. order by SQL注入修復建議--在Java層面作映射
預編譯機制只能處理查詢參數,其餘地方還須要研發人員根據具體狀況來解決。如前面提到的排序情景: Select * from news where title =‘京東’ order by #{time} asc,這裏time不是查詢參數,沒法使用預編譯機制,只能這樣拼接:Select * from news where title =‘京東’ order by ${time} asc 。
針對這種狀況研發人員能夠在java層面作映射來進行解決。如當存在發佈時間time和點擊量click兩種排序選擇時,咱們能夠限制用戶只能輸入1和2。當用戶輸入1時,咱們在代碼層面將其映射爲time,當用戶輸入2時,將其映射爲click。而當用戶輸入1和2以外的其餘內容時,咱們能夠將其轉換爲默認排序選擇time(或者click)。