簡介: 本文將針對開發過程中依舊經常出現的SQL編碼缺陷,講解其背后原理及形成原因。並以幾個常見漏洞存在形式,提醒技術同學注意相關問題。最后會根據原理,提供解決或緩解方案。
來源 | 阿里技術公眾號
一 前言
本文將針對開發過程中依舊經常出現的SQL編碼缺陷,講解其背后原理及形成原因。並以幾個常見漏洞存在形式,提醒技術同學注意相關問題。最后會根據原理,提供解決或緩解方案。
二 SQL注入漏洞的原理、形成原因
SQL注入漏洞,根本上講,是由於錯把外部輸入當作SQL代碼去執行。目前最佳的解決方案就是預編譯的方式。
SQL語句在執行過程中,需要經過以下三大基本步驟:
- 代碼語義分析
- 制定執行計划
- 獲得返回結果
而一個SQL語句是由代碼和數據兩部分,如:
SELECT id, name, phone FROM userTable WHERE name = 'xiaoming';
SELECT id, name, phone FROM userTable WHERE name = 是代碼,'xiaoming'是數據。
而預編譯,以Mybatis為例,就是預先分析帶有占位符的語義:
如SELECT id, name, phone FROM userTable WHERE id = #{name};
然后再將數據'xiaoming',傳入到占位符。這樣一來,錯開來代碼語義分析階段,也就不會被誤認為是代碼的一部分了。
在最早期,開發者顯式使用JDBC來自己創建Connection,執行SQL語句。這種情況下,如果將外部可控數據拼接到SQL語句,且沒有做充分過濾的話,就會產生漏洞。這種情況在正常的業務開發過程中已經很少了,按照公司規定,無特殊情況下,必須使用ORM框架來執行SQL。
但目前部分項目中,仍會使用JDBC來編寫一些工具腳本,如DataMerge.java 、DatabaseClean.java,借用JDBC的靈活性,通過這些腳本來執行數據庫批量操作。
此類代碼不應該出現在線上版本中,以免因各種情況,被外部調用。
三 直接使用Mybatis
1 易錯點
目前大部分的平台代碼是基於Mybatis來處理持久層和數據庫之間的交互的,Mybatis傳入數據有兩種占位符{}和#{}。{}和#{}。{}可以理解為語義分析前的字符串拼接,講傳入的參數,原封不動地傳入。
比如說
SELECT id, name, phone FROM userTable WHERE name = '${name}';
傳入name=xiaoming后,相當於
SELECT id, name, phone FROM userTable WHERE name = 'xiaoming';
實際應用中
SELECT id, name, phone FROM userTable WHERE ${col} = 'xiaoming';
傳入col = "name",相當於
SELECT id, name, phone FROM userTable WHERE name = 'xiaoming';
就像預編譯原理介紹里講的一樣,使用#{} 占位符就不存在注入問題了。但有些業務場景是不可以直接使用#{}的。
比如order by語法中
如果編寫SELECT id, name, phone FROM userTable ORDER BY #{}; ,執行時是會報錯的。因為order by后的內容,是一個列名,屬於代碼語義的一部分。如果在語義分析部分沒有確定下來,就相當於執行SELECT id, name, phone FROM userTable ORDER BY 。肯定會有語法錯誤。
再比如like場景下
SELECT id, name, phone FROM userTable WHERE name like '%#{name}%';
#{}不會被解析,從而導致報錯。
in 語法和 between語法都是如此,那么如何解決這類問題呢?
2 正確寫法
order by(group by)語句中使用${}
1.使用條件判斷
2.使用全局過濾機制,限制order by后的變量內容只能是數字、字母、下划線。
如使用正則過濾:
這里需要注意,過濾需要使用白名單,不能使用黑名單,黑名單無法解決注入問題。
LIKE語句
由於需要like中的關鍵詞需要包裹在兩個%符號中,因此可以使用CONCAT函數進行拼接。
注意不要用 CONCAT('%','${stuName}','%') ,這樣仍然存在漏洞。也就是說,使用$符號是不對的,使用#符號才安全。
IN語句
類似於like語句,直接使用#{}會報錯,常見的錯誤寫法為:
正確的寫法為:
四 Mybatis-generator使用安全
繁重的CRUD代碼壓力下,開發者慢慢開始通過Mybatis-generator、idea-mybatis-generator插件、通用Mapper、Mybatis-generator-plus來自動生成Mapper、POJO、Dao等文件。
這些工具可以自動的生成CRUD所需要的文件,但如果使用不當,就會自動產生SQL注入漏洞。我們以最常用的org.mybatis.generator為例,來講解可能會出現的問題。
1 動態語句支持
Mybatis-generator提供來一些函數,幫助用戶把SQL的各個條件連接起來,比如多個參數的like語法,多個參數的比較語法。為了保證使用的簡潔性,需要使用將一些語義代碼拼接到SQL語句中。而如果開發者使用不當,將外部輸入也傳入了{}占位符。就會產生漏洞。
2 targetRuntime參數配置
在配置generator時,配置文件generator-rds.xml中有一個targetRuntime屬性,默認為MyBatis3。在這種情況下,會啟動Mybatis的動態語句支持,啟動enableSelectByExample、enableDeleteByExample、enableCountByExample 以及 enableUpdateByExample功能。
以enableSelectByExample為例,會在xml映射文件中代入以下動態模塊:
開發者include該模塊就可以添加where條件,但如果使用不當,就會導致SQL注入漏洞:
並使用自定義的參數添加函數:
目的是為了實現同時對display_name、org、status、id的like操作。其中addCriterion是Mybatis-generator自帶的函數:
這里的誤區在於,addCriterion本身提供了多個條件的支持,但開發者認為需要自己把多個條件拼接起來,一同傳入addCriterion方法。如同案例中的代碼一樣,最終傳入addCriterion的只有一個參數。從而執行Example_Where_Clause語句中的:
而按照Mybatis-generator的文檔,正確的寫法應該是:
類似的,也提供了In語法的安全使用方法:
Mybatis-generator默認生成的order by語句也是使用${}直接進行拼接的:
3 order by
除了自己寫的SQL語句以外,Mybatis-generator默認生成的order by語句也是使用${}直接進行拼接的:
PS: 實際掃雷過程中發現很多語句自動生成了order by語法,但上層調用時,並沒有傳入該可選參數。這種情況應當刪除多余的order by語法。
4 其它插件
插件與插件之間的安全缺陷還不太一樣,下面簡單列舉了常用的幾種插件。
idea-mybatis-generator
這是IDEA的插件,可以在開發過程中,從IDE的層面,自動生成CRUD中需要的文件。使用該插件時,也有一些默認安全隱患需要注意。
1)自定義order by處理
like\in\between可以參照官方文檔使用,無安全隱患。
但該插件沒有內置的order by處理,需要自行編寫,編寫時,參考Case2
2)默認的IF條件前需要判斷是否為空
插件默認生成的語法大致如下:
com.baomidou.mybatis-plus
- apply方法傳參時,應當使用{}
- 自帶的last方法,其原理是直接拼接到SQL語句的末尾,存在注入漏洞。
五 其它ORM框架
1 Hibernate
ORM全稱為對象關系映射(Object Relational Mapping),簡單地說,就是將數據庫中的表映射為Java對象, 這種只有屬性,沒有業務邏輯的對象也叫做POJO(Plain Ordinary Java Object)對象。
Hibernate是第一個被廣泛使用的ORM框架,它通過XML管理數據庫連接,提供全表映射模型,封裝程度很高。在配置映射文件和數據庫鏈接文件后,Hibernate就可以通過Session對象進行數據庫操作,開發者無需接觸SQL語句,只需要寫HQL語句即可。
Hibernate經常與Struts、Spring搭配使用,也就是Java世界的經典SSH框架。
HQL相較於SQL,多了很多語法限制:
- 不能查詢未做映射的表,只有當模型之間的關系明確后,才可以使用UNION語法。
- 表名,列名大小寫敏感。
- 沒有*、#、-- 。
- 沒有延時函數。
所以HQL注入利用要比SQL注入苦難得多。從代碼審計的角度和普通SQL注入是一致的:
拼接會導致注入漏洞:
- 全表映射不靈活,更新時需要發送所有字段,影響程序運行效率。
- 對復雜查詢的支持很差。
- 對存儲過程的支持很差。
- HQL性能較差,無法根據SQL進行優化。
在審計Hibernate相關注入時,可以通過全局搜索createQuery來快速定位SQL操作的位置。
2 JPA
JPA全稱為Java Persistence API,是Java EE提供的一種數據持久化的規范,允許開發者通過XML或注解的方式,將某個對象,持久化到數據庫中。
主要包括三方面內容:
1.ORM映射元數據,通過XML或注解,描述對象和數據表之間的對應關系。框架便可以自動將對象中的數據保存到數據庫中。
常見的注解有:@Entity、@Table、@Column、@Transient
2.數據操作API,內置接口,方便對某個數據表執行CRUD操作,節省開發者編寫SQL的時間。
常見的方法有:entityManager.merge(T t);
3.JPQL, 提供一種面向對象而不是面向數據庫的查詢語言,將程序和數據庫、SQL解耦合。
JPA是一套規范,Hibernate實現了這一JPA規范。
和HQL注入一樣,如果使用拼接的方式,將用戶可控的數據代入了查詢語句中,就會導致SQL注入。
安全的查詢應該使用預編譯技術。
Spring Data JPA的預編譯寫法為:
小貼士:其實Hibernate的出現日期比JPA規范要早,Hibernate逐漸成熟之后,JavaEE的開發團隊,邀請Hibernate核心開發人員一起制定了JPA規范。之后Spring Data JPA按照規范做了進一步優化。除此之外,JPA規范的實現有很多產品,比如Eclipse的TopLink(OracleLink)。
六 總結
經過上面的介紹,尤其是圍繞Mybatis易錯點的討論,我們可以得到以下結論:
- 持久層組件種類繁多。
- 開發者對工具使用的錯誤理解,是漏洞出現的主要原因。
- 由於自動生成插件的動態特性,自動化發現SQL漏洞不能簡單地使用${}來尋找。必須要根據全局的持久層組件特性,來做詳細的匹配規則。
原文鏈接
本文為阿里雲原創內容,未經允許不得轉載。