Web十大安全隱患之SQL注入

 注入往往是應用程序缺少對輸入進行安全性檢查所引起的,***者把一些包含指令的數據發送給解釋器,解釋器會把收到的數據轉換成指令執行。常見的注入包括SQL注入,OS Shell,LDAP,Xpath,Hibernate等等,而其中SQL注入尤爲常見。這種***所造成的後果往往很大,一般整個數據庫的信息都能被讀取或篡改,通過SQL注入,***者甚至能夠獲得更多的包括管理員的權限。
       
    先來說說sql注入漏洞是怎麼產生的,或者說對於一個程序開發人員應該怎麼防範SQL注入的吧。

    SQL注入往往是在編寫包含用戶輸入的動態數據庫查詢時產生的,但其實防範SQL注入的方法非常簡單。程序員只不再寫動態查詢,或防止用戶輸入包含能夠破壞查詢邏輯的惡意SQL語句,就能夠防範SQL注入。當然,我作爲一個測試人員說起來很容易,做起來就難了。
  我本人只會java,所以後邊我就用Java代碼作爲示例:
  1. <font face="宋體" color="#000000">String query ="SELECT account_balance FROM  user_data WHERE user_name ="
  2.     + request.getParameter("customerName");
  3.    
  4.   try {
  5.   Statement statement =
  6.   connection.createStatement( …);
  7.   ResultSet results =
  8.   Statement.executeQuery(query);
  9.   }</font>
複製代碼


  在以上代碼中,我們可以看到並未對變量customerName做驗證,customerName的值可以直接附在query語句的後面傳送到數據庫執行,那麼***者可以將任意的sql語句注入。


    上邊說到怎麼產生的,我們接着說怎麼樣才能讓sql注入的漏洞避免。本來計劃把這一段放在如何測試之後再介紹,但是貌似先介紹出來更方便理解。

防範方法一 :參數化查詢
  參數化查詢是所有開發人員在做數據庫查詢時首先需要學習的,參數化查詢迫使所有開發者首先要定義好所有的SQL代碼,然後再將每個參數逐個傳入,這種編碼風格就能夠讓數據庫辨明代碼和數據。
  參數化查詢能夠確保***者無法改變查詢的內容,在下面修正過的例子中,如果***者輸入了UsrID是“’or ‘1 ‘=’1”,參數化查詢會去查找一個完全滿足名字爲‘or ‘1 ‘=’ 1的用戶。
  對於不同編程語言,有一些不同的建議:
  Java——使用帶綁定變量的PreparedStatement();
   其他的:。。。不好意思,真不會
  示例:
  1. <font face="宋體" color="#000000">String custname = request.getParameter("customerName");
  2.   String query ="SELECT account_balance FROM user_data WHERE user_name= ?";
  3.    
  4.   PreparedStatement pstmt = connection.prepareStatement(query);
  5.   Pstmt.setString1,custname();
  6.   ResultSet results = pstmt.executeQuery();</font>
複製代碼
防範方法二:存儲過程
  存儲過程和參數化查詢的作用是一樣的,唯一的不同在於存儲過程是預先定義並存放在數據庫中,從而被應用程序調用的。
  Java存儲過程示例:
  1. String custname = request.getParameter("customerName");
  2.   try {
  3.         CallableStatement cs = connection.prepareCall("call sp_getAccountBalance(?)}");
  4.         cs.setString(1,custname);
  5.         Result results = cs.executeQuery();
  6.   }catch(SQLException se){
  7.   //error handling
  8.   }
複製代碼
防範方法三:對所有用戶輸入進行轉義
  我們知道每個DBMS都有一個字符轉義機制來告知DBMS輸入的是數據而不是代碼,如果我們將所有用戶的輸入都進行轉義,那麼DBMS就不會混淆數據和代碼,也就不會出現SQL注入了。
  當然,如果要採用這種方法,那麼你就需要對所使用的數據庫轉義機制,也可以使用現存的諸如OWASP ESAPI的escaping routines。ESAPI目前是基於MySQL和Oracle的轉義機制的,使用起來也很方便。一個Oracle的ESAPI的使用示例如下:
  1. ESAPI.encoder().encodeForSQL(new  OracleCodec(),queryparam);
複製代碼
  那麼,假設你有一個要訪問Oracle數據庫的動態查詢代碼如下:
  1. String query ="SELECT user_id FROM user_data  WHERE user_name = ‘"+req.getParameter("userID")+"’ and user_password = ‘"+req.getParameter("pwd")+"’";
  2.   try {
  3.         Statement statement =  connection.createStatement(…);
  4.         ResultSet results =  statement.executeQuery(query) ;
  5.   }
複製代碼
  那麼,你就必須重寫你的動態查詢的第一行如下:
  1. Codec ORACLE_CODEC = new OracleCodec();
  2.   String query ="SELECT user_id FROM user_data WHERE user_name = ‘"+
  3.   ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("userID"))+"’ and user_password = ‘"+
  4.   ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter("pwd"))+"’";
複製代碼
  最小權限法:
  爲了避免注入***對數據庫造成的損害,我們可以把每個數據庫用戶的權限儘可能縮小,不要把DBA或管理員的權限賦予你應用程序賬戶,在給用戶權限時是基於用戶需要什麼樣的權限,而不是用戶不需要什麼樣的權限。當一個用戶只需要讀的權限時,我們就只給他讀的權限,當用戶只需要一張表的部分數據時,我們寧願另建一個視圖讓他訪問。
  如果你的策略是都是用存儲過程的話,那麼僅允許應用程序的賬戶執行這些查詢,而不給他們直接訪問數據庫表的權限。諸如此類的最小權限法能夠在很大程度上保證我們數據庫的安全。
  輸入驗證白名單法:
  輸入驗證能夠在數據傳遞到SQL查詢前就察覺到輸入是否正確合法,採用白名單而不是黑名單則能在更大程度上保證數據的合法性。


上面說的是關於sql注入漏洞的產生和開發人員的防範方式,那麼對於我們測試人員來說,如何測試sql注入漏洞是否存在呢?
  首先,我們將SQL注入***能分爲以下三種類型:
  Inband數據經由SQL代碼注入的通道取出,這是最直接的一種***,通過SQL注入獲取的信息直接反映到應用程序的Web頁面上;
  Out-of-band數據通過不同於SQL代碼注入的方式獲得(譬如通過郵件等)
  推理:這種***是說並沒有真正的數據傳輸,但***者可以通過發送特定的請求,重組返回的結果從而得到一些信息。
  不論是哪種SQL注入,***者都需要構造一個語法正確的SQL查詢,如果應用程序對一個不正確的查詢返回了一個錯誤消息,那麼就和容易重新構造初始的查詢語句的邏輯,進而也就能更容易的進行注入;如果應用程序隱藏了錯誤信息,那麼***者就必須對查詢邏輯進行反向工程,即我們所謂的“SQL盲注”
  黑盒測試及示例:
  這個測試的第一步是理解我們的應用程序在什麼時候需要訪問數據庫,典型的需要訪問數據庫的時機是:
  認證表單:輸入用戶名和密碼以檢查是否有權限
  搜索引擎:提交字符串以從數據庫中獲取相應的記錄
  電子商務站點:獲取某類商品的價格等信息
  作爲測試人員,我們需要列對所有輸入域的值可能用於查詢的字段做一個表單,包括那些POST請求的隱含字段,然後截取查詢語句併產生錯誤信息。第一個測試往往是用一個單引號“‘”或是分號“;”,前者在SQL中是字符串終結符,如果應用程序沒有過濾,則會產生一條錯誤信息;後者在SQL中是一條SQL語句的終結符,同樣如果沒有過濾,也會產生錯誤信息。在Microsoft SQL Server中,返回的錯誤信息一般是這樣:
  1. Microsoft OLE DB Provider for  ODBC Drivers error ‘80040e14’
  2.   [Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before  the character string ‘’.
  3.   /target/target.asp, line 113
複製代碼
  同樣可用於測試的還有“--”以及SQL中的一些諸如“AND”的關鍵字,通常很常見的一種測試是在要求輸入爲數字的輸入框中輸入字符串,會返回如下的錯誤信息:
  1. Microsoft OLE DB Provider for  ODBC Drivers error ‘80040e07’
  2.   [Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the  varchar value ‘tester’ to a column of data type int.
  3.   /target/target.asp, line 113
複製代碼
  類似上面這樣的出錯返回信息能讓我們知道很多數據庫的信息,通常不會返回那麼多信息,會返回諸如“500 Server Error”的信息,那就需要“盲SQL注入”了。注意,我們需要對所有可能存在SQL注入漏洞的輸入域進行測試,並且在每個測試用例時只變化一個域的值,從而才能找到真正存在漏洞的輸入域。
 
下面我們看一下標準的SQL注入測試是怎樣的,這段我在上邊的帖子裏說過了,這裏再詳細重複一下
  仍然以下面的SQL查詢爲例:
  1. SELECT * FROM Users WHERE  Username='$username' AND Password='$password'
複製代碼
  如果我們在頁面上輸入以下的用戶名和密碼:
  1. $username = 1' or '1' = '1
  2. $password = 1' or '1' = '1
複製代碼
  那麼整個查詢語句就變爲:
  1.  SELECT * FROM Users WHERE  Username='1' OR '1' = '1' AND Password='1' OR '1' = '1'
複製代碼
  假設參數值是通過GET方法傳遞到服務器的,且域名爲www.example.com,那麼我們的訪問請求就是:
  1. [url]http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1&password=1'%20or%20'1'%20=%20'1[/url]
複製代碼
  對上面的SQL語句作簡單分析後我們就知道由於該語句永遠爲真,所以肯定會返回一些數據,在這種情況下實際上並未驗證用戶名和密碼,並且在某些系統中,用戶表的第一行記錄是管理員,那這樣造成的後果則更爲嚴重。
  另外一個查詢的例子如下:
  1.  SELECT * FROM Users WHERE ((Username='$username')  AND (Password=MD5('$password')))
複製代碼
  在這個例子中,存在兩個問題,一個是括號的用法,還有一個是MD5哈希函數的用法。對於第一個問題,我們可以很容易找到缺失的右括號解決,對於第二個問題,我們可以想辦法使第二個條件失效。我們在查詢語句的最後加上一個註釋符以表示後面的都是註釋,常見的註釋起始符是/*(在Oracle中是--),也就是說,我們用如下的用戶名和密碼:
  1. $username = 1' or '1' = '1'))/*
  2.     $password = foo
複製代碼
  那麼整條SQL語句就變爲:
  1. SELECT * FROM Users WHERE  ((Username='1' or '1' = '1'))/*') AND (Password=MD5('$password')))
複製代碼
  我們的URL請求就變爲:
  1. [url]http://www.example.com/index.php?username=1'%20or%20'1'%20=%20'1'[/url]))/*&password=foo
複製代碼
 
Union查詢SQL注入測試
  還有一種測試是利用Union的,利用Union可以連接查詢,從而從其他表中得到信息,假設我們有如下的查詢:
  1. SELECT Name, Phone, Address FROM  Users WHERE Id=$id
複製代碼
  然後我們設置id的值爲:
  1. $id=1 UNION ALL SELECT creditCardNumber,1,1  FROM CreditCarTable
複製代碼
  那麼整體的查詢就變爲:
  1. 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請求:
  1. [url]http://www.example.com/index.php?id=1'[/url]
複製代碼
  顯然由於語法錯誤,我們會得到一個預先定義好的出錯頁面,假設服務器上的查詢語句爲SELECT field1, field2, field3 FROMUsers WHERE Id='$Id',假設我們想要得到用戶名字段的值,那麼通過一些函數,我們就可以逐字符的讀取用戶名的值。在這裏我們使用以下的函數:
  1.  SUBSTRING (text, start, length),ASCII (char),LENGTH (text)
複製代碼
  我們定義id爲:
  1.  $Id=1' AND  ASCII(SUBSTRING(username,1,1))=97 AND '1'='1
複製代碼
  那麼最終的SQL查詢語句爲:
  1. SELECT field1, field2, field3  FROM Users WHERE Id='1' AND ASCII(SUBSTRING(username,1,1))=97 AND '1'='1'
複製代碼
  那麼,如果在數據庫中有用戶名的第一個字符的ASCII碼爲97的話,那麼我們就能得到一個真值,那麼我們就繼續尋找該用戶名的下一個字符;如果沒有的話,那麼我們就遞增猜測第一個字符的ASCII碼爲98的用戶名,這樣反覆下去就能判斷出合法的用戶名。
  那麼,什麼時候我們可以結束推理呢,我們假設id的值爲:
  1. $Id=1' AND LENGTH(username)=N  AND '1' = '1
複製代碼
  其中N是我們到目前爲止已經分析的字符數目,那麼整體的sql查詢爲:
  1. SELECT field1, field2, field3  FROM Users WHERE Id='1' AND LENGTH(username)=N AND '1' = '1'
複製代碼
  這個查詢的返回值如果是真,那我們就已經完成了推理並且我們已經得到了想要的數值,如果爲假,則表示我們還要繼續分析。
  這種SQL盲注會要求我們輸入大量的sql嘗試,也有一些工具能夠幫我們實現。我所用過的只有sqlmap,以後再介紹。
 
存儲過程注入
  在上面寫的內容中,提到使用存儲過程是能夠防範SQL注入的,但同時也要注意,存儲過程如果使用不得當,使用存儲過程的動態查詢事實上也會造成一定的SQL注入漏洞。
  以下面的SQL存儲過程爲例:
  1.  Create procedure user_login  @username varchar(20), @passwd varchar(20) As
  2.     Declare @sqlstring varchar(250)
  3.     Set @sqlstring = ‘
  4.     Select 1 from users
  5.     Where username = ‘ + @username + ‘ and passwd = ‘ + @passwd
  6.     exec(@sqlstring)
  7.     Go
複製代碼
  用戶的輸入如下:
  1. anyusername or 1=1'
  2. anypassword
複製代碼
  如果我們沒有對輸入進行驗證,那麼上面的語句就會返回數據庫中的一條記錄。
  我們再看下面的一條:
  1.  Create procedure get_report  @columnamelist varchar(7900) As
  2.     Declare @sqlstring varchar(8000)
  3.     Set @sqlstring = ‘
  4.     Select ‘ + @columnamelist + ‘ from ReportTable‘
  5.     exec(@sqlstring)
  6.     Go
複製代碼
  如果用戶輸入是:
  1.  1 from users; update users set  password = 'password'; select *
複製代碼
  後面則顯而易見,用戶的所有密碼都被更改且得到了報表信息。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章