一、SQL注入漏洞基本原理
在常見的web漏洞中,SQL注入漏洞較為常見,危害也較大。攻擊者一旦利用系統(tǒng)中存在的SQL注入漏洞來發(fā)起攻擊,在條件允許的情況下,不僅可以獲取整站數(shù)據(jù),還可通過進(jìn)一步的滲透來獲取服務(wù)器權(quán)限,從而進(jìn)入內(nèi)網(wǎng)。
注入攻擊的本質(zhì),是把用戶輸入的數(shù)據(jù)當(dāng)做代碼執(zhí)行。這里有兩個(gè)關(guān)鍵條件,第一個(gè)是用戶能夠控制輸入;第二個(gè)是原本程序要執(zhí)行的代碼,拼接了用戶輸入的數(shù)據(jù)。接下來說下SQL注入漏洞的原理。
舉個(gè)栗子。
當(dāng)用戶發(fā)送GET請(qǐng)求:
http://www.xxx.com/news.jsp?id=1
這是一個(gè)新聞詳情頁(yè)面,會(huì)顯示出新聞的title和content,程序內(nèi)部會(huì)接收這個(gè)id參數(shù)傳遞給SQL語(yǔ)句,SQL如下:
SELECT title,content FROM news WHERE id = 1
這是SQL的原義,也是程序員想要得到的結(jié)果,但是如果用戶改變了id的內(nèi)容,修改成如下:
http://www.jd.com/news.jsp?id=1 and 1=2 UNION SELECT userna-me, password FROM admin
此時(shí)內(nèi)部程序執(zhí)行的SQL語(yǔ)句為:
SELECT title,content FROM news WHERE id = 1 and 1=2 UNION SELECT username, password FROM admin
這條SQL的原義就會(huì)被改變,導(dǎo)致將管理員數(shù)據(jù)表中的用戶名顯示在頁(yè)面title位置,密碼顯示在頁(yè)面content位置,攻擊成功。
二、Mybatis框架介紹
1. Mybatis框架架構(gòu)
Mybatis框架架構(gòu)講解(架構(gòu)圖如下圖所示):
(1)加載配置:配置來源于兩個(gè)地方,一處是配置文件,一處是Java代碼的注解,將SQL的配置信息加載成為一個(gè)個(gè)MappedStatement對(duì)象(包括了傳入?yún)?shù)映射配置、執(zhí)行的SQL語(yǔ)句、結(jié)果映射配置),存儲(chǔ)在內(nèi)存中。
(2)SQL解析:當(dāng)API接口層接收到調(diào)用請(qǐng)求時(shí),會(huì)接收到傳入SQL的ID和傳入對(duì)象(可以是Map、JavaBean或者基本數(shù)據(jù)類型),Mybatis會(huì)根據(jù)SQL的ID找到對(duì)應(yīng)的MappedStatement,然后根據(jù)傳入?yún)?shù)對(duì)象對(duì)MappedStatement進(jìn)行解析,解析后可以得到最終要執(zhí)行的SQL語(yǔ)句和參數(shù)。
(3)SQL執(zhí)行:將最終得到的SQL和參數(shù)拿到數(shù)據(jù)庫(kù)進(jìn)行執(zhí)行,得到操作數(shù)據(jù)庫(kù)的結(jié)果。
(4)結(jié)果映射:將操作數(shù)據(jù)庫(kù)的結(jié)果按照映射的配置進(jìn)行轉(zhuǎn)換,可以轉(zhuǎn)換成HashMap、JavaBean或者基本數(shù)據(jù)類型,并將最終結(jié)果返回。
Mybatis架構(gòu)圖
2.?JDBC預(yù)編譯模式
Mybatis框架作為一款半自動(dòng)化的持久層框架,其SQL語(yǔ)句都需要我們自己手動(dòng)編寫,此時(shí)就需要按照安全編碼規(guī)范進(jìn)行開發(fā),以防止SQL注入漏洞的產(chǎn)生。
針對(duì)上一節(jié)中所舉的例子,應(yīng)用Mybatis框架SQL語(yǔ)句安全寫法(即JDBC預(yù)編譯模式)可以寫為:
select * from news where id=#{id},這種寫法可以很好地避免SQL注入漏洞的產(chǎn)生。
3.?動(dòng)態(tài)拼接SQL語(yǔ)句
如果在開發(fā)過程中沒有采用JDBC的預(yù)編譯模式,如我們將上述SQL語(yǔ)句寫為:select * from news where id=${id},這種寫法就產(chǎn)生了SQL語(yǔ)句的動(dòng)態(tài)拼接。因?yàn)椤?{xxx}”這樣格式的參數(shù)會(huì)直接參與SQL語(yǔ)句的編譯,從而不能避免SQL注入攻擊。
三、Mybatis框架下易產(chǎn)生SQL注入漏洞場(chǎng)景分析
在基于Mybatis框架的Java白盒代碼審計(jì)工作中,通常將著手點(diǎn)定位在Mybatis的配置文件中。通過查看這些與數(shù)據(jù)庫(kù)交互的配置文件來確定SQL語(yǔ)句中是否存在拼接情況,進(jìn)而確立跟蹤點(diǎn)。通過總結(jié),Mybatis框架下易產(chǎn)生SQL注入漏洞的情況主要分為以下三種:
1. 模糊查詢like
還以第一節(jié)中提到的新聞詳情頁(yè)面為例,按照新聞標(biāo)題對(duì)新聞進(jìn)行模糊查詢,如果考慮安全編碼規(guī)范問題,其對(duì)應(yīng)的SQL語(yǔ)句如下:
Select * from news where title like ‘%#{title}%’,
但由于這樣寫程序會(huì)報(bào)錯(cuò),研發(fā)人員將SQL查詢語(yǔ)句修改如下:
Select * from news where title like ‘%${title}%’,
在這種情況下我們發(fā)現(xiàn)程序不再報(bào)錯(cuò),但是此時(shí)產(chǎn)生了SQL語(yǔ)句拼接問題,如果java代碼層面沒有對(duì)用戶輸入的內(nèi)容做處理勢(shì)必會(huì)產(chǎn)生SQL注入漏洞。
2. in之后的參數(shù)
在對(duì)新聞進(jìn)行同條件多值查詢的時(shí)候,如當(dāng)用戶輸入1001,1002,1003…100N時(shí),如果考慮安全編碼規(guī)范問題,其對(duì)應(yīng)的SQL語(yǔ)句如下:
Select * from news where id in (#{id}),
但由于這樣寫程序會(huì)報(bào)錯(cuò),研發(fā)人員將SQL查詢語(yǔ)句修改如下:
Select * from news where id in (${id}),
修改SQL語(yǔ)句之后,程序停止報(bào)錯(cuò),但是卻引入了SQL語(yǔ)句拼接的問題,如果研發(fā)人員沒有對(duì)用戶輸入的內(nèi)容做過濾,勢(shì)必會(huì)產(chǎn)生SQL注入漏洞。
3. order by之后
當(dāng)根據(jù)發(fā)布時(shí)間、點(diǎn)擊量等信息對(duì)新聞進(jìn)行排序的時(shí)候,如果考慮安全編碼規(guī)范問題,其對(duì)應(yīng)的SQL語(yǔ)句如下:
Select * from news where title =‘京東’ order by #{time} asc,
但由于發(fā)布時(shí)間time不是用戶輸入的參數(shù),無法使用預(yù)編譯。研發(fā)人員將SQL查詢語(yǔ)句修改如下:
Select * from news where title =‘京東’ order by ${time} asc,
修改之后,程序通過預(yù)編譯,但是產(chǎn)生了SQL語(yǔ)句拼接問題,極有可能引發(fā)SQL注入漏洞。
四、Mybatis框架下SQL注入漏洞修復(fù)建議
1.?模糊查詢like SQL注入修復(fù)建議
按照新聞標(biāo)題對(duì)新聞進(jìn)行模糊查詢,可將SQL查詢語(yǔ)句設(shè)計(jì)如下:
select * from news where tile like concat(‘%’,#{title}, ‘%’),
采用預(yù)編譯機(jī)制,避免了SQL語(yǔ)句拼接的問題,從根源上防止了SQL注入漏洞的產(chǎn)生。
2. ?in之后的參數(shù)SQL注入修復(fù)建議
在對(duì)新聞進(jìn)行同條件多值查詢的時(shí)候,可使用Mybatis自帶循環(huán)指令解決SQL語(yǔ)句動(dòng)態(tài)拼接的問題:
select * from news where id in
#{item}
3. order by SQL注入修復(fù)建議--在Java層面做映射
預(yù)編譯機(jī)制只能處理查詢參數(shù),其他地方還需要研發(fā)人員根據(jù)具體情況來解決。如前面提到的排序情景: Select * from news where title =‘京東’ order by #{time} asc,這里time不是查詢參數(shù),無法使用預(yù)編譯機(jī)制,只能這樣拼接:Select * from news where title =‘京東’ order by ${time} asc 。
針對(duì)這種情況研發(fā)人員可以在java層面做映射來進(jìn)行解決。如當(dāng)存在發(fā)布時(shí)間time和點(diǎn)擊量click兩種排序選擇時(shí),我們可以限制用戶只能輸入1和2。當(dāng)用戶輸入1時(shí),我們?cè)诖a層面將其映射為time,當(dāng)用戶輸入2時(shí),將其映射為click。而當(dāng)用戶輸入1和2之外的其他內(nèi)容時(shí),我們可以將其轉(zhuǎn)換為默認(rèn)排序選擇time(或者click)。