MyBatis SQL注入隱患及防範

什麼是 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)。
相關文章
相關標籤/搜索