如何避免出現 SQL 注入漏洞

如何避免出現 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。使用條件判斷

如何避免出現 SQL 注入漏洞

2。使用全域性過濾機制,限制order by後的變數內容只能是數字、字母、下劃線。 如使用正則過濾: keyword = keyword。replaceAll(“[^a-zA-Z0-9_\s+]”, “”); 這裡需要注意,過濾需要使用白名單,不能使用黑名單,黑名單無法解決注入問題。 LIKE語句 由於需要like中的關鍵詞需要包裹在兩個%符號中,因此可以使用CONCAT函式進行拼接。

如何避免出現 SQL 注入漏洞

注意不要用 CONCAT(‘%’,‘${stuName}’,‘%’) ,這樣仍然存在漏洞。也就是說,使用$符號是不對的,使用#符號才安全。 IN語句 類似於like語句,直接使用#{}會報錯,常見的錯誤寫法為: tenant_id in (${tenantIds}) 正確的寫法為:

如何避免出現 SQL 注入漏洞

四 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對映檔案中代入以下動態模組:

如何避免出現 SQL 注入漏洞

開發者include該模組就可以新增where條件,但如果使用不當,就會導致SQL注入漏洞:

如何避免出現 SQL 注入漏洞

並使用自定義的引數新增函式: public Criteria addKeywordTo(String keyword) { StringBuilder sb = new StringBuilder(); sb。append(“(display_name like ‘%” + keyword + “%’ or ”); sb。append(“org like ‘” + keyword + “%’ or ”); sb。append(“status like ‘%” + keyword + “%’ or ”); sb。append(“id like ‘” + keyword + “%’) ”); addCriterion(sb。toString()); return (Criteria) this;} 目的是為了實現同時對display_name、org、status、id的like操作。其中addCriterion是Mybatis-generator自帶的函式: protected void addCriterion(String condition) { if (condition == null) { throw new RuntimeException(“Value for condition cannot be null”); } criteria。add(new Criterion(condition));} 這裡的誤區在於,addCriterion本身提供了多個條件的支援,但開發者認為需要自己把多個條件拼接起來,一同傳入addCriterion方法。如同案例中的程式碼一樣,最終傳入addCriterion的只有一個引數。從而執行Example_Where_Clause語句中的:

如何避免出現 SQL 注入漏洞

也就是說,開發者把自己拼接的SQL語句,直接代入了${criterion。condition}中,從而導致了漏洞的產生。而按照Mybatis-generator的文件,正確的寫法應該是: public void addKeywordTo(String keyword, UserExample userExample) { userExample。or()。andDisplayNameLike(“%” + keyword + “%”); userExample。or()。andOrgLike(keyword + “%”); userExample。or()。andStatusLike(“%” + keyword + “%”); userExample。or()。andIdLike(“%” + keyword + “%”);} or方法負責建立Criteria,這時觸發的邏輯就是

如何避免出現 SQL 注入漏洞

${criterion。condition}被替換為了沒有單引號的like,like作為語義程式碼,在語義分析前拼接到了SQL語句中,而“%” + keyword + “%”會作為資料新增到預編譯#{criterion。value}中去,從而避免了注入。 類似的,也提供了In語法的安全使用方法:

如何避免出現 SQL 注入漏洞

Beetween的安全使用方法: example。or() 。andField6Between(3, 7); Mybatis-generator預設生成的order by語句也是使用${}直接進行拼接的:

如何避免出現 SQL 注入漏洞

如果沒有對傳入的引數進行額外的過濾的話,就會導致注入問題。 3 order by 除了自己寫的SQL語句以外,Mybatis-generator預設生成的order by語句也是使用${}直接進行拼接的:

如何避免出現 SQL 注入漏洞

如果沒有對傳入的引數進行額外的過濾的話,就會導致注入問題。 PS: 實際掃雷過程中發現很多語句自動生成了order by語法,但上層呼叫時,並沒有傳入該可選引數。這種情況應當刪除多餘的order by語法。 4 其它外掛 外掛與外掛之間的安全缺陷還不太一樣,下面簡單列舉了常用的幾種外掛。 idea-mybatis-generator 這是IDEA的外掛,可以在開發過程中,從IDE的層面,自動生成CRUD中需要的檔案。使用該外掛時,也有一些預設安全隱患需要注意。 1)自定義order by處理 like\in\between可以參照官方文件使用,無安全隱患。 但該外掛沒有內建的order by處理,需要自行編寫,編寫時,參考Case2 2)預設的IF條件前需要判斷是否為空 外掛預設生成的語法大致如下:

如何避免出現 SQL 注入漏洞

當ID引數為null時,if標籤下的邏輯不會新增到SQL語句中,可能會導致DOS、許可權繞過等漏洞。因此,引數傳入查詢語句前,需要確認不為空。 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注入是一致的: 拼接會導致注入漏洞:

如何避免出現 SQL 注入漏洞

可以使用佔位符和具名引數來防止SQL語句,其本質都是預編譯。

如何避免出現 SQL 注入漏洞

如何避免出現 SQL 注入漏洞

Hibernate在使用過程中有很多不足:

全表對映不靈活,更新時需要傳送所有欄位,影響程式執行效率。

對複雜查詢的支援很差。

對儲存過程的支援很差。

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規範。

如何避免出現 SQL 注入漏洞

在Spring框架中,提供了簡易版的JPA實現——spirng data jpa。按照約定好的方法命名規則寫dao層介面,就可以在不寫介面實現的情況下,實現對資料庫的訪問和操作。同時提供了很多除了CRUD之外的功能,如分頁、排序、複雜查詢等等。使用起來更簡單,但底層仍然在使用Hibernate的JPA實現。 和HQL注入一樣,如果使用拼接的方式,將使用者可控的資料代入了查詢語句中,就會導致SQL注入。 安全的查詢應該使用預編譯技術。 Spring Data JPA的預編譯寫法為: String getUser = “SELECT username FROM users WHERE id = ?”;Query query = em。createNativeQuery(getUser);query。setParameter(1, id);String username = query。getResultList(); 小貼士:其實Hibernate的出現日期比JPA規範要早,Hibernate逐漸成熟之後,JavaEE的開發團隊,邀請Hibernate核心開發人員一起制定了JPA規範。之後Spring Data JPA按照規範做了進一步最佳化。除此之外,JPA規範的實現有很多產品,比如Eclipse的TopLink(OracleLink)。 六 總結 經過上面的介紹,尤其是圍繞Mybatis易錯點的討論,我們可以得到以下結論:

持久層元件種類繁多。

開發者對工具使用的錯誤理解,是漏洞出現的主要原因。

相關文章