web安全(4)-- sql注入
1.1 漏洞描述
所謂SQL注入,就是通過把SQL命令插入到Web表單提交或輸入域名或頁面請求的查詢字串,最終達到欺騙伺服器執行惡意的SQL命令。具體來說,它是利用現有應用程式,將(惡意)的SQL命令注入到後臺資料庫引擎執行的能力,它可以通過在Web表單中輸入(惡意)SQL語句得到一個存在安全漏洞的網站上的資料庫,而不是按照設計者意圖去執行SQL語句。比如先前的很多影視網站洩露VIP會員密碼大多就是通過WEB表單遞交查詢字元暴出的,這類表單特別容易受到SQL注入式攻擊。
根據相關技術原理,SQL注入可以分為平臺層注入和程式碼層注入。前者由不安全的資料庫配置或資料庫平臺的漏洞所致;後者主要是由於程式設計師對輸入未進行細緻地過濾,從而執行了非法的資料查詢。基於此,SQL注入的產生原因通常表現在以下幾方面:①不當的型別處理;②不安全的資料庫配置;③不合理的查詢集處理;④不當的錯誤處理;⑤轉義字元處理不合適;⑥多個提交處理不當。
1.2 漏洞危害
資料庫資訊洩漏:資料庫中存放的使用者的隱私資訊的洩露。
網頁篡改:通過操作資料庫對特定網頁進行篡改。
網站被掛馬,傳播惡意軟體:修改資料庫一些欄位的值,嵌入網馬連結,進行掛馬攻擊。
資料庫被惡意操作:資料庫伺服器被攻擊,資料庫的系統管理員帳戶被竄改。
伺服器被遠端控制,被安裝後門(WEBSHELL)。經由資料庫伺服器提供的作業系統支援,讓黑客得以修改或控制作業系統。
破壞硬碟資料,癱瘓全系統。
1.3 漏洞演示
注入攻擊的本質就是把使用者輸入的資料當做程式碼來執行,這裡有兩個關鍵條件:第一個是使用者能夠控制輸入,第二個是原本程式要執行的程式碼,拼接了使用者輸入的資料。
比如這個sql:"select id from users where username = '"+username +"' and password = '" + password +"'" 。如果使用者按著設計者的要求輸入正常的使用者名稱和密碼,這個沒有任何問題,可以正常登陸。但是這個sql因為應用了sql拼接,使用者是可以控制輸入的,所以如果使用者拼接了可執行的資料,例如:username = "",password = "' or '1' = '1";那麼sql就變成了"select id from users where username = '' and password = '' or '1' = '1'",它等價於select id from users where '1' = '1'。這個SQL是恆真的,所以它就繞過了使用者名稱和密碼,登入成功了!
注入步驟:
1)尋找注入點(URL位址列、登陸介面、留言板、搜尋框等)
2)使用者自己構造SQL語句(如:’ or 1 = 1)
3)將SQL語句傳送給資料庫管理系統(DBMS)
4)DBMS接收請求,並將該請求解釋成機器程式碼指令,執行必要的存取操作
5)DBMS接收返回的結果,並處理,返回給使用者
因為使用者構造了特殊的SQL語句,必定返回特殊的結果(只要你的SQL語句夠靈活的話)。
1.4 修復方案
1)基本上大家都知道採用SQL語句預編譯和繫結變數,是防禦SQL注入的最佳方法。但是其中的深層次原因就不見得都理解了。
String sql = "select id, no from user where id=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, id);
ps.executeQuery();
如上所示,就是典型的採用 SQL語句預編譯和繫結變數 。為什麼這樣就可以防止SQL注入呢?
其原因就是:採用了PreparedStatement,就會將SQL語句:"select id, no from user where id=?" 預先編譯好,也就是SQL引擎會預先進行語法分析,產生語法樹,生成執行計劃,也就是說,後面你輸入的引數,無論你輸入的是什麼,都不會影響該SQL語句的語法結構了,因為語法分析已經完成了,而語法分析主要是分析SQL命令,比如 select ,from ,where ,and, or ,order by 等等。所以即使你後面輸入了這些SQL命令,也不會被當成SQL命令來執行了,因為這些SQL命令的執行, 必須先的通過語法分析,生成執行計劃,既然語法分析已經完成,已經預編譯過了,那麼後面輸入的引數,是絕對不可能作為SQL命令來執行的,只會被當做字串字面值引數。所以SQL語句預編譯可以防禦SQL注入。
2)但是不是所有場景都能夠採用SQL語句預編譯,有一些場景必須的採用字串拼接的方式,此時,我們嚴格檢查引數的資料型別,還有可以使用一些安全函式來防止SQL注入。
比如 String sql = "select id,no from user where id=" + id;
在接收到使用者輸入的引數時,我們就嚴格檢查id,只能是int型。複雜情況可以使用正則表示式來判斷。這樣也是可以防止SQL注入的。
安全函式的使用,比如:
MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
name = ESAPI.encoder().encodeForSQL(codec, name);
String sql = "select id,no from user where name=" + name;
ESAPI.encoder().encodeForSQL(codec, name)
該函式會將name中包含的一些特殊字元進行編碼,這樣SQL引擎就不會將name中的字串當成SQL命令來進行語法分析了。
注:實際專案中,一般我們都是採用各種的框架,比如ibatis,hibernate,mybatis等等。他們一般也預設就是SQL預編譯的。對於ibatis/mybatis,如果使用的是#{name}形式的,那麼就是SQL預編譯,使用${name}就不是SQL預編譯的。
3)使用正則表示式過濾:
private String CHECKSQL = “^(.+)\\sand\\s(.+)|(.+)\\sor(.+)\\s$”;
//判斷是否匹配:
Pattern.matches(CHECKSQL,targerStr);
//下面是具體的正則表示式:
//檢測SQL meta-characters的正則表示式 :
/(\%27)|(\’)|(\-\-)|(\%23)|(#)/ix
//修正檢測SQL meta-characters的正則表示式 :
/((\%3D)|(=))[^\n]*((\%27)|(\’)|(\-\-)|(\%3B)|(:))/i
//典型的SQL注入攻擊的正則表示式 :
/\w*((\%27)|(\’))((\%6F)|o|(\%4F))((\%72)|r|(\%52))/ix
//檢測SQL注入,UNION查詢關鍵字的正則表示式 :
/((\%27)|(\’))union/ix(\%27)|(\’)
//檢測MSSQL Server SQL注入攻擊的正則表示式:
/exec(\s|\+)+(s|x)p\w+/ix
//其實可以簡單的使用replace方法也可以實現上訴功能:
public static String TransactSQLInjection(String str){
return str.replaceAll(".*([';]+|(--)+).*", " ");
}
總的來說有以下幾點:
a)永遠不要信任使用者的輸入,要對使用者的輸入進行校驗,可以通過正則表示式,或限制長度,對單引號和雙"-"進行轉換等。
b)永遠不要使用動態拼裝SQL,可以使用引數化的SQL或者直接使用儲存過程進行資料查詢存取。
c)永遠不要使用管理員許可權的資料庫連線,為每個應用使用單獨的許可權有限的資料庫連線。
d)不要把機密資訊明文存放,請加密或者hash掉密碼和敏感的資訊。
e)應用的異常資訊應該給出儘可能少的提示,最好使用自定義的錯誤資訊對原始錯誤資訊進行包裝,把異常資訊存放在獨立的表中。
1.5 相關參考
http://www.cnblogs.com/smilewxt/p/4229810.html
http://blog.csdn.net/quiet_girl/article/details/50586809