mybatis 中使用 sqlMap 進行 sql 查詢時,經常需要動態傳遞參數,例如sql 如下:
select * from student where uid=#{uid} AND student_name='${studentName}'
在動態 SQL 解析階段, #{ } 和 ${ } 會有不同的表現:
#{ } 解析爲一個 JDBC 預編譯語句(prepared statement)的參數標記符佔位符 ?。
上面的例子就被解析爲
...... uid=?
${ } 僅僅爲一個純碎的 string 替換,在mybatis的動態 SQL 解析階段將會進行變量替換。比如:傳入的studentName參數是“張三”
...... student_name='張三'
最後解析完成的完整語句就是
select * from student where uid= ? AND student_name='張三'
然後把解析好的語句在通過JDBC執行。
所以因爲在${ } 在預編譯之前已經被變量替換了,這會存在 sql 注入問題。比如:傳入的studentName參數是“1' OR '1'='1”
在mybatis中解析完成後變成了
select * from student where uid= ? AND student_name='1' OR '1'='1'
這樣就查詢出所有的數據了。
源碼查看:
在執行mybtis的查詢是,會執行到PreparedStatementHandler#query,查看生成的PreparedStatement
如果傳入參數傳入的studentName參數是“1' OR '1'='1”
所以就出現注入的情況了。
如果我們把${studentName}換成#{studentName},在傳入“1' OR '1'='1”呢
在mybatis解析出的結果
在數據庫的執行日誌中查詢執行的sql語句
發現我們傳入的“'”被轉義了,
所以通過預編譯,用佔位符的方式傳值可以把一些特殊的字符進行轉義,這樣可以防止一些sql注入。
其原因就是:採用了JDBC的PreparedStatement,就會將sql語句:“select * from student where uid= ? AND student_name= ?” 預先編譯好,也就是SQL引擎會預先進行語法分析,產生語法樹,生成執行計劃,也就是說,後面你輸入的參數,無論你輸入的是什麼,都不會影響該SQL語句的語法結構了,因爲語法分析已經完成了,而語法分析主要是分析sql命令,比如select,from,where,and,or,order by等等。所以即使你後面輸入了這些sql命令,也不會被當成sql命令來執行了,因爲這些SQL命令的執行,必須先得通過語法分析,生成執行計劃,既然語法分析已經完成,已經預編譯過了,那麼後面輸入的參數,是絕對不可能作爲SQL命令來執行的,只會被當做字符串字面值參數。所以的sql語句預編譯可以防禦SQL注入。而且在多次執行同一個SQL時,能夠提高效率。原因是SQL已編譯好,再次執行時無需再編譯。