SQL注入的危害就不多說,但是如何在日常開發中預防SQL注入?
1. 理論
- 什麼是SQL注入
注入攻擊的本質,是程序把用戶輸入的數據當做代碼執行。這裏有兩個關鍵條件:
第一是用戶能夠控制輸入;
第二是用戶輸入的數據被拼接到要執行的代碼中從而被執行。
sql注入漏洞則是程序將用戶輸入數據拼接到了sql語句中,從而攻擊者即可構造、改變sql語義從而進行攻擊。
- SQL漏洞一般的預防措施:
- 對於接受外部參數的動態SQL,能夠進行預編譯操作的地方一律使用預編譯,禁止直接從字符串拼接SQL;
- 對於SQL不能預編譯的地方,例如order by $param處,應該使用嚴格的白名單進行校驗,然後拼接參數;
- mybatis動態sql的兩種方式
動態 sql 是 mybatis 的主要特性之一,在mybatis中我們可以把參數傳到xml文件,由mybatis對sql及其語法進行解析,mybatis支持使用${}和#{}。
#{}:在mybatis中是預編譯操作;
${}:在mybatis中是直接拼接;
- 爲什麼#{}可以預發SQL注入
直觀的理由:
使用${}方式傳入的參數,mybatis不會對它進行特殊處理,而使用#{}傳進來的參數,mybatis默認會將其當成字符串。
例如:selec * from #{table};
解析後:select * from "test";
例如:selec * from ${table};
解析後:select * from test;
因爲前者多了字符串的引號,那麼可以預防sql注入。
書面化的理由是:
#和$在預編譯處理中是不一樣的。#類似jdbc中的PreparedStatement,對於傳入的參數,在預處理階段會使用?代替,待真正查詢的時候,即在數據庫管理系統中(DBMS)纔會代入參數。而${}只是簡單的替換。
2. 實踐
2.1 一般sql的處理
核心:能夠進行預編譯的地方,一律使用#{}進行預編譯。
在where接受參數:
<select id="selectField">
SELECT field_name FROM table_name WHERE 1 = 1
<if test="condition != null">
AND field_name = #{condtion} <!--禁止使用${param}拼接-->
</if>
</select>
在like接受參數:使用CONCAT函數。
<select id="selectByLike">
SELECT field_name FROM table_name WHERE 1 = 1
<if test="condition != null">
AND field_name like CONCAT(#{condition}, '%') <!--禁止使${param}拼接-->
</if>
</select>
在in處接受參數:
<select id="selectInSQL">
SELECT field_name FROM table_name WHERE 1 = 1
<if test="conditionArray != null">
field_name in
<!--禁止使用${param}拼接-->
<foreach collection="conditionArray" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
</select>
2.2 order by的處理
核心:不能使用#{}進行預編譯時,在mybatis進行白名單校驗。
<select id="sortSQL">
SELECT field_name FROM table_name WHERE 1 = 1
<!--傳入的字段不爲空,且傳入的字段爲name,才執行`order by name`的sql-->
<if test="columnName != null and columnName=='name'.toString()">
order by name
<if test="orderName !=null and orderName=='desc'.toString()">
desc
</if>
<if test="orderName !=null and orderName=='asc'.toString()">
asc
</if>
</if>
</select>