A1-注入 php
A2-跨站腳本(XSS) 程序員
A3-錯誤的認證和會話管理 sql
A4-不正確的直接對象引用 數據庫
A5-僞造跨站請求(CSRF) -- Cross-Site Request Forgery 編程
A7-限制遠程訪問失敗 api
A8-未驗證的重定向和傳遞 安全
A9-不安全的加密存儲 服務器
A10-不足的傳輸層保護 編程語言
注入每每是應用程序缺乏對輸入進行安全性檢查所引發的,攻擊者把一些包含指令的數據發送給解釋器,解釋器會把收到的數據轉換成指令執行。常見的注入包括SQL注入,OS Shell,LDAP,Xpath,Hibernate等等,而其中SQL注入尤其常見。這種攻擊所形成的後果每每很大,通常整個數據庫的信息都能被讀取或篡改,經過SQL注入,攻擊者甚至可以得到更多的包括管理員的權限。 ide
SQL注入每每是在程序員編寫包含用戶輸入的動態數據庫查詢時產生的,但其實防範SQL注入的方法很是簡單。程序員只要a)再也不寫動態查詢,或b)防止用戶輸入包含可以破壞查詢邏輯的惡意SQL語句,就可以防範SQL注入。在這篇文章中,咱們將會說明一些很是簡單的防止SQL注入的方法。
咱們用如下Java代碼做爲示例:
String query ="SELECT account_balance FROM user_data WHERE user_name =" |
在以上代碼中,咱們能夠看到並未對變量customerName作驗證,customerName的值能夠直接附在query語句的後面傳送到數據庫執行,則攻擊者能夠將任意的sql語句注入。
防範方法1:參數化查詢
參數化查詢是全部開發人員在作數據庫查詢時首先須要學習的,參數化查詢迫使全部開發者首先要定義好全部的SQL代碼,而後再將每一個參數逐個傳入,這種編碼風格就可以讓數據庫辨明代碼和數據。
參數化查詢可以確保攻擊者沒法改變查詢的內容,在下面修正過的例子中,若是攻擊者輸入了UsrID是「’or ‘1 ‘=’1」,參數化查詢會去查找一個徹底知足名字爲‘or ‘1 ‘=’ 1的用戶。
對於不一樣編程語言,有一些不一樣的建議:
Java EE——使用帶綁定變量的PreparedStatement();
.Net——使用帶綁定變量的諸如SqlCommand()或OleDbCommand()的參數化查詢;
PHP——使用帶強類型的參數化查詢PDO(使用bindParam());
Hibernate——使用帶綁定變量的createQuery()。
Java示例:
String custname = request.getParameter("customerName"); |
C# .Net示例:
String query ="SELECT account_balance FROM user_data WHERE user_name = ?"; |
防範方法二:存儲過程
存儲過程和參數化查詢的做用是同樣的,惟一的不一樣在於存儲過程是預先定義並存放在數據庫中,從而被應用程序調用的。
Java存儲過程示例:
String custname = request.getParameter("customerName"); |
VB .Net存儲過程示例:
Try |
防範方法三:對全部用戶輸入進行轉義
咱們知道每一個DBMS都有一個字符轉義機制來告知DBMS輸入的是數據而不是代碼,若是咱們將全部用戶的輸入都進行轉義,那麼DBMS就不會混淆數據和代碼,也就不會出現SQL注入了。
固然,若是要採用這種方法,那麼你就須要對所使用的數據庫轉義機制,也可使用現存的諸如OWASP ESAPI的escaping routines。ESAPI目前是基於MySQL和Oracle的轉義機制的,使用起來也很方便。一個Oracle的ESAPI的使用示例以下:
ESAPI.encoder().encodeForSQL(new OracleCodec(),queryparam); |
那麼,假設你有一個要訪問Oracle數據庫的動態查詢代碼以下:
String query ="SELECT user_id FROM user_data WHERE user_name = ‘"+req.getParameter("userID")+"’ and user_password = ‘"+req.getParameter("pwd")+"’"; |
那麼,你就必須重寫你的動態查詢的第一行以下:
Codec ORACLE_CODEC = new OracleCodec(); |
固然,爲了保證本身代碼的可讀性,咱們也能夠構建本身的OracleEncoder:
Encoder e = new OracleEncoder(); |
除了上面所說的三種防範方法之外,咱們還建議能夠用如下兩種附加的方法來防範SQL注入:最小權限法、輸入驗證白名單法。
最小權限法:
爲了不注入攻擊對數據庫形成的損害,咱們能夠把每一個數據庫用戶的權限盡量縮小,不要把DBA或管理員的權限賦予你應用程序帳戶,在給用戶權限時是基於用戶須要什麼樣的權限,而不是用戶不須要什麼樣的權限。當一個用戶只須要讀的權限時,咱們就只給他讀的權限,當用戶只須要一張表的部分數據時,咱們寧願另建一個視圖讓他訪問。
若是你的策略是都是用存儲過程的話,那麼僅容許應用程序的帳戶執行這些查詢,而不給他們直接訪問數據庫表的權限。諸如此類的最小權限法可以在很大程度上保證咱們數據庫的安全。
輸入驗證白名單法:
輸入驗證可以在數據傳遞到SQL查詢前就察覺到輸入是否正確合法,採用白名單而不是黑名單則能在更大程度上保證數據的合法性。
對於測試人員來講,如何測試SQL注入漏洞是否存在呢?
首先,咱們將SQL注入攻擊能分爲如下三種類型:
Inband:數據經由SQL代碼注入的通道取出,這是最直接的一種攻擊,經過SQL注入獲取的信息直接反映到應用程序的Web頁面上;
Out-of-band:數據經過不一樣於SQL代碼注入的方式得到(譬如經過郵件等)
推理:這種攻擊是說並無真正的數據傳輸,但攻擊者能夠經過發送特定的請求,重組返回的結果從而獲得一些信息。
不管是哪一種SQL注入,攻擊者都須要構造一個語法正確的SQL查詢,若是應用程序對一個不正確的查詢返回了一個錯誤消息,那麼就和容易從新構造初始的查詢語句的邏輯,進而也就能更容易的進行注入;若是應用程序隱藏了錯誤信息,那麼攻擊者就必須對查詢邏輯進行反向工程,即咱們所謂的「盲SQL注入」
黑盒測試及示例:
這個測試的第一步是理解咱們的應用程序在何時須要訪問數據庫,典型的須要訪問數據庫的時機是:
認證表單:輸入用戶名和密碼以檢查是否有權限
搜索引擎:提交字符串以從數據庫中獲取相應的記錄
電子商務站點:獲取某類商品的價格等信息
做爲測試人員,咱們須要列對全部輸入域的值可能用於查詢的字段作一個表單,包括那些POST請求的隱含字段,而後截取查詢語句併產生錯誤信息。第一個測試每每是用一個單引號「‘」或是分號「;」,前者在SQL中是字符串終結符,若是應用程序沒有過濾,則會產生一條錯誤信息;後者在SQL中是一條SQL語句的終結符,一樣若是沒有過濾,也會產生錯誤信息。在Microsoft SQL Server中,返回的錯誤信息通常是這樣:
Microsoft OLE DB Provider for ODBC Drivers error ‘80040e14’ |
一樣可用於測試的還有「--」以及SQL中的一些諸如「AND」的關鍵字,一般很常見的一種測試是在要求輸入爲數字的輸入框中輸入字符串,會返回以下的錯誤信息:
Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’ |
相似上面這樣的出錯返回信息能讓咱們知道不少數據庫的信息,一般不會返回那麼多信息,會返回諸如「500 Server Error」的信息,那就須要「盲SQL注入」了。注意,咱們須要對全部可能存在SQL注入漏洞的輸入域進行測試,而且在每一個測試用例時只變化一個域的值,從而才能找到真正存在漏洞的輸入域。
下面咱們看一下標準的SQL注入測試是怎樣的。
咱們如下面的SQL查詢爲例:
SELECT * FROM Users WHERE Username='$username' AND Password='$password' |
若是咱們在頁面上輸入如下的用戶名和密碼:
$username = 1' or '1' = '1 |
那麼整個查詢語句就變爲:
SELECT * FROM Users WHERE Username='1' OR '1' = '1' AND Password='1' OR '1' = '1' |
假設參數值是經過GET方法傳遞到服務器的,且域名爲www.example.com,那麼咱們的訪問請求就是:
對上面的SQL語句做簡單分析後咱們就知道因爲該語句永遠爲真,因此確定會返回一些數據,在這種狀況下實際上並未驗證用戶名和密碼,而且在某些系統中,用戶表的第一行記錄是管理員,那這樣形成的後果則更爲嚴重。
另一個查詢的例子以下:
SELECT * FROM Users WHERE ((Username='$username') AND (Password=MD5('$password'))) |
在這個例子中,存在兩個問題,一個是括號的用法,還有一個是MD5哈希函數的用法。對於第一個問題,咱們能夠很容易找到缺失的右括號解決,對於第二個問題,咱們能夠想辦法使第二個條件失效。咱們在查詢語句的最後加上一個註釋符以表示後面的都是註釋,常見的註釋起始符是/*(在Oracle中是--),也就是說,咱們用以下的用戶名和密碼:
$username = 1' or '1' = '1'))/* |
那麼整條SQL語句就變爲:
SELECT * FROM Users WHERE ((Username='1' or '1' = '1'))/*') AND (Password=MD5('$password'))) |
咱們的URL請求就變爲:
Union查詢SQL注入測試
還有一種測試是利用Union的,利用Union能夠鏈接查詢,從而從其餘表中獲得信息,假設咱們有以下的查詢:
SELECT Name, Phone, Address FROM Users WHERE Id=$id |
而後咱們設置id的值爲:
$id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCarTable |
那麼總體的查詢就變爲:
SELECT Name, Phone, Address FROM Users WHERE Id=1 UNION ALL SELECT creditCardNumber,1,1 FROM CreditCarTable |
顯然這樣就能獲得全部信用卡用戶的信息。
盲SQL注入測試
在上面咱們提到過盲SQL注入,即blind SQL injection,它意味着對於某個操做咱們得不到任何信息,一般這是因爲程序員已經編寫了特定的出錯返回頁面,從而隱藏了數據庫結構的信息。
利用推理方法,有時候咱們可以恢復特定字段的值。這種方法一般採用一組對服務器的布爾查詢,依據返回的結果來推斷結果的含義。仍然延續上面的www.example.com,有一個參數名爲id,那麼咱們輸入如下url請求:
顯然因爲語法錯誤,咱們會獲得一個預先定義好的出錯頁面,假設服務器上的查詢語句爲SELECT field1, field2, field3 FROM Users WHERE Id='$Id',假設咱們想要獲得用戶名字段的值,那麼經過一些函數,咱們就能夠逐字符的讀取用戶名的值。在這裏咱們使用如下的函數:
SUBSTRING (text, start, length),ASCII (char),LENGTH (text) |
咱們定義id爲:
$Id=1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1 |
那麼最終的SQL查詢語句爲:
SELECT field1, field2, field3 FROM Users WHERE Id='1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1' |
那麼,若是在數據庫中有用戶名的第一個字符的ASCII碼爲97的話,那麼咱們就能獲得一個真值,那麼咱們就繼續尋找該用戶名的下一個字符;若是沒有的話,那麼咱們就遞增猜想第一個字符的ASCII碼爲98的用戶名,這樣反覆下去就能判斷出合法的用戶名。
那麼,何時咱們能夠結束推理呢,咱們假設id的值爲:
$Id=1' AND LENGTH(username)=N AND '1' = '1 |
其中N是咱們到目前爲止已經分析的字符數目,那麼總體的sql查詢爲:
SELECT field1, field2, field3 FROM Users WHERE Id='1' AND LENGTH(username)=N AND '1' = '1' |
這個查詢的返回值若是是真,那咱們就已經完成了推理而且咱們已經獲得了想要的數值,若是爲假,則表示咱們還要繼續分析。
這種盲SQL注入會要求咱們輸入大量的sql嘗試,有一些自動化的工具可以幫咱們實現,SqlDumper就是這樣的一種工具,對MySQL數據庫進行GET訪問請求。
存儲過程注入
在上一篇《如何防範SQL注入—編程篇》中,咱們提到使用存儲過程是可以防範SQL注入的,但同時也要注意,存儲過程若是使用不得當,使用存儲過程的動態查詢事實上也會形成必定的SQL注入漏洞。
如下面的SQL存儲過程爲例:
Create procedure user_login @username varchar(20), @passwd varchar(20) As |
用戶的輸入以下:
anyusername or 1=1' |
若是咱們沒有對輸入進行驗證,那麼上面的語句就會返回數據庫中的一條記錄。
咱們再看下面的一條:
Create procedure get_report @columnamelist varchar(7900) As |
若是用戶輸入是:
1 from users; update users set password = 'password'; select * |
後面則顯而易見,用戶的全部密碼都被更改且獲得了報表信息。