Mybatis下的SQL注入漏洞原理及防護(hù)方法解析
之前我一直認(rèn)為 Mybatis 框架下已經(jīng)實(shí)現(xiàn)預(yù)編譯機(jī)制,很多東西都封裝好了,應(yīng)該基本上不會(huì)再有 SQL 注入問題了。近期在滲透中發(fā)現(xiàn),在實(shí)際項(xiàng)目中,即使使用了 Mybatis 框架,但仍然有可能因?yàn)榫幋a人員安全意識(shí)不足而導(dǎo)致 SQL 注入問題。出現(xiàn)情況還真不少,因此有了這篇文章。
一、前言
之前我一直認(rèn)為 Mybatis 框架下已經(jīng)實(shí)現(xiàn)預(yù)編譯機(jī)制,很多東西都封裝好了,應(yīng)該基本上不會(huì)再有 SQL 注入問題了。近期在滲透中發(fā)現(xiàn),在實(shí)際項(xiàng)目中,即使使用了 Mybatis 框架,但仍然有可能因?yàn)榫幋a人員安全意識(shí)不足而導(dǎo)致 SQL 注入問題。出現(xiàn)情況還真不少,因此有了這篇文章。
二、SQL 注入漏洞原理
1、概述
SQL 注入(SQL Injection)是發(fā)生在 Web 程序中數(shù)據(jù)庫層的安全漏洞,是網(wǎng)站存在最多也是最簡(jiǎn)單的漏洞。主要原因是程序?qū)τ脩糨斎霐?shù)據(jù)的合法性沒有判斷和處理,導(dǎo)致攻擊者可以在 Web 應(yīng)用程序中事先定義好的 SQL 語句中添加額外的 SQL 語句,在管理員不知情的情況下實(shí)現(xiàn)非法操作,以此來實(shí)現(xiàn)欺騙數(shù)據(jù)庫服務(wù)器執(zhí)行非授權(quán)的任意查詢,從而進(jìn)一步獲取到數(shù)據(jù)信息。
簡(jiǎn)單地說,就是通過在用戶可控參數(shù)中注入 SQL 語法,破壞原有 SQL 結(jié)構(gòu),達(dá)到編寫程序時(shí)意料之外結(jié)果的攻擊行為。其成因可以歸結(jié)為如下原因造成的:
- 程序編寫者在處理應(yīng)用程序和數(shù)據(jù)庫交互時(shí),使用字符串拼接的方式構(gòu)造 SQL 語句。
- 且未對(duì)用戶可控參數(shù)進(jìn)行足夠的過濾。
2、漏洞復(fù)現(xiàn)
下面使用DVWA靶場(chǎng)來進(jìn)行演示,網(wǎng)站架構(gòu)為PHP,我們重點(diǎn)關(guān)注漏洞原理即可。
該頁面提供了一個(gè)簡(jiǎn)單的查詢功能,可以根據(jù)前端輸入的用戶ID來查詢對(duì)應(yīng)的用戶信息。如圖,輸入 1,返回了對(duì)應(yīng) admin 用戶的信息。

查看該頁面的源代碼:
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>
進(jìn)行代碼審計(jì)可以發(fā)現(xiàn),程序?qū)⑶岸溯斎氲?id 參數(shù)未加任何處理,直接拼接在了 SQL 語句中,那么此時(shí)就導(dǎo)致了SQL注入漏洞。
若此時(shí)攻擊者輸入的用戶ID為 1' or 1='1,則程序拼接后執(zhí)行的 SQL 語句變成了:
SELECT first_name, last_name FROM users WHERE user_id = '1' or 1='1';
可見,攻擊者通過單引號(hào) ' 閉合了數(shù)據(jù)庫查詢語句,并且在查詢條件之后構(gòu)造了“或 1=1”,即“或真”的邏輯,導(dǎo)致查詢出了全部用戶的數(shù)據(jù)。

如果攻擊者可以任意替代提交的字符串,就可以利用 SQL 注入漏洞改變?cè)?SQL 語句的含義,進(jìn)而執(zhí)行任意 SQL 命令,入侵?jǐn)?shù)據(jù)庫進(jìn)行脫庫、刪庫,甚至通過數(shù)據(jù)庫提權(quán)獲取系統(tǒng)權(quán)限,造成不可估量的損失。(SQL注入的場(chǎng)景類型非常之多,攻擊手法、繞過姿勢(shì)也非常多,本文不作重點(diǎn)討論)
3、修復(fù)建議
一般來說,防御 SQL 注入的最佳方式就是使用預(yù)編譯語句(其他防御方法還有很多,本文不作重點(diǎn)討論),綁定變量。例如:
String sql = "SELECT * FROM user_table WHERE username = ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setString(1, "zxd"); ResultSet results = pstmt.executeQuery();
使用預(yù)編譯的 SQL 語句,SQL 語句的語義不會(huì)發(fā)生改變。在 SQL 語句中,變量用占位符 ? 表示,攻擊者無法改變 SQL 的結(jié)構(gòu)。
三、Mybatis 框架簡(jiǎn)介
1、參數(shù)符號(hào)的兩種方式
Mybatis 的 SQL 語句可以基于注解的方式寫在類方法上面,更多的是以 xml 的方式寫到 xml 文件。Mybatis 中 SQL 語句需要我們自己手動(dòng)編寫或者用 generator 自動(dòng)生成。編寫 xml 文件時(shí),MyBatis 支持兩種參數(shù)符號(hào),#{} 和 ${} 。
#{}使用預(yù)編譯,通過 PreparedStatement 和占位符來實(shí)現(xiàn),會(huì)把參數(shù)部分用一個(gè)占位符?替代,而后注入的參數(shù)將不會(huì)再進(jìn)行 SQL 編譯,而是當(dāng)作字符串處理。可以有效避免 SQL 注入漏洞。${}表示使用拼接字符串,將接受到參數(shù)的內(nèi)容不加任何修飾符拼接在 SQL 中。易導(dǎo)致 SQL 注入漏洞。
兩者的區(qū)別如下:
#{}為參數(shù)占位符?,即 SQL 預(yù)編譯。${}為字符串替換,即 SQL 拼接。#{}是“動(dòng)態(tài)解析->預(yù)編譯->執(zhí)行”的過程。${}是“動(dòng)態(tài)解析->編譯->執(zhí)行”的過程。#{}的變量替換是在 DBMS 中。${}的變量替換是在 DBMS 外。- 變量替換后,
#{}對(duì)應(yīng)的變量自動(dòng)加上引號(hào)。變量替換后,${}對(duì)應(yīng)的變量不會(huì)加上引號(hào)。
2、漏洞復(fù)現(xiàn)
下面以一個(gè)查詢場(chǎng)景進(jìn)行簡(jiǎn)單演示,數(shù)據(jù)庫表 user_table 的表數(shù)據(jù)如下:

若沒有采用 JDBC 的預(yù)編譯模式,查詢 SQL 寫為:
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username = '${username}'
</select>這種寫法就產(chǎn)生了 SQL 語句的動(dòng)態(tài)拼接,這樣格式的參數(shù)會(huì)直接參與 SQL 語句的編譯,從而不能避免SQL注入攻擊。
若此時(shí)攻擊者提交的參數(shù)值為 zxd' or 1='1,如下圖,利用 SQL 注入漏洞,成功查詢了所有用戶數(shù)據(jù)。

因此,應(yīng)用 Mybatis 框架 SQL語句的安全寫法(即 JDBC 預(yù)編譯模式):
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO"> select * from user_table where username = #{username}</select>
可見,此時(shí)采用 JDBC 預(yù)編譯模式,即使攻擊者嘗試 SQL 注入攻擊,也只會(huì)將參數(shù)整體作為字符串處理,有效避免了 SQL 注入問題。

四、Mybatis 框架下的 SQL 注入問題及防護(hù)方法
還是以上節(jié)的查詢場(chǎng)景舉例,Mybatis 框架下易產(chǎn)生 SQL 注入漏洞的情況主要有以下三種:
1、模糊查詢
在模糊查詢場(chǎng)景下,考慮安全編碼規(guī)范,使用 #{} 傳入?yún)?shù):
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username = #{username}
</select>在這種情況下使用 #{} 程序會(huì)報(bào)錯(cuò):

于是很多安全經(jīng)驗(yàn)不足的程序員就把 #{} 號(hào)改成了 ${},如果應(yīng)用層代碼沒有對(duì)用戶輸入的內(nèi)容做處理勢(shì)必會(huì)產(chǎn)生SQL注入漏洞。
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username like '%${username}%'
</select>若此時(shí)攻擊者提交的參數(shù)值為 zxd' or 1=1#,如下圖,利用 SQL 注入漏洞,成功查詢了所有用戶數(shù)據(jù)。

因此,安全的寫法應(yīng)當(dāng)使用 CONCAT 函數(shù)連接通配符:
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username like concat('%',#{username},'%')
</select>
2、帶有 IN 謂詞的查詢
在 IN 關(guān)鍵字之后使用 #{} 查詢多個(gè)參數(shù):
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username in (#{usernames})
</select>正常提交查詢參數(shù) 'zxd','hhh',因?yàn)轭A(yù)編譯機(jī)制,系統(tǒng)將我們輸入的字符當(dāng)作了一個(gè)字符串,因此查詢結(jié)果為空,不能滿足業(yè)務(wù)功能需求。

于是很多安全經(jīng)驗(yàn)不足的程序員就把 #{} 號(hào)改成了 ${} :
<select id="getUser" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table where username in (${usernames})
</select>攻擊者提交參數(shù)值 'hhh') or 1=1#,利用 SQL 注入漏洞,成功查詢了所有用戶數(shù)據(jù)。

因此,此種情況下,安全的做法應(yīng)當(dāng)使用 foreach 標(biāo)簽:
<select id="getUserFromList" resultType="user.NewUserDO">
select * from user_table where username in
<foreach collection="list" item="username" open="(" separator="," close=")">
#{username}
</foreach>
</select>3、帶有動(dòng)態(tài)排序功能的查詢
動(dòng)態(tài)排序功能,需要在 ORDER BY 之后傳入?yún)?shù),考慮安全編碼規(guī)范,使用 #{} 傳入?yún)?shù):
<select id="getUserOrder" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table order by #{column} limit 0,1
</select>提交參數(shù) username 根據(jù)用戶名字段排序。但因?yàn)轭A(yù)編譯機(jī)制,系統(tǒng)將我們輸入的字符當(dāng)作了一個(gè)字符串,根據(jù)字符串排序是不生效的,不能滿足業(yè)務(wù)功能需求。(根據(jù)用戶名字段排序,此時(shí)正常應(yīng)返回 root 用戶)

于是很多安全經(jīng)驗(yàn)不足的程序員就把 #{} 號(hào)改成了 ${} :
<select id="getUserOrder" parameterType="java.lang.String" resultType="user.NewUserDO">
select * from user_table order by ${column} limit 0,1
</select>攻擊者提交參數(shù)值 username#,利用 SQL 注入漏洞,成功查詢了所有用戶數(shù)據(jù)。

因此,此種情況下,安全的做法應(yīng)當(dāng)在 Java 代碼層面來進(jìn)行解決??梢栽O(shè)置一個(gè)字段值的白名單,僅允許用戶傳入白名單內(nèi)的字段。
String sort = request.getParameter("sort");
String[] sortWhiteList = {"id", "username", "password"};
if(!Arrays.asList(sortWhiteList).contains(sort)){
sort = "id";
} 或者僅允許用戶傳入索引值,代碼再將索引值映射成對(duì)應(yīng)字段。
String sort = request.getParameter("sort");
switch(sort){
case "1":
sort = "id";
break;
case "2":
sort = "username";
break;
case "3":
sort = "password";
break;
default:
sort = "id";
break;
} 需要注意的是在 mybatis-generator 自動(dòng)生成的 SQL 語句中,ORDER BY 使用的也是 ${},而 LIKE 和 IN 沒有問題。
到此這篇關(guān)于Mybatis下的SQL注入漏洞原理及防護(hù)方法的文章就介紹到這了,更多相關(guān)Mybatis SQL注入漏洞原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)字符串倒序輸出的四種方法匯總
這篇文章主要介紹了Java實(shí)現(xiàn)字符串倒序輸出的四種方法匯總,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
java編譯時(shí)出現(xiàn)使用了未經(jīng)檢查或不安全的操作解決方法
這篇文章主要介紹了java編譯時(shí)出現(xiàn)使用了未經(jīng)檢查或不安全的操作的解決方法,需要的朋友可以參考下2014-03-03
Eclipse運(yùn)行android項(xiàng)目報(bào)錯(cuò)Unable to build: the file dx.jar was not
今天小編就為大家分享一篇關(guān)于Eclipse運(yùn)行android項(xiàng)目報(bào)錯(cuò)Unable to build: the file dx.jar was not loaded from the SDK folder的解決辦法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2018-12-12
SpringBoot整合Redis及Redis工具類撰寫實(shí)例
這篇文章主要介紹了SpringBoot整合Redis及Redis工具類撰寫實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java責(zé)任鏈設(shè)計(jì)模式實(shí)例分析
這篇文章主要介紹了Java責(zé)任鏈設(shè)計(jì)模式,結(jié)合實(shí)例形式詳細(xì)分析了Java責(zé)任鏈設(shè)計(jì)模式的原理與相關(guān)操作技巧,需要的朋友可以參考下2019-07-07
alibaba?seata服務(wù)端具體實(shí)現(xiàn)
seata是來處理分布式服務(wù)之間互相調(diào)用的事務(wù)問題,本文重點(diǎn)給大家介紹alibaba-seata實(shí)現(xiàn)方法,文中通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-02-02

