1.網絡應用和SQL注射
1.1概述
有些網絡數據庫沒有過濾客戶提供的數據中可能有害的字符,SQL注射就是利用插入有害字符進行攻擊的技術。盡管非常容易防范,但因特網上仍然有驚人數量的存儲系統容易受到這種攻擊。這篇文章的目的是指導專業安全組織了解這種技術,并告訴他們正確的,用來防范SQL注射的辦法,以及處理各種常見的,由于非法輸入引起的問題.
1.2背景
在讀這篇文章之前,你應該對數據庫如何工作,以及SQL如何被用來訪問數據庫有一些基礎的了解。我建議您閱讀eXtropia.com的文章“Introduction to Databases for WebDevelopers”。(網址:http://www.extropia.com/tutorials/sql/toc.html)
1.3字符編碼
在大多數的網絡瀏覽器中,標點符號和許多其它符號在用于一個網絡請求前需要把URL編碼,以便被適當地編譯(interpret)。在本文中的例子和截圖中我使用了固定的ASCII字符以保證最大的可讀性。然而,在實際應用中,你需要在HTTP請求中用%25來代替百分號(%),用%2B來代替加號(+)等等。
3.2.5 LIKE語句查詢
另一個大的災難是陷入一個LIKE子句的陷阱.(Seeing the LIKE keyword or percent signs cited in an error message are indications of this situation.)大多數的web搜索程序使用LIKE子句來查詢數據庫,比如下面這個:
|
這里面的%是通配符,在這個例子里,WHERE子句會返回TRUE,只要LASTNAME里有字符串含有strLastNameSearch.為了阻止SQL SERVER返回預計中的記錄,你構造的SQL語句里必須含有LASTNAME里沒有的字符串.web搜索程序搜索的字符串來自于用戶的輸入.通常有一個'和一個%在輸入的字符串之前,因此我們構造字符串時,需要在WHERE子句中匹配它們.如果你提交了NULL作為搜索字符串,那么LIKE的參數會變成"%%",這是一個全匹配,會返回所有的記錄.
3.2.6 “死胡同”
大部分的時候sql injection都要伴隨著大量失敗的實踐,如果你發現你無論如何都不能插入相關的語句,并且無論你怎么做都不對,這個時候你就要判斷自己是否掉進了一個死胡同,很多時候遇到這種情況你很可能是在一個多重嵌套的WHERE和SELECT子句的語句中,或者一些更加復雜的多重嵌套,連使用“;--”都沒有用,所以自己要小心和避免在這種地方停留。
3.2.7 列的數目不匹配問題
我們可以從幾次錯誤中得到很多有用的信息,并且加以調整自己的請求語句,這種信息多了,那就意味著我們離成功不遠了。在猜列名時,如圖所示,我們提交語句后會碰到以下錯誤“在UNION語句中的所有查詢都必須在目標列表中具有相同數目的表達式”,這就是說你需要找出或者說是探測出在合法的請求中有多少個列。
這里我解釋一下,UNION 語句是用來將兩個不同的查詢結果集相加得到一個結果集,UNION使用的唯一要求是兩個查詢的信息(你的查詢語句)必須有相同的列數和相同的數據類型
我舉個例子,web程序中有如下語句:
SQLstring= "SELECT FirstName,LastName,EmployeeID FROM Employees WHERE City ='"&strCity"'"
合法的SELECT語句和我們注入的UNION SELECT語句在WHERE子句中都要有相同的列。就上面的語句來說,如果我要加入UNION 語句的話,前后兩者都要有3個列。并且他們列的數據類型也要相互匹配才可以。如果FirstName這個值是字符串類型的,那么在你注入的語句中所對應的值也應該是字符串類型的。一些數據庫,如ORACLE,是對類型檢查非常嚴格的。其他的數據庫相對要好一些,允許你輸入任何數據類型并且它會自動的把你輸入錯誤的數據類型轉換成正確的。比如SQL數據庫中,你在varchar類型的地方輸入數值類型的數據(如int)是不會報錯的,因為在這里數值類型會被自動轉為字符串類型。但是如果在smallint列處輸入text類型則被認為是非法的,因為text類型不能被轉換成int類型。把數值類型的數據轉換成字符串型是被允許的,而反之則不行,所以默認都是使用數值類型的數據。
要想知道我們要注入的目標語句中有多少個列,你就要試探性的往UNION SELECT子句中添加相應的值,直到它不報“在UNION語句中的所有查詢都必須在目標列表中具有相同數目的表達式”這樣的錯為止。如圖所示,如果你遇到的是數據類型不匹配的錯誤,那么你要去改變列的數據類型。如果返回消息只是一個轉換數據類型失敗的錯誤,那就說明你已經猜對了列的數目,只是其中有個別的列的數據類型不對。那么接下來要做的就是判斷是哪個列的數據類型的不正確導致的錯誤。然后將他改過來就可以了。
如果一切順利,那么祝賀你,你會得到一個和上面格式類似的而且是合法的頁面;無論動態頁面在哪里出現,你都可以構造自己的語句應對自如。
3.2.8.WHERE關鍵字
報錯為“無效的列名'EmployeeID'”,這個問題可能是由我們注入的語句結尾的WHERE關鍵字引起的,舉例說明:
SQLString="SELECT FirstName,LastName,Title FROM Employees WHERE City='"&strcity&"'AND Country ='USA'"
如果我們注入的語句是UNION ALL SELECT OtherField FROM OtherTable WHERE 1=1 那么會得到如下的提交語句:
SELECT FirstName, LastName, Title FROM Employees WHERE City = 'NoSuchCity' UNION ALL SELECT OtherField FROM OtherTable WHERE 1=1 AND Country = 'USA'
這樣就會報錯:[Microsoft][ODBC SQL Server Driver][SQL Server]無效的列名 'Country'。
其實問題就是因為你注入的語句后,系統沒有在從數據庫的表中找到一個叫'Country'的列名。我們這里可以簡單的用“;--”注釋符號將其注釋掉(如果我們是SQL Server)。或者干脆繼續猜其他的列名,然后構造合法請求就如我們上一節講到的一樣。
表名的枚舉
我們已經開始掌握如何來使用注入進行攻擊,但是我們還要確定要從哪個表得到信息,換句話說就是我們要的到關鍵的表名才能獲得我們想要的有用信息。如何獲得表名呢?在SQL Server中,你可以很容易得從數據庫中得到全部的表名和列名。但是在Oracle和Access中,你就不一定能如此輕易的得到了,這要看WEB程序對數據庫的訪問權限了。關鍵在于是否能得到系統建立時自動生成的表中包含的表名和列名。如在SQL Server中,它們分別為'sysobjects'和'syscolumns',(在本文最后我們將給出其他數據庫系統自建表和相應的列名)我們用以下的句子可以在這些表中列出數據庫的所有列名和表名,(根據情況自行修改):
SELECT name FROM sysobjects WHERE xtype = 'U'
這句話會返回數據庫中用戶定義的所有表,如果我們看到我們感興趣的或者是想要看的表,那么我們就把他打開,這里以Orders為例構造語句:SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = 'Orders')得到結果如圖。
3.2.10.單一紀錄
上面我們構造的語句返回了大量的信息,如果你只想顯示一條數據紀錄也是可以的。你完全可以構造你的注入語句來得到你想要的唯一的信息。我們只要在WHERE子句中添加關鍵字來避免某些行的關鍵字被選中就可以了。我來舉個列子:' UNION ALL SELECT name, FieldTwo, FieldThree FROM TableOne WHERE ''='
我們這樣就可以得到FieldOne,FieldTwo和FieldThree的第一個值,假設我們的到的分別是"Alpha", "Beta"和"Delta"。注意,更有意思的來了,我們要得到第2行的值,怎么構造下面的語句呢?這樣來:' UNION ALL SELECT FieldOne, FieldTwo, FieldThree FROM TableOne WHERE FieldOne NOT IN ('Alpha') AND FieldTwo NOT IN ('Beta') AND FieldThree NOT IN ('Delta') AND ''='
這里有一個子句“NOT IN VALUES”,它的作用是不再返回我們已經得到的信息,即不是alpha,不是beta,不是delta.既然都不是,數據庫就會傻乎乎的告訴我們第二行的值。我們再假設我們得到第二行的值為"AlphaAlpha", "BetaBeta"和"DeltaDelta"。
我們來獲得第三行的值,構造語句如下:' UNION ALL SELECT FieldOne, FieldTwo, FieldThree FROM TableOne WHERE FieldOne NOT IN ('Alpha', 'AlphaAlpha') AND FieldTwo NOT IN ('Beta', 'BetaBeta') AND FieldThree NOT IN ('Delta', 'DeltaDelta') AND ''='
這樣就避免了得到第一次和第二次我們已經得到的值,我們就這樣試下去會得到數據庫中所有的值。這看起來好像確實比較麻煩,但在這里卻是最有效的,不是么?
3.3 插入
3.3.1 插入基礎
關鍵字INSERT 被用于向數據庫添加信息,通常使用INSERT主要在包括用戶注冊,論壇,添加商品到購物車,等等。檢查INSERT使用的弱點和檢查WHERE一樣。你可能不想使用INSERT,如何避免被利用弱點是一個重要的考慮問題。INSERT注入嘗試常常會讓數據庫以行形式返回結果導致泛濫的單獨的引用和SQL關健字的意義可能改變.壬能的過濾象s/[^0-9a-zA-Z]//g 這樣的特殊字符。可能的時候盡量使用數字,在這以后只使用數字和字母。如果你需要包括各種各樣的標志或標點。確信完全的把它們轉換成html標記,像“"e;" or ">”。例如,一個用戶提交了一個email地址只允許使用數字和字母還有"@", "_", "." 和"-"。僅僅只有這些字符可以轉換成html標記。
4.2. 編寫安全的web程序
這里同樣有很少的特殊的sql注入規則。First, prepend and append a quote to all user input。
盡管數據使數字。其次,限制網頁應用程序的數據庫用戶在數據庫里的權限。不要給這個用戶訪問所有的存儲過程的權利如果這個用戶只需要訪問一些預定義的。
這部分包括了所有在sql注入中有用的系統表,你可以在google上搜索到每一個的表的列的定義
5.1. MS SQL Server
Sysobjects
syscolumns
5.2. MS Access Server
MSysACEs
MSysObjects
MSysQueries
MSysRelationships
5.3. Oracle
SYS.USER_OBJECTS
SYS.TAB SYS.USER_TABLES
SYS.USER_VIEWS SYS.ALL_TABLE
S SYS.USER_TAB_COLUMNS
SYS.USER_CONSTRAINTS SYS.USER_TRIGGERS
SYS.USER_CATALOG


