徹底干掉惡心的 SQL 注入漏洞, 一網(wǎng)打盡!
點(diǎn)擊上方藍(lán)色“小哈學(xué)Java”,選擇“設(shè)為星標(biāo)”
回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!


來源:b1ngz.github.io/java-sql-injection-note/
0x01簡介 0x02 JDBC 介紹 說明 0x03 Mybatis 介紹 說明 更多場景 0x04 JPA和休眠 介紹 說明 JPA
0x01簡介
文章主要內(nèi)容包括:
Java持久層技術(shù)/框架簡單介紹 不同場景/框架下易導(dǎo)致SQL注入的寫法 如何避免和修復(fù)SQL注入
0x02 JDBC
介紹
JDBC:
全稱Java數(shù)據(jù)庫連接 是Java訪問數(shù)據(jù)庫的API,不依賴于特定數(shù)據(jù)庫(database-independent) 所有Java持久層技術(shù)都基于JDBC
更多請參考http://www.oracle.com/technetwork/java/javase/jdbc/index.html
說明
直接使用JDBC的場景,如果代碼中存在分解SQL語句,那么很有可能會(huì)產(chǎn)生注入,如
//?concat?sql
String?sql?=?"SELECT?*?FROM?users?WHERE?name?='"+?name?+?"'";
Statement?stmt?=?connection.createStatement();
ResultSet?rs?=?stmt.executeQuery(sql);
安全的寫法是使用參數(shù)化查詢(參數(shù)化查詢),即SQL語句中使用參數(shù)綁定(?占位符)和PreparedStatement,如
//?use???to?bind?variables
String?sql?=?"SELECT?*?FROM?users?WHERE?name=???";
PreparedStatement?ps?=?connection.prepareStatement(sql);
//?參數(shù)?index?從?1?開始
ps.setString(1,?name);
還有一些情況,例如按名稱,列名稱排序,不能使用參數(shù)綁定,此時(shí)需要手工過濾,如通常按按順序排序,其名稱是有限的,因此可以使用白名單的方式來限制參數(shù)值
這里需要注意的是,使用了PreparedStatement 并不意味著不會(huì)產(chǎn)生注入,如果在使用PreparedStatement之前,存在拆分sql語句,那么仍然會(huì)導(dǎo)致注入,如
//?拼接?sql
String?sql?=?"SELECT?*?FROM?users?WHERE?name?='"+?name?+?"'";
PreparedStatement?ps?=?connection.prepareStatement(sql);
看到這里,大家肯定會(huì)好奇PreparedStatement是如何防止SQL注入的,來了解一下
正常情況下,用戶的輸入是作為參數(shù)值的,而在SQL注入中,用戶的輸入是作為SQL指令的一部分,會(huì)被數(shù)據(jù)庫進(jìn)行編譯/解釋執(zhí)行。當(dāng)使用了PreparedStatement,帶占位符(?)的sql語句只會(huì)被編譯一次,之后執(zhí)行只是將占位符替換為用戶輸入,并不會(huì)再次編譯/解釋,因此從根本上防止了SQL注入問題。
更詳細(xì)和準(zhǔn)確的回答,請參考:
PreparedStatement如何避免或阻止SQL注入? 如何使用Java PreparedStatement和CallableStatement修復(fù)SQL注入
0x03 Mybatis
介紹
首個(gè)類持久性框架 分為JDBC(原始SQL)和Hibernate(ORM) 簡化絕大部分JDBC代碼,手工設(shè)置參數(shù)和獲取結(jié)果 靈活,使用者能夠完全控制SQL,支持高級(jí)映射
更多請參考http://www.mybatis.org/
說明
在MyBatis中,使用XML文件或注釋來進(jìn)行配置和映射,將接口和Java POJO(普通的舊Java對象)映射到數(shù)據(jù)庫記錄
XML例子
映射器界面
@Mapper
public?interface?UserMapper?{
????User?getById(int?id);
}
XML配置文件
<select?id="getById"?resultType="org.example.User">
?SELECT?*?FROM?user?WHERE?id?=?#{id}
select>
注釋示例
@Mapper
public?interface?UserMapper?{
????@Select("SELECT?*?FROM?user?WHERE?id=?#{id}")
????User?getById(@Param("id")?int?id);
}
可以看到,使用者需要自己編寫SQL語句,因此當(dāng)使用不當(dāng)時(shí),會(huì)導(dǎo)致注入問題
與使用JDBC不同的是,MyBatis使用#{}和${}來進(jìn)行參數(shù)值替換
使用#{}語法時(shí),MyBatis會(huì)自動(dòng)生成PreparedStatement,使用參數(shù)綁定(?)的方式來設(shè)置值,上述兩個(gè)示例等價(jià)的JDBC查詢代碼如下:
String?sql?=?"SELECT?*?FROM?users?WHERE?id?=??";
PreparedStatement?ps?=?connection.prepareStatement(sql);
ps.setInt(1,?id);
因此#{}可以有效防止SQL注入,詳細(xì)可參考http://www.mybatis.org/mybatis-3/sqlmap-xml.html 字符串替換部分
而使用${}語法時(shí),MyBatis會(huì)直接注入原始字符串,即相當(dāng)于分段字符串,因此會(huì)導(dǎo)致SQL注入,如
<select?id="getByName"?resultType="org.example.User">
?SELECT?*?FROM?user?WHERE?name?=?'${name}'?limit?1
select>
名稱估計(jì)' or '1'='1,實(shí)際執(zhí)行的語句為
SELECT?*?FROM?user?WHERE?name?=?''?or?'1'='1'?limit?1
因此建議盡量使用#{},但有些時(shí)候,如按語句排序,使用#{}會(huì)導(dǎo)致錯(cuò)誤,如
ORDER?BY?#{sortBy}
sortBy參數(shù)估計(jì)name,替換后會(huì)成為
ORDER?BY?"name"
即以字符串“ name”來排序,而不是按名稱排序,詳細(xì)可參考https://stackoverflow.com/a/32996866/6467552。
這種情況就需要使用 ${}
ORDER?BY?${sortBy}
使用了${}后,使用者需要自行過濾輸入,方法有:
代碼層使用白名單的方式,限制
sortBy允許的值,如只能為name,email變量,異常情況則設(shè)置為替換值name在XML配置文件中,使用
if標(biāo)簽來進(jìn)行判斷Mapper接口方法
List?getUserListSortBy(@Param("sortBy")?String?sortBy) ;xml配置文件
<select?id="getUserListSortBy"?resultType="org.example.User">
??SELECT?*?FROM?user
??<if?test="sortBy?==?'name'?or?sortBy?==?'email'">
?order?by?${sortBy}
??if>
select>因?yàn)镸ybatis不支持else,需要替換值的情況,可以使用
choose (when, otherwise)<select?id="getUserListSortBy"?resultType="org.example.User">
??SELECT?*?FROM?user
??<choose>
????<when?test="sortBy?==?'name'?or?sortBy?==?'email'">
??????order?by?${sortBy}
????when>
????
??????order?by?name
????
?choose>
select>
更多場景
除了order by之外,還有一些可能會(huì)使用到${}情況,可以使用其他方法避免,如
像語句
如需要使用通配符(通配符%和_),可以
在代碼層,在參數(shù)值兩邊加上
%,然后再使用#{}使用
bind標(biāo)簽來構(gòu)造新參數(shù),然后再使用#{}Mapper接口方法
List?getUserListLike(@Param("name")?String?name) ;xml配置文件
<select?id="getUserListLike"?resultType="org.example.User">
????name="pattern"?value="'%'?+?name?+?'%'"?/>
????SELECT?*?FROM?user
????WHERE?name?LIKE?#{pattern}
select>語句內(nèi)部的值為OGNL表達(dá)式,具體可參考http://www.mybatis.org/mybatis-3/dynamic-sql.html bind部分使用SQL
concat()函數(shù)<select?id="getUserListLikeConcat"?resultType="org.example.User">
?SELECT?*?FROM?user?WHERE?name?LIKE?concat?('%',?#{name},?'%')
select>
除了注入問題之外,這里還需要對用戶的輸入進(jìn)行過濾,永久有通配符,否則在表中數(shù)據(jù)量中斷的時(shí)候,假設(shè)用戶輸入為%%,會(huì)進(jìn)行全表模糊查詢,嚴(yán)重情況下可導(dǎo)致DOS ,參考http://www.tothenew.com/blog/sql-wildcards-is-your-application-safe/
IN條件
使用和#{}
Mapper接口方法
List?getUserListIn(@Param("nameList")?List?nameList) ;
xml配置文件
<select?id="selectUserIn"?resultType="com.example.User">
??SELECT?*?FROM?user?WHERE?name?in
??"name"?collection="nameList"
???????????open="("?separator=","?close=")">
????????#{name}
??
select>
具體可參考http://www.mybatis.org/mybatis-3/dynamic-sql.html foreach部分
極限語句
直接使用#{}即可
Mapper接口方法
List?getUserListLimit(@Param("offset")?int?offset,?@Param("limit")?int?limit) ;
xml配置文件
<select?id="getUserListLimit"?resultType="org.example.User">
?SELECT?*?FROM?user?limit?#{offset},?#{limit}
select>
0x04 JPA和休眠
介紹
JPA:
全稱Java持久性API ORM(對象關(guān)系映射)持久層API,需要有具體的實(shí)現(xiàn)
更多請參考https://en.wikipedia.org/wiki/Java_Persistence_API
休眠:
JPA ORM實(shí)現(xiàn)
更多請參考http://hibernate.org/
說明
這里有一種錯(cuò)誤的認(rèn)識(shí),使用了ORM框架,就不會(huì)有SQL注入。而實(shí)際上,在Hibernate中,支持HQL(Hibernate查詢語言)和native sql查詢,前者存在HQL注入,封裝和之前JDBC存在相同的注入問題,來具體看一下
高品質(zhì)
HQL查詢例子
Query?query?=?session.createQuery("from?User?where?name?=?'"?+?name?+?"'",?User.class);
User?user?=?query.getSingleResult();
這里的User為類名,和原生SQL類似,拼接會(huì)導(dǎo)致注入
正確的用法:
位置參數(shù)(位置參數(shù))
Query?query?=?session.createQuery("from?User?where?name?=??",?User.class);
query.setParameter(0,?name);
命名參數(shù)(命名參數(shù))
Query?query?=?session.createQuery("from?User?where?name?=?:name",?User.class);
query.setParameter("name",?name);
命名參數(shù)列表(命名參數(shù)列表)
Query?query?=?session.createQuery("from?User?where?name?in?(:nameList)",?User.class);
query.setParameterList("nameList",?Arrays.asList("lisi",?"zhaowu"));
類實(shí)例(JavaBean)
User?user?=?new?User();
user.setName("zhaowu");
Query?query?=?session.createQuery("from?User?where?name?=?:name",?User.class);
//?User?類需要有?getName()?方法
query.setProperties(user);
本機(jī)SQL
存在SQL注入
String?sql?=?"select?*?from?user?where?name?=?'"?+?name?+?"'";
//?deprecated
//?Query?query?=?session.createSQLQuery(sql);
Query?query?=?session.createNativeQuery(sql);
使用參數(shù)綁定來設(shè)置參數(shù)值
String?sql?=?"select?*?from?user?where?name?=?:name";
//?deprecated
//?Query?query?=?session.createSQLQuery(sql);
Query?query?=?session.createNativeQuery(sql);
query.setParameter("name",?name);
JPA
JPA中使用JPQL(Java持久性查詢語言),同時(shí)也支持本地sql,因此和Hibernate存在類似的問題,這里就不再細(xì)說,注意到的可以參考[如何使用Java Persistence API修復(fù)SQL注入( JPA)
END
有熱門推薦?
1.?2020 年 9 月程序員工資統(tǒng)計(jì),我扯后腿了~
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)

