以Java的視角來聊聊SQL注入
在大二就接觸過sql注入,以前一直在學習windows逆向技術,認爲web安全之後不是本身的從業方向,因此當時也就沒有深刻研究。工做多年來,本人也一直從事安全開發相關工做,隨着Java的市場份額愈來愈重,在工做中接觸Java的機會也愈來愈多,也是機緣巧合的契機,本身開始走向了偏 Java開發的道路。最近工做中接觸到一個項目,其代碼風格極其不堪入目,更嚴重的是DAO部分存在大量SQL注入的隱患,因此趁這個機會,做者複習研究了一把SQL注入相關的知識,在這裏與你們探討一下。
0
什麼是SQL注入
SQL注入是影響企業運營最具備破壞性的漏洞之一。
應用程序向後臺數據庫進行SQL查詢時,若是爲攻擊者提供了影響該查詢的能力,就會引發SQL注入。
1
靶場準備
首先咱們來準備一個web接口服務,該服務能夠提供管理員的信息查詢,這裏咱們採用springboot + jersey 來構建web服務框架,數據庫則採用最經常使用的mysql。下面,咱們來準備測試環境,首先創建一張用戶表jwtk_admin,SQL以下:
而後插入默認的管理員:
這樣咱們就有了兩位系統內置管理員了,管理員密碼採用MD5進行Hash,固然這是一個很簡單的爲了做爲研究靶場的表,因此沒有很全的字段。
接下來,咱們建立 spring boot + jersey 構建的RESTFul web服務,這裏咱們提供了一個經過管理員用戶名查詢管理員具體信息的接口,以下:
2
SQL注入測試
首先咱們以開發者正向思惟向web服務發送管理員查詢請求,這裏咱們用PostMan工具發送一個GET請求,請求與結果以下圖所示:
不出咱們和開發者所料,Web接口返回了咱們想要的結果,用戶名爲admin的管理員信息。OK,如今開發任務完成,Git Push,Jira任務點爲待測試,那麼這樣的接口就真的沒有問題了嗎?如今咱們發送這樣一條GET請求:
發送該請求後,咱們發現PostMan沒有接收到返回結果,而Web服務後臺卻開始拋MySQLSyntaxErrorException異常了,錯誤以下:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''xxxx''' at line 1
緣由是在咱們查詢的 xxxx' 處sql語句語法不正確致使。這裏咱們先不討論SQL語法問題,咱們繼續實驗,再次構造一條GET查詢請求:
此時,咱們能夠驚訝的發現,查詢接口非但沒有報錯,反而將咱們數據庫jwti_admin表中的全部管理員信息都查詢出來了:
這是什麼鬼,難道管理員表中還有 name=xxxx'or'a'='a 的用戶?這就是 SQL Injection。
3
注入原理分析
在接口中接受了一個String類型的name參數,而且經過字符串拼接的方式構建了查詢語句。在正常狀況下,用戶會傳入合法的name進行查詢,可是黑客卻會傳入精心構造的參數,只要參數經過字符串拼接後依然是一句合法的SQL查詢,此時SQL注入就發生了。正如咱們上文輸入的name=xxxx'or'a'='a與咱們接口中的查詢語句進行拼接後構成以下SQL語句:
當接口執行此句SQL後,系統後臺也就至關於拱手送給黑客了,黑客一看到管理員密碼這個hash,都不用去cmd5查了,直接就用123456密碼去登陸你的後臺系統了。Why?由於123456的md5哈希太常見了,別笑,這就是不少中小網站的現實,弱口令橫行,不見棺材不落淚!
好了,如今咱們應該明白了,SQL Injection緣由就是因爲傳入的參數與系統的SQL拼接成了合法的SQL而致使的,而其本質仍是將用戶輸入的數據當作了代碼執行。在系統中只要有一個SQL注入點被黑客發現,那麼黑客基本上能夠執行任意想執行的SQL語句了,例如添加一個管理員,查詢全部表,甚至「脫褲」 等等,固然本文不是講解SQL注入技巧的文章,這裏咱們只探討SQL注入發生的緣由與防範方法。
4
JDBC的預處理
在上文的接口中,DAO使用了比較基礎的JDBC的方式進行數據庫操做,直接使JDBC構建DAO在比較老的系統中仍是很常見的,但這並不意味着使用JDBC就必定不安全,若是我將傳入的參數 xxxx'or'a'='a 總體做爲參數進行name查詢,那就不會產生SQL注入。在JDBC中,提供了PreparedStatement (預處理執行語句)的方式,能夠對SQL語句進行查詢參數化,使用預處理後的代碼以下:
一樣,咱們使用上文的注入方式注入 ,此時咱們發現,SQL注入沒能成功。如今,咱們來打印一下被被預處理後的SQL,看看有什麼變化:
看到了嗎?全部的 ' 都被 \' 轉義掉了,從而能夠確保SQL的查詢參數就是參數,不會被惡意執行,從而防止了SQL注入。
5
Mybatis下注入防範
MyBatis 是支持定製化 SQL、存儲過程以及高級映射的優秀的持久層框架, 其幾乎避免了全部的 JDBC 代碼和手動設置參數以及獲取結果集。同時,MyBatis 能夠對配置和原生Map使用簡單的 XML 或註解,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄,所以mybatis如今在市場中採用率也很是高。這裏咱們定義以下一個mapper,來實現經過用戶名查詢管理員的接口:
一樣提供Web訪問接口:
接下來,咱們嘗試SQL注入name字段,能夠發現注入並無成功,經過打印mybatis的Log能夠看到mybatis框架對參數進行了預處理處理,從而防止了注入:
那是否只要使用了mybatis就必定能夠避免SQL注入的危險?咱們把mapper作以下修改,將參數#{name}修改成${name},並使用name='xxxx' or 'a'='a' 做爲GET請求的參數,能夠發現SQL注入仍是發生了:
那這是爲何,mybatis ${}與#{}的差異在哪裏?
原來在mybatis中若是以${}形式聲明爲SQL傳遞參數,mybatis將不會進行參數預處理,會直接動態拼接SQL語句,此時就會存在被注入的風險,因此在使用mybatis做爲持久框架時應儘可能避免採用${}的形式進行參數傳遞,若是沒法避免(有些SQL如like、in、order by等,程序員可能依舊會選擇${}的方式傳參),那就須要對傳入參數自行進行轉義過濾。
6
JPA注入防範
JPA是Sun公司用來整合ORM技術,實現天下歸一的ORM標準而定義的Java Persistence API(java持久層API),JPA只是一套接口,目前引入JPA的項目都會採用Hibernate做爲其具體實現,隨着無配置Spring Boot框架的流行,JPA愈來愈具備做爲持久化首選的技術,由於其能讓程序員寫更少的代碼,就能完成現有的功能,例如強大的JpaRepository,常規的SQL查詢只需按照命名規則定義接口,即可以不寫SQL(JPQL/SQL)就能夠實現數據的查詢操做,從SQL注入防範的角度來講,這種將安全責任拋給框架遠比依靠程序員自身控制來的保險。所以若是項目使用JPA做爲數據訪問層,基本上能夠很大程度的消除SQL注入的風險。可是話不能說的太死,在我見過的一個Spring Boot項目中,雖然採用了JPA做爲持久框架,可是有一位老程序員不熟悉於使用JPQL來構建查詢接口,依舊使用字符串拼接的方式來實現業務,而爲項目安全埋下了隱患。
安全須要一絲不苟,安全是100 - 1 = 0的業務,即便你防護了99%的攻擊,那還不算勝利,只要有一次被入侵了,那就有可能給公司帶來很嚴重的後果。
關於JPA的SQL注入,咱們就不詳細討論了,由於框架下的注入漏洞屬於框架漏洞範疇(如CVE-2016-6652),程序員只要遵循JPA的開發規範,就無需擔憂注入問題,框架都爲你作好幕後工做了。
7
SQL注入的其餘防範辦法
不少公司都會存在老系統中有大量SQL注入風險代碼的問題,可是因爲其已穩定支持公司業務好久,不宜採用大面積代碼更新的方式來消除注入隱患,因此須要考慮其採用他方式來防範SQL注入。除了在在SQL執行方式上防範SQL注入,不少時候還能夠經過架構上,或者經過其餘過濾方式來達到防止SQL注入的效果。
- 一切輸入都是不安全的:對於接口的調用參數,要進行格式匹配,例如admin的經過name查詢的接口,與之匹配的Path應該使用正則匹配(由於用戶名中不該該存在特殊字符),從而確保傳入參數是程序控制範圍以內的參數,即只接受已知的良好輸入值,拒毫不良輸入。注意:驗證參數應將它與輸出編碼技術結合使用。
- 利用分層設計來避免危險:前端儘可能靜態化,儘可能少的暴露能夠訪問到DAO層的接口到公網環境中,若是現有項目,很難修改存在注入的代碼,能夠考慮在web服務以前增長WAF進行流量過濾,固然代碼上就不給hacker留有攻擊的漏洞才最好的方案。也能夠在擁有nginx的架構下,採用OpenRestry作流量過濾,將一些特殊字符進行轉義處理。
- 儘可能使用預編譯SQL語句:因爲動態SQL語句是引起SQL注入的根源。應使用預編譯語句來組裝SQL查詢。
- 規範化:將輸入安裝規定編碼解碼後再進行輸入參數過濾和輸出編碼處理;拒絕一切非規範格式的編碼。