相關(guān)關(guān)鍵詞
關(guān)于我們
最新文章
- PHP中opcode緩存簡單用法分析
- thinkPHP控制器變量在模板中的顯示方法示例
- PHP move_uploaded_file() 函數(shù)(將上傳的文件移動到新位置)
- dirname(__FILE__)的含義和應(yīng)用說明
- thinkPHP5框架實(shí)現(xiàn)分頁查詢功能的方法示例
- PHP中單雙號與變量
- PHP獲得當(dāng)日零點(diǎn)時間戳的方法分析
- Laravel ORM對Model::find方法進(jìn)行緩存示例詳解
- PHP讀寫文件高并發(fā)處理操作實(shí)例詳解
- 【CLI】利用Curl下載文件實(shí)時進(jìn)度條顯示的實(shí)現(xiàn)
測試工具:SQL盲注攻擊的簡單介紹
1 簡介
1.1 普通SQL注入技術(shù)概述
目前沒有對SQL注入技術(shù)的標(biāo)準(zhǔn)定義,微軟中國技術(shù)中心從2個方面進(jìn)行了描述[1]:
(1) 腳本注入式的攻擊
(2) 惡意用戶輸入用來影響被執(zhí)行的SQL腳本
根據(jù)Chris Anley的定義[2], 當(dāng)一個攻擊者通過在查詢語句中插入一系列的SQL語句來將數(shù)據(jù)寫入到應(yīng)用程序中,這種方法就可以定義成SQL注入。Stephen Kost[3]給出了這種攻擊形式的另一個特征,“從一個數(shù)據(jù)庫獲得未經(jīng)授權(quán)的訪問和直接檢索”,SQL注入攻擊就其本質(zhì)而言,它利用的工具是SQL的語法,針對的是應(yīng)用程序開發(fā)者編程過程中的漏洞,“當(dāng)攻擊者能夠操作數(shù)據(jù),往應(yīng)用程序中插入一些SQL語句時,SQL注入攻擊就發(fā)生了”。實(shí)際上,SQL注入是存在于常見的多連接的應(yīng)用程序中一種漏洞,攻擊者通過在應(yīng)用程序中預(yù)先定義好的查詢語句結(jié)尾加上額外的SQL語句元素,欺騙數(shù)據(jù)庫服務(wù)器執(zhí)行非授權(quán)的任意查詢。這類應(yīng)用程序一般是網(wǎng)絡(luò)應(yīng)用程序(Web Application),它允許用戶輸入查詢條件,并將查詢條件嵌入SQL請求語句中,發(fā)送到與該應(yīng)用程序相關(guān)聯(lián)的數(shù)據(jù)庫服務(wù)器中去執(zhí)行。通過構(gòu)造一些畸形的輸入,攻擊者能夠操作這種請求語句去獲取預(yù)先未知的結(jié)果。
在風(fēng)險方面,SQL注入攻擊是位居前列的,與緩沖區(qū)溢出等漏洞基本相當(dāng)。而且如果要實(shí)施緩沖區(qū)溢出攻擊,攻擊者必須首先能繞過站點(diǎn)的防火墻;而對于SQL注入攻擊,由于防火墻為了使用戶能訪問網(wǎng)絡(luò)應(yīng)用程序,必須允許從Internet到Web服務(wù)器的正向連接,因此一旦網(wǎng)絡(luò)應(yīng)用程序有注入漏洞,攻擊者就可以直接訪問數(shù)據(jù)庫進(jìn)而甚至能夠獲得數(shù)據(jù)庫所在的服務(wù)器的訪問權(quán),因此在某些情況下,SQL注入攻擊的風(fēng)險要高于所有其他漏洞。
SQL注入攻擊利用的是SQL語法,這使得這種攻擊具有廣泛性。理論上說,對于所有基于SQL語言標(biāo)準(zhǔn)的數(shù)據(jù)庫軟件包括SQL Server,Oracle,MySQL, DB2,Informix等以及與之連接的網(wǎng)絡(luò)應(yīng)用程序包括Active/Java Server Pages, Cold Fusion Management, PHP或Perl等都是有效的。當(dāng)然各種軟件有自身的特點(diǎn),實(shí)際的攻擊代碼可能不盡相同。SQL注入攻擊的原理相對簡單,且各類基于數(shù)據(jù)庫系統(tǒng)的應(yīng)用程序被廣泛使用,介紹注入漏洞和利用方法的公開出版物也大量問世,造成近年SQL注入攻擊的數(shù)量一直增長,注入攻擊的形式也有被濫用的趨勢。
關(guān)于針對MS SQL Server的普通SQL注入技術(shù)的詳細(xì)介紹,可以參考Chris Anley所撰的“SQL Server應(yīng)用程序中的高級SQL注入”[2]一文和其后續(xù)“更多的高級SQL注入”[4],Cesar Cerrundo所撰的“利用SQL注入操縱Microsoft SQL Server” [5]一文,以及SPI實(shí)驗(yàn)室的Kevin Spett撰寫的白皮書“SQL注入 你的網(wǎng)絡(luò)應(yīng)用程序是否會受攻擊?” [6];而針對Oracle的普通SQL注入技術(shù)介紹,可以參考Stephen Kost的“針對Oracle開發(fā)人員的SQL注入攻擊簡介”[3]一文。
1.2 SQL注入攻擊的防御手段
由于越來越多的攻擊利用了SQL注入技術(shù),也隨之產(chǎn)生了很多試圖解決注入漏洞的方案。目前被提出的方案有:
(1) 在服務(wù)端正式處理之前對提交數(shù)據(jù)的合法性進(jìn)行檢查;
(2) 封裝客戶端提交信息;
(3) 替換或刪除敏感字符/字符串;
(4) 屏蔽出錯信息。
方案(1)被公認(rèn)是最根本的解決方案,在確認(rèn)客戶端的輸入合法之前,服務(wù)端拒絕進(jìn)行關(guān)鍵性的處理操作,不過這需要開發(fā)者能夠以一種安全的方式來構(gòu)建網(wǎng)絡(luò)應(yīng)用程序,雖然已有大量針對在網(wǎng)絡(luò)應(yīng)用程序開發(fā)中如何安全地訪問數(shù)據(jù)庫的文檔出版,但仍然有很多開發(fā)者缺乏足夠的安全意識,造成開發(fā)出的產(chǎn)品中依舊存在注入漏洞;方案(2)的做法需要RDBMS的支持,目前只有Oracle采用該技術(shù);方案(3)則是一種不完全的解決措施,例如,當(dāng)客戶端的輸入為“…ccmdmcmdd…”時,在對敏感字符串“cmd”替換刪除以后,剩下的字符正好是“…cmd…”;方案(4)是目前最常被采用的方法,很多安全文檔都認(rèn)為SQL注入攻擊需要通過錯誤信息收集信息,有些甚至聲稱某些特殊的任務(wù)若缺乏詳細(xì)的錯誤信息則不能完成,這使很多安全專家形成一種觀念,即注入攻擊在缺乏詳細(xì)錯誤的情況下不能實(shí)施。
而實(shí)際上,屏蔽錯誤信息是在服務(wù)端處理完畢之后進(jìn)行補(bǔ)救,攻擊其實(shí)已經(jīng)發(fā)生,只是企圖阻止攻擊者知道攻擊的結(jié)果而已。本文所介紹SQL盲注技術(shù)就是一些攻擊者使用的新技術(shù),其在錯誤信息被屏蔽的情況下使攻擊者仍能獲得所需的信息,并繼續(xù)實(shí)施注入攻擊。
1.3 本文的結(jié)構(gòu)組織
為了理解盲注攻擊,我們首先將介紹確定SQL注入漏洞所需的服務(wù)器的最小響應(yīng);其次,我們將構(gòu)造一個合乎語法的SQL請求,并可以將之替換成任何有效的SQL請求;最后,我們將討論在沒有詳細(xì)錯誤信息的情況下如何利用UNION SELECT語句。本文所討論的盲注攻擊的條件是我們在攻擊前對網(wǎng)絡(luò)應(yīng)用程序、數(shù)據(jù)庫類型、表結(jié)構(gòu)等等信息都一無所知,這些信息都需要在注入的過程中通過探測獲得。
2 確定注入漏洞
要進(jìn)行SQL注入攻擊,首先當(dāng)然是確認(rèn)要攻擊的網(wǎng)絡(luò)應(yīng)用程序存在注入漏洞,因此攻擊者首先必須能確立一些與服務(wù)器產(chǎn)生的錯誤相關(guān)的提示類型。盡管錯誤信息本身已被屏蔽,網(wǎng)絡(luò)應(yīng)用程序仍然具有能區(qū)分正確請求和錯誤請求的能力,攻擊者只需要學(xué)習(xí)去識別這些提示,尋找相關(guān)錯誤,并確認(rèn)其是否和SQL相關(guān)。
2.1 識別錯誤
一個網(wǎng)絡(luò)應(yīng)用程序主要會產(chǎn)生兩種類型的錯誤,第一種是由Web服務(wù)器產(chǎn)生的代碼異常(exception),類似于“500:Internal Server Error”,通常如果SQL注入語句出現(xiàn)語法錯誤,比如出現(xiàn)未閉合的引號,就會使服務(wù)器拋出這類異常。如果要屏蔽該類錯誤,一般會采用將默認(rèn)的錯誤信息替換成一個事先定制的HTML頁面,但只要觀察到有這種響應(yīng)出現(xiàn),就可以確認(rèn)其實(shí)是發(fā)生了服務(wù)器錯誤。在其他情況下,為了進(jìn)一步屏蔽該類錯誤,有些服務(wù)器一出現(xiàn)異常,會簡單地跳轉(zhuǎn)到主頁面或前一個訪問過的頁面, 或者顯示一條簡單的錯誤消息但不提供任何細(xì)節(jié)。
第二類錯誤是由應(yīng)用程序代碼產(chǎn)生的,這代表其開發(fā)者有較好的編程習(xí)慣。這類應(yīng)用程序考慮到可能會出現(xiàn)一些無效的情況,并分別為之產(chǎn)生了一個特定的錯誤信息。盡管出現(xiàn)這類錯誤一般會返回一個請求有效的響應(yīng)(200 OK),但頁面仍然會跳轉(zhuǎn)到主頁面,或者采用某種隱藏信息的辦法,類似于“Internal Server Error”。
為了區(qū)分這兩種錯誤,我們看一個例子:有兩個電子商務(wù)的應(yīng)用程序,A和B,兩個應(yīng)用程序都使用同一個叫proddetails.asp的頁面,該頁面期待獲得一個參數(shù),叫ProdID。它獲取該參數(shù)后,從數(shù)據(jù)庫中提取相應(yīng)的產(chǎn)品詳細(xì)信息數(shù)據(jù),然后對返回的結(jié)果進(jìn)行一些處理。兩個應(yīng)用程序都是通過一個產(chǎn)品列表頁面上的鏈接調(diào)用proddetails.asp,因此能保證ProdID一直都是存在且有效的。應(yīng)用程序A認(rèn)為這樣就不會出現(xiàn)問題,因此對參數(shù)不做額外的檢查,而如果攻擊者篡改了ProdID,插入了一個在數(shù)據(jù)表中不存在的id,數(shù)據(jù)庫就會返回一個空記錄。由于應(yīng)用程序A沒有料到可能會出現(xiàn)空記錄,當(dāng)它試圖去處理該記錄中的數(shù)據(jù)時,就可能會出現(xiàn)異常,產(chǎn)生一個“500:Internal Server Error”。而應(yīng)用程序B,會在對記錄進(jìn)行處理前確認(rèn)記錄的大小超過0,如果是空記錄,則會出現(xiàn)一個錯誤提示“該產(chǎn)品不存在”,或者開發(fā)者為了隱藏該錯誤,會將頁面重新定位到產(chǎn)品的列表頁面。
因此攻擊者為了進(jìn)行SQL盲注,會首先嘗試提交一些無效的請求,并觀察應(yīng)用程序如何處理這些錯誤,以及如果出現(xiàn)SQL錯誤會發(fā)生什么情況。
2.2 定位錯誤
對要攻擊的應(yīng)用程序有了初步的認(rèn)識后,攻擊者會試圖定位由人為構(gòu)造的輸入而產(chǎn)生的錯誤信息。這時,攻擊者就會使用標(biāo)準(zhǔn)的SQL注入測試技術(shù),比如添加一些SQL關(guān)鍵字(如OR,AND等)和一些META字符(如;或’等)。每一個參數(shù)都被獨(dú)立地進(jìn)行測試,而獲得的響應(yīng)將被檢驗(yàn)用來判斷是否產(chǎn)生了錯誤。通過一個攔截代理服務(wù)器(intercepting proxy)或者類似的工具可以方便地識別頁面跳轉(zhuǎn)和其他一些可預(yù)測的隱藏錯誤,而任何一個返回錯誤的參數(shù)都有可能存在SQL注入漏洞。而在單獨(dú)測試每個參數(shù)過程中,必須保證其他參數(shù)都是有效的,因?yàn)樾枰苊獬⑷胍酝馊魏纹渌赡艿脑蛩鶎?dǎo)致的錯誤影響了判斷結(jié)果。測試的結(jié)果一般是一個可疑參數(shù)的列表,列表中的一些參數(shù)可能的確可以進(jìn)行注入利用,另外一些參數(shù)則可能是由一些SQL無關(guān)的錯誤所造成,因此需要被剔除。攻擊者接下來就需要從這些參數(shù)中挑選真正存在注入漏洞的參數(shù),我們稱之為確定注入點(diǎn)。
2.3 確定注入點(diǎn)
SQL字段可以被劃分為三個主要類型:數(shù)字、字符串和日期。雖然每個類型都有其特點(diǎn),但卻與注入的過程無關(guān)。每一個從網(wǎng)絡(luò)應(yīng)用程序提交給SQL查詢的參數(shù)都屬于以上三個類型中的一類,其中數(shù)字參數(shù)被直接提交給服務(wù)器,而字符串和日期則需要加上引號才被提交,例如:
SELECT * FROM Products WHERE ProdID = 4
與
SELECT * FROM Products WHERE ProdName = 'Book'
而SQL服務(wù)器,并不關(guān)心它接受到的是什么類型的參數(shù)表達(dá)式,只要該表達(dá)式是相關(guān)的類型即可。而這個特點(diǎn)則使攻擊者能夠很容易地確認(rèn)一個錯誤是否和SQL相關(guān)。如果是數(shù)字類型,最簡單的處理辦法是使用基本的算術(shù)操作,例如以下請求:
/mysite/proddetails.asp?ProdID=4
測試該參數(shù)的一種辦法是插入4’作為參數(shù),另一種是使用3+1作為參數(shù),假設(shè)這兩個參數(shù)已直接被提交給SQL請求語句,則將形成以下兩個SQL請求語句:
(1) SELECT * FROM Products WHERE ProdID = 4'
(2) SELECT * FROM Products WHERE ProdID = 3 + 1
第一個SQL語法有問題,將一定會產(chǎn)生一個錯誤,而第二個如果被順利地執(zhí)行,返回和最初的請求(即ProdID等于4)一樣的產(chǎn)品信息,這就提示該參數(shù)是存在注入漏洞的。
類似的技術(shù)可以被應(yīng)用于用一個符合SQL語法的字符串表達(dá)式替換該參數(shù),這里有兩個區(qū)別:第一,字符串表示式是放在引號中的,因此需要阻斷引號;第二,不同的SQL服務(wù)器連結(jié)字符串的語法不同,比如MS SQL Server使用符號+來連結(jié)字符串,而Oracle使用符號||來連結(jié)。例如以下請求:
/mysite/proddetails.asp?ProdName=Book
要測試該P(yáng)rodName參數(shù)是否有注入漏洞,可以先其替換成一個無效的字符串比如Book’,然后再替換成一個可能生成正確字符串的表達(dá)式,比如B’+’ook(對于Oracle,是B’||’ook)。這就會形成以下兩個SQL請求語句:
(1) SELECT * FROM Products WHERE ProdName = 'Book''
(2) SELECT * FROM Products WHERE ProdID = 'B' + 'ook'
則第一個仍然可能產(chǎn)生一個SQL錯誤,而第二個則可能返回和最初的請求一樣的值為Book的產(chǎn)品。
我們注意到,即使應(yīng)用程序已經(jīng)過濾了’和+等META字符,我們?nèi)匀豢梢栽谳斎霑r過把字符轉(zhuǎn)換成URL編碼(即字符ASCII碼的16進(jìn)制)來繞過檢查,例如:
/mysite/proddetails.asp?ProdID=3+1就等于/mysite/proddetails.asp?ProdID=3%2B1
/mysite/proddetails.asp?ProdID=B’+’ook就等于/mysite/proddetails.asp?ProdID=B%27%2B%27ook
類似的,任何表達(dá)式都可以用來替換最初的參數(shù)。而特殊的系統(tǒng)函數(shù)也可以被用來提交以返回一個數(shù)字,一個字符串或一個日期,比如Oracle中sysdate返回一個日期表達(dá)式,而在SQL Server中,getdate()會返回日期表達(dá)式。其他的技術(shù)同樣可以被用來判斷是否存在SQL注入漏洞。
通過以上介紹可以發(fā)現(xiàn),即使沒有詳細(xì)的錯誤信息,對于攻擊者來說,判斷是否存在SQL注入漏洞仍然是一個非常簡單的任務(wù)。
3 實(shí)施注入攻擊
攻擊者在確定注入點(diǎn)后,就要嘗試進(jìn)行注入利用,這需要其能確定符合SQL語法的注入請求表達(dá)式,判斷出后臺數(shù)據(jù)庫的類型,然后構(gòu)造出所需的利用代碼。
3.1 確定正確的注入句法
這是SQL盲注攻擊中最難也最有技巧的步驟,如果最初的SQL請求語句很簡單,那么確定正確的注入語法也相對容易,而如果最初的SQL請求語句較復(fù)雜,那么要想突破其限制就需要多次的嘗試,但進(jìn)行這些嘗試所需要的基本技術(shù)卻是非常簡單。
確定基本的句法的過程即通過標(biāo)準(zhǔn)的SELECT … WHERE語句,被注入的參數(shù)(即注入點(diǎn))就是WHERE語句的一部分。為了確定正確的注入句法,攻擊者必須能夠在最初的WHERE語句后添加其他數(shù)據(jù),使其能返回非預(yù)期的結(jié)果。對一些簡單的應(yīng)用程序,僅僅加上OR 1=1就可以完成,但在大多數(shù)情況下如果想構(gòu)造出成功的利用代碼,這樣做當(dāng)然是不夠的。經(jīng)常需要解決的問題是如何配對插入語符號(parenthesis,比如成對的括號),使之能與前面的已使用的符號,比如左括號匹配。另外常見的問題是一個被篡改的請求語句可能會導(dǎo)致應(yīng)用程序產(chǎn)生其他錯誤,這個錯誤往往難于和一個SQL錯誤相區(qū)分,比如應(yīng)用程序一次如果只能處理一個記錄,在請求語句后添加OR 1=1可能使數(shù)據(jù)庫返回1000條記錄,這時就會產(chǎn)生錯誤。由于WHERE語句本質(zhì)上是一串通過OR、AND或插入語符號連接起來的值為TRUE或FALSE的表達(dá)式,因此要想確定正確的注入句法,關(guān)鍵就在于能否成功地突破插入語符號限制并能順利地結(jié)束請求語句,這就需要進(jìn)行多次組合測試。例如,添加AND 1=2能將整個表達(dá)式的值變?yōu)镕ALSE,而添加OR 1=2則不會對整個表達(dá)式的值產(chǎn)生影響(除非操作符有優(yōu)先級)。
對于一些注入利用,僅僅改變WHERE語句就足夠了,但對于其他情況,比如UNION SELECT注入或存儲過程(stored procedures)注入,還需要能先順利地結(jié)束整個SQL請求語句,然后才能添加其他攻擊者所需要的SQL語句。在這種情況下,攻擊者可以選擇使用SQL注釋符號來結(jié)束語句,該符號是兩個連續(xù)的破折號(--),它要求SQL Server忽略其后同一行的所有輸入。例如,一個登錄頁面需要訪問者輸入用戶名和密碼,并將其提交給SQL請求語句:
SELECT Username, UserID, Password FROM Users WHERE Username = ‘user’ AND Password = ‘pass’
通過輸入john’--作為用戶名,將會構(gòu)造出以下WHERE語句:
WHERE Username = ‘john’ --'AND Password = ‘pass’
這時,該語句不但符合SQL語法,而且還使用戶跳過了密碼認(rèn)證。但是如果是另外一種WHERE語句:
WHERE (Username = ‘user’ AND Password = ‘pass’)
注意到這里出現(xiàn)了插入語符號,這時再使用john’--作為用戶名,請求語句就會錯誤:
WHERE (Username = ‘john' --' AND Password = ‘pass’)
這是因?yàn)橛形磁鋵Φ牟迦胝Z符號,請求語句就不會被執(zhí)行。
這個例子顯示出使用注釋符號能夠用來判斷請求語句是否被順利地結(jié)束了,如果添加了注釋符號且沒有產(chǎn)生錯誤,這就意味著注釋符號前的語句已經(jīng)順利地被結(jié)束。如果出現(xiàn)了錯誤,這就需要攻擊者進(jìn)行更多的請求嘗試。
3.2 判斷數(shù)據(jù)庫類型
攻擊者一旦確定了正確的注入句法后,就會開始利用注入去判斷后臺數(shù)據(jù)庫的類型,這個步驟比確定注入句法要簡單得多。攻擊者一般會使用以下幾種技巧,這些技巧是基于不同類型數(shù)據(jù)庫引擎在具體實(shí)現(xiàn)上的差異。下面只介紹如何區(qū)分Oracle和MS SQL Server:
最簡單的辦法,就是前面提到的利用字符串的連結(jié)符號,在注入句法已經(jīng)確定的情況下,攻擊者可以對WHERE語句自由地添加額外的表達(dá)式,那么就可以利用字符串的比較來區(qū)分?jǐn)?shù)據(jù)庫,例如:
AND 'xxx' = 'x' + 'xx' (或者 AND %27xxx%27+%3D+%27x%27+%2B+%27xx%27)
通過將+替換成||,就可以判斷出是數(shù)據(jù)庫是Oracle還是MS SQL Server,或者是其他類型。
其他的辦法是利用分號字符(即;),在SQL中,分號是用來將幾個SQL語句連接在同一行中。在注入時,也可以在注入代碼中使用分號,但Oracle驅(qū)動程序卻不允許這樣使用分號。假設(shè)在前面使用注釋符號時沒有出現(xiàn)錯誤,那么在注釋符號前加上分號對MS SQL Server是沒有影響的,但如果是Oracle就會產(chǎn)生錯誤。另外,還可以使用COMMIT語句來確認(rèn)是否允許在分號后再執(zhí)行其他語句(例如,注入語句xxx' ; COMMIT --),如果沒有出現(xiàn)錯誤就可以認(rèn)為允許多句執(zhí)行。
最后,表達(dá)式還可以被替換成能返回正確值的系統(tǒng)函數(shù),由于不同類型的數(shù)據(jù)庫使用的系統(tǒng)函數(shù)也是不同的,因此也可以通過使用系統(tǒng)函數(shù)來確定數(shù)據(jù)庫類型,比如2.3節(jié)提到的MS SQL Server的日期函數(shù)getdate()與Oracle的sysdate.
3.3 構(gòu)造注入利用代碼
當(dāng)所有相關(guān)的信息都已獲得后,攻擊者就可以開始進(jìn)行注入利用,而且在構(gòu)造注入利用代碼過程中也不再需要詳細(xì)的錯誤信息,構(gòu)造利用代碼本身可以參考其他描述標(biāo)準(zhǔn)SQL注入攻擊的文檔。
由于對于普通的SQL注入利用,已經(jīng)有很多其他論文進(jìn)行了詳細(xì)的討論,故本文只會在下一節(jié)介紹一種UNION SELECT注入。
4 UNION SELECT注入
盡管通過篡改SELECT…WHERE語句來注入對于很多應(yīng)用程序非常有效,但在盲注情況下,攻擊者仍然愿意使用UNION SELECT語句,這是因?yàn)榕cWHERE語句所進(jìn)行的操作不同,使用UNION SELECT可以讓攻擊者在沒有錯誤信息的情況下依然能訪問數(shù)據(jù)庫中所有表。
進(jìn)行UNION SELECT注入需要預(yù)先獲知數(shù)據(jù)庫的表中的字段個數(shù)和類型,而這些信息一般被認(rèn)為在沒有詳細(xì)錯誤信息的提示下是不可能獲得的,但本文下面就將給出解決該問題的方法。
另外需要注意的是,進(jìn)行UNION SELECT的前提是攻擊者已經(jīng)確定了正確的注入句法,本文的前面一節(jié)已經(jīng)闡明了這在盲注條件下是可以實(shí)現(xiàn)的,而且在使用UNION SELECT語句之前,SQL語句中所有的插入語符號都應(yīng)該已經(jīng)完成配對,從而可以自由地使用UNION或者其它指令進(jìn)行注入。UNION SELECT還要求當(dāng)前語句和最初的語句查詢的信息必須具有相同的數(shù)和相同的數(shù)據(jù)類型,不然就會出錯。
4.1 統(tǒng)計(jì)列數(shù)
當(dāng)錯誤信息沒有被屏蔽時,要獲取列數(shù)只需要在進(jìn)行UNION SELECT注入時每次嘗試使用不同的字段數(shù)即可,當(dāng)錯誤信息由“列數(shù)不匹配”變成“列的類型不匹配”時,當(dāng)前嘗試的列數(shù)就是正確的。但在盲注條件下,由于我們對無法獲悉錯誤信息究竟是哪個,所以該方法也就失去了作用。
新的辦法是利用ORDER BY語句,在SELECT語句最后加上ORDER BY能夠改變返回的記錄集的次序,一般是按一個指定的列名的值進(jìn)行排序。例如,當(dāng)通過產(chǎn)品號查詢產(chǎn)品時,一個有效的注入語句如下:
SELECT ProdNum FROM Products WHERE (ProdID=1234) ORDER BY ProdNum --
AND ProdName=’Computer’) AND UserName=’john’
人們往往會忽略的是ORDER BY語句后還可以使用數(shù)字指代列名,在上例中如果ProdNum是查詢請求返回的記錄中的第一列,則注入1234) ORDER BY 1--返回的結(jié)果是一樣的。由于上例查詢請求只返回一個字段,注入1234) ORDER BY 2 --就會出錯,即返回的記錄無法按指定的第二個字段排序。這樣,ORDER BY就可以被利用來對列數(shù)進(jìn)行統(tǒng)計(jì)了。由于每個SELECT語句都至少返回一個字段,故攻擊者可以先在注入句法中添加ORDER BY 1來確定語句是否能被正確執(zhí)行,有時對字段的排序也可能會產(chǎn)生錯誤,這時添加關(guān)鍵字ASC或DESC可以解決該問題。一旦確定ORDER BY句法是有效的,攻擊者就會對排序列號從列1到列100進(jìn)行遍歷(或者到列1000,直到列號被確定為無效),理論上當(dāng)出現(xiàn)第一個錯誤時,前一個列號就是要統(tǒng)計(jì)的列數(shù),但在實(shí)際情況中,有些字段可能不允許排序,那么在出現(xiàn)第一次錯誤時可以再多嘗試一到兩個數(shù)字,以確認(rèn)列號已遍歷完。
4.2 判斷列的數(shù)據(jù)類型
在統(tǒng)計(jì)完列數(shù)后,攻擊者需要再判斷列的數(shù)據(jù)類型,在盲注情況下判斷類型也是有技巧的,由于UNION SELECT要求前后查詢語句查詢的字段類型相同,故如果字段數(shù)有限,可以簡單地利用UNION SELECT語句對字段類型進(jìn)行暴力窮舉(brute force),但如果字段數(shù)較多,判斷就會出現(xiàn)問題。根據(jù)前文,字段的類型只有數(shù)字、字符串和日期三種可能的類型,一旦字段數(shù)有10個,那么就意味著有310(約60,000)種可能的組合,假設(shè)每一秒可以自動進(jìn)行20次嘗試,窮舉一遍也需要近一個小時,如果字段數(shù)更多,那么測試所需時間就會令人難以忍受。
一種簡單的辦法是利用SQL的關(guān)鍵字NULL,與靜態(tài)字段的注入需要區(qū)分是數(shù)字類型還是字符類型不同,NULL可以匹配任何一種數(shù)據(jù)類型。因此可以注入一個所有查詢字段都為NULL的UNION SELECT語句,那么就不會出現(xiàn)任何類型不匹配的錯誤。讓我們再舉一個與前面類似的例子:
SELECT ProdNum,ProdType,ProdPrice,ProdProvider FROM Products
WHERE (ProdID=1234 AND ProdName=’ Computer’) AND UserName=’john’
假設(shè)攻擊者已經(jīng)獲得了列數(shù)(在該例中為4),那么就可以很簡單地構(gòu)造一個UNION SELECT語句,其中所有查詢字段都為NULL,還需要構(gòu)造一個不會產(chǎn)生權(quán)限問題的FROM語句。對于MS SQL Server,即使忽略FROM語句也不會出錯,但對于Oracle,則可以使用一個名叫dual的表。最后,還需要一個值一定為FALSE的WHERE語句(比如WHERE 1=2),這是為了確保查詢不會返回只包含null值的記錄集,以杜絕產(chǎn)生其他可能的錯誤。那么針對MS SQL Server的注入語句如下:
SELECT ProdNum,ProdType,ProdPrice,ProdProvider FROM Products
WHERE (ProdID=1234) UNION SELECT NULL,NULL,NULL,NULL
WHERE 1=2 -- AND ProdName=’ Computer’) AND UserName=’john’
這個NULL注入語句有兩個目的,主要目的是構(gòu)造一個不會產(chǎn)生任何錯誤的UNION SELECT語句以測試UNION語句是否可以被執(zhí)行,另一個目的是為了對數(shù)據(jù)庫類型的判斷進(jìn)行100%確認(rèn)(可以通過在FROM語句里添加一個數(shù)據(jù)庫開發(fā)商預(yù)置的表名進(jìn)行測試)。
如果NULL注入語句被順利執(zhí)行,那么就可以快速地對每個列的類型進(jìn)行判斷。在每一輪嘗試中,只對一個字段類型進(jìn)行測試,由于類型只有三類,所以每個字段最多被測試三次就會有結(jié)果,這樣嘗試的次數(shù)最多是列數(shù)的三倍,而不是以3為底數(shù)以列數(shù)為指數(shù)的次數(shù)。假設(shè)ProdNum屬于數(shù)字類型,其它三個字段都屬于字符串類型,那么以下順序的注入語句就可以判斷出正確的類型:
1234) UNION SELECT NULL,NULL,NULL,NULL WHERE 1=2 --
無錯 句法正確,使用的是MS SQL Server數(shù)據(jù)庫
1234) UNION SELECT 1,NULL,NULL,NULL WHERE 1=2 --
無錯 第一個字段是數(shù)字類型
1234) UNION SELECT 1,2,NULL,NULL WHERE 1=2 --
出錯 第二個字段不是數(shù)字類型
1234) UNION SELECT 1,’2’,NULL,NULL WHERE 1=2 --
無錯 第二個字段是字符串類型
1234) UNION SELECT 1,’2’,3,NULL WHERE 1=2 --
出錯 第三個字段不是數(shù)字類型
1234) UNION SELECT 1,’2’,’3’,NULL WHERE 1=2 --
無錯 第三個字段是字符串類型
1234) UNION SELECT 1,’2’,’3’,4 WHERE 1=2 --
出錯 第四個字段不是數(shù)字類型
1234) UNION SELECT 1,’2’,’3’,’4’ WHERE 1=2 --
無錯 第四個字段是字符串類型
攻擊者現(xiàn)在就已經(jīng)獲得了每一列的數(shù)據(jù)類型,盲注還可以被應(yīng)用于從數(shù)據(jù)庫的表中獲取數(shù)據(jù),比如獲得數(shù)據(jù)表的列表以及它們各自的列名,還可以從應(yīng)用程序中獲得數(shù)據(jù),而這些技術(shù)在其他一些關(guān)于SQL注入的論文中已經(jīng)有討論,故本文不再繼續(xù)介紹。