10大最重要的Web安全風險之一:A1--注入

OWASP TOP10

A1-注入    php

A2-跨站腳本(XSS)   程序員

A3-錯誤的認證和會話管理  sql

A4-不正確的直接對象引用   數據庫

A5-僞造跨站請求(CSRF)     -- Cross-Site Request Forgery 編程

A7-限制遠程訪問失敗  api

A8-未驗證的重定向和傳遞  安全

A9-不安全的加密存儲    服務器

A10-不足的傳輸層保護 編程語言


A1-注入

注入每每是應用程序缺乏對輸入進行安全性檢查所引發的,攻擊者把一些包含指令的數據發送給解釋器,解釋器會把收到的數據轉換成指令執行。常見的注入包括SQL注入,OS Shell,LDAP,Xpath,Hibernate等等,而其中SQL注入尤其常見。這種攻擊所形成的後果每每很大,通常整個數據庫的信息都能被讀取或篡改,經過SQL注入,攻擊者甚至可以得到更多的包括管理員的權限。 ide

防範SQL注入——編程篇

SQL注入每每是在程序員編寫包含用戶輸入的動態數據庫查詢時產生的,但其實防範SQL注入的方法很是簡單。程序員只要a)再也不寫動態查詢,或b)防止用戶輸入包含可以破壞查詢邏輯的惡意SQL語句,就可以防範SQL注入。在這篇文章中,咱們將會說明一些很是簡單的防止SQL注入的方法。

  咱們用如下Java代碼做爲示例:

String query =SELECT account_balance FROM user_data WHERE user_name =
  + request.getParameter(customerName);
 
try {
Statement statement =
connection.createStatement( …);
ResultSet results =
Statement.executeQuery(query);
}

  在以上代碼中,咱們能夠看到並未對變量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);
String query =SELECT account_balance FROM user_data WHERE user_name= ?;
 
PreparedStatement pstmt = connection.prepareStatement(query);
Pstmt.setString1,custname();
ResultSet results = pstmt.executeQuery();

  C# .Net示例:

String query =SELECT account_balance FROM user_data WHERE user_name = ?;
Try {    
   OleDbCommand command = new OleDbCommand(query,connection);
   command.Parameters.Add(new OleDbParameter(customerName,CustomerName.Text));
   OleDbDataReader reader = command.ExecuteReader();
  } catch (OleDbException se){
  //error handling
}

防範方法二:存儲過程

  存儲過程和參數化查詢的做用是同樣的,惟一的不一樣在於存儲過程是預先定義並存放在數據庫中,從而被應用程序調用的。

  Java存儲過程示例:

String custname = request.getParameter(customerName);
try {
      CallableStatement cs = connection.prepareCall(call sp_getAccountBalance(?)});
      cs.setString(1,custname);
      Result results = cs.executeQuery();
}catch(SQLException se){
//error handling
}

  VB .Net存儲過程示例:

Try
Dim command As SqlCommand = new SqlCommand(sp_getAccountBalance,connection)
 command.CommandType = CommandType.StoredProcedure
 command.Parameters.Add(new SqlParameter(@CustomerName,CustomerName.Text))
 Dim reader As SqlDataReader = command.ExecuteReader()
 ‘…
Catch se As SqlException
 ‘error handling
End 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)+;
try {
      Statement statement = connection.createStatement(…);
      ResultSet results = statement.executeQuery(query) ;
}

  那麼,你就必須重寫你的動態查詢的第一行以下:

Codec ORACLE_CODEC = new OracleCodec();
String query =SELECT user_id FROM user_data WHERE user_name = ‘+
ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter(userID))+’ and user_password = ‘+
ESAPI.encoder().encodeForSQL(ORACLE_CODEC,req.getParameter(pwd))+;

  固然,爲了保證本身代碼的可讀性,咱們也能夠構建本身的OracleEncoder:

Encoder e = new OracleEncoder();
String query =SELECT user_id FROM user_data WHERE user_name = ‘
      + oe.encode(req.getParameter(userID)) +’ and user_password = ‘
      + oe.encode(req.getParameter(pwd))+;

  除了上面所說的三種防範方法之外,咱們還建議能夠用如下兩種附加的方法來防範SQL注入:最小權限法、輸入驗證白名單法。

  最小權限法:

  爲了不注入攻擊對數據庫形成的損害,咱們能夠把每一個數據庫用戶的權限盡量縮小,不要把DBA或管理員的權限賦予你應用程序帳戶,在給用戶權限時是基於用戶須要什麼樣的權限,而不是用戶不須要什麼樣的權限。當一個用戶只須要讀的權限時,咱們就只給他讀的權限,當用戶只須要一張表的部分數據時,咱們寧願另建一個視圖讓他訪問。

  若是你的策略是都是用存儲過程的話,那麼僅容許應用程序的帳戶執行這些查詢,而不給他們直接訪問數據庫表的權限。諸如此類的最小權限法可以在很大程度上保證咱們數據庫的安全。

  輸入驗證白名單法:

  輸入驗證可以在數據傳遞到SQL查詢前就察覺到輸入是否正確合法,採用白名單而不是黑名單則能在更大程度上保證數據的合法性。

防範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’
[Microsoft][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark before the character string ‘’.
/target/target.asp, line 113

  一樣可用於測試的還有「--」以及SQL中的一些諸如「AND」的關鍵字,一般很常見的一種測試是在要求輸入爲數字的輸入框中輸入字符串,會返回以下的錯誤信息:

Microsoft OLE DB Provider for ODBC Drivers error ‘80040e07’
[Microsoft][ODBC SQL Server Driver][SQL Server]Syntax error converting the varchar value ‘tester’ to a column of data type int.
/target/target.asp, line 113

  相似上面這樣的出錯返回信息能讓咱們知道不少數據庫的信息,一般不會返回那麼多信息,會返回諸如「500 Server Error」的信息,那就須要「盲SQL注入」了。注意,咱們須要對全部可能存在SQL注入漏洞的輸入域進行測試,而且在每一個測試用例時只變化一個域的值,從而才能找到真正存在漏洞的輸入域。

下面咱們看一下標準的SQL注入測試是怎樣的。

  咱們如下面的SQL查詢爲例:

  SELECT * FROM Users WHERE Username='$username' AND Password='$password'

  若是咱們在頁面上輸入如下的用戶名和密碼:

  $username = 1' or '1' = '1
  $password = 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'))/*
  $password = foo

  那麼整條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
  Declare @sqlstring varchar(250)
  Set @sqlstring = ‘
  Select 1 from users
  Where username = ‘ + @username  + ‘ and passwd = ‘ + @passwd
  exec(@sqlstring)
  Go

  用戶的輸入以下:

  anyusername or 1=1'
  anypassword

  若是咱們沒有對輸入進行驗證,那麼上面的語句就會返回數據庫中的一條記錄。

  咱們再看下面的一條:

  Create procedure get_report @columnamelist varchar(7900) As
  Declare @sqlstring varchar(8000)
  Set @sqlstring = ‘
  Select ‘ + @columnamelist + ‘ from ReportTable‘
  exec(@sqlstring)
  Go

  若是用戶輸入是:

  1 from users; update users set password = 'password'; select *

  後面則顯而易見,用戶的全部密碼都被更改且獲得了報表信息。

相關文章
相關標籤/搜索