1014.WebGoat SQL盲註 解題思路
WebGoat SQL盲註 解題思路
★ 題目:SQL Injection (advanced)
地址: http://127.0.0.1:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/4
題目要求最終以Tom的身份登錄到系統中。
LOGIN界面:
REGISTER界面:
★ 什麽是SQL盲註?
這是我自己的理解,不一定準確,僅供參考。
SQL盲註的意思是,註入數據到SQL語句中,服務器不會返回數據庫裏的詳細信息,只會給出 true或false的信息,或者給出延時的信息(註入的sleep(10)起作用了)。
所以,我們只能根據有限的信息去獲取數據庫中更多地信息,這種方式像盲人摸象一樣,只能一點一點的去收集數據庫的信息(每一次的true表示獲取一個有效信息),來慢慢形成對整個數據庫信息的理解(表名是什麽,列名是什麽),最終達到獲取數據庫中數據的目的(獲取某個表的某個值)。
權威的對SQL盲註的解釋,可以參考:
WebGoat中對 SQL盲註的說明:
Blind SQL Injection: http://127.0.0.1:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/3
OWASP對Blind SQL Injection的說明: https://www.owasp.org/index.php/Blind_SQL_Injection
★ SQL盲註的思路
以我自己的理解,有兩種思路。一種思路是,先獲取數據庫中表的名字,然後獲取表中每一列的列名,然後獲取表中的數據。另一種思路是,直接猜表中的列名,然後獲取表中的數據。第一種思路是穩妥的,復雜的,需要很多次SQL查詢。第二種有些碰運氣了,運氣好可以很快猜中列名,運氣不好(例如列名中帶有隨機值password_1ey2d),那猜中的概率是極低的。
對於WebGoat這道題,有人是通過直接猜列名來解題的,鏈接在這裏,該文作者直接試列名’password’,發現沒有報錯,說明列名沒有錯誤,然後通過暴力破解的方式解決此題。
下面開始解題。
★ 判斷註入的點在哪裏
有2個界面:LOGIN和REGISTER。
LOGIN界面中有個可以輸入的值:Username和Password。
REGISTER界面有4個可以輸入的值: Username、Email、Password和Confirm Password。
分別對每一個輸入值進行嘗試。
當然,用 sqlmap 進行探測SQL註入的位置更高效一些。
? 對LOGIN界面的嘗試
Username輸入 hello‘ or 1=1 --,Password輸入任意值。無效。
同理,對Password的幾次嘗試也都無效。
通過Burpsuite的Proxy可以知道,采用sleep(10)會報錯,因為HSQLDB中不支持這個函數。
? 對REGISTER界面的嘗試
先註冊一個新用戶,用戶信息如下:
Username輸入 hello。
Email地址隨意,例如[email protected]
Password和Confirm Password都輸入 1。
點擊『Register Now』之後,界面提示『User hello created, please proceed to the login page.』。說明註冊成功。
然後,分別嘗試用戶名:hello‘ or 1=1 --,hello‘ or 1=2 --,hello‘ and 1=1 -- 和 hello‘ and 1=2 --
其他信息:Email地址隨意,例如[email protected]。Password和Confirm Password都輸入 1。
結果如下:
用戶名 結果
hello‘ or 1=1 -- User hello’ or 1=1 – already exists please try to register with a different username.
這說明存在SQL註入,因為如果不存在SQL註入,用戶名hello‘ or 1=1 --是不存在的,不應該提示『已存在』。說明hello‘ or 1=1 --SQL語句被解析了。變成了一定要查詢數據(where true),即使查詢出來的不是hello用戶。
hello‘ or 1=2 -- User hello’ or 1=2 – already exists please try to register with a different username.
說明存在SQL註入,否則用戶名為hello‘ or 1=2 --的用戶是不存在的。這也說明此時查詢的就是剛剛註冊的hello用戶。
hello‘ and 1=1 -- User hello’ and 1=1 – already exists please try to register with a different username.
說明存在SQL註入,否則用戶名為hello‘ and 1=1 --的用戶是不存在的。這也說明此時查詢的就是剛剛註冊的hello用戶。
hello‘ and 1=2 -- User hello’ and 1=2 – created, please proceed to the login page.
如果單從這一條來說,是不能確定是否存在SQL註入的,因為有兩種可能性。
可能1: 用戶名為hello‘ and 1=2 --的用戶確實不存在,所以可以註冊。
可能2:and 1=2起作用了,它的結果是false,對於select * from xxx_table where false來說,是始終不會查詢到數據的。所以可以註冊。
通過對四種情況的分析,可以得出結論: REGISTER界面的Username存在SQL註入。由於只存在兩種返回結果,沒有其他多余的信息,所以是SQL盲註。
我們能利用的就是這兩種返回信息,來做布爾判斷(true/false)。
信息1:User xxx already exists please try to register with a different username.
信息2:User xxx created, please proceed to the login page.
? 用 sqlmap 確認SQL註入的位置
使用 sqlmap 之前,需要確認幾個數據:
--cookie;cookie信息是什麽?
-u: url是什麽
--data:HTTP請求的body是什麽?
通過Burpsuite的Proxy工具可以截獲http請求,例如,拿到的http數據可能是這樣的:
PUT /WebGoat/SqlInjection/challenge HTTP/1.1
Host: 127.0.0.1:8080
Content-Length: 78
Accept: */*
Origin: http://127.0.0.1:8080
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://127.0.0.1:8080/WebGoat/start.mvc
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: JSESSIONID=CD6CF7EC1538E41A8A915D03167F5A72
Connection: close
username_reg=tom&email_reg=aaa%40bbb.ccc&password_reg=1&confirm_password_reg=1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
cookie的信息:Cookie: JSESSIONID=CD6CF7EC1538E41A8A915D03167F5A72
url:http://127.0.0.1:8080/WebGoat/SqlInjection/challenge
body信息:username_reg=tom&email_reg=aaa%40bbb.ccc&password_reg=1&confirm_password_reg=1
有了這3個信息,構造sqlmap的命令參數,如下:
C:\Python27>python.exe H:\git\sqlmap-dev\sqlmap.py --cookie "JSESSIONID=CD6CF7EC1538E41A8A915D03167F5A72" -u http://127.0.0.1:8080/WebGoat/SqlInjection/challenge --data "username_reg=tom1&email_reg=aaa%40bbb.ccc&password_reg=1&confirm_password_reg=1"
1
執行sqlmap,第一次執行sqlmap得到的結果很多,再執行一次,得到的關鍵信息如下:
C:\Python27>python.exe H:\git\sqlmap-dev\sqlmap.py --cookie "JSESSIONID=CD6CF7EC1538E41A8A915D03167F5A72" -u http://127.0.0.1:8080/WebGoat/SqlInjection/challeng
e --data "username_reg=tom1&email_reg=aaa%40bbb.ccc&password_reg=1&confirm_password_reg=1" --method "PUT"
___
__H__
___ ___[‘]_____ ___ ___ {1.2.9.17#dev}
|_ -| . [‘] | .‘| . |
|___|_ [‘]_|_|_|__,| _|
|_|V |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user‘s responsibility to obey all applicable
local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting at 17:22:59
[17:23:00] [INFO] resuming back-end DBMS ‘hsqldb‘
[17:23:00] [INFO] testing connection to the target URL
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: username_reg (PUT) (註:SQL註入的位置,是username_reg,註冊時的用戶名)
Type: boolean-based blind (註:存在基於布爾值的盲註)
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: username_reg=tom1‘ AND 263password88=2688 AND ‘qNhV‘=‘qNhV&[email protected]&password_reg=1&confirm_password_reg=1
Type: stacked queries (註:存在堆疊查詢的問題)
Title: HSQLDB >= 1.7.2 stacked queries (heavy query - comment)
Payload: username_reg=tom1‘;CALL REGEXP_SUBSTRING(REPEAT(RIGHT(CHAR(9762),0),500000000),NULL)--&[email protected]&password_reg=1&confirm_password_reg=1
Type: AND/OR time-based blind (註:存在基於時間的盲註)
Title: HSQLDB > 2.0 AND time-based blind (heavy query)
Payload: username_reg=tom1‘ AND CHAR(121)||CHAR(117)||CHAR(79)||CHAR(68)=REGEXP_SUBSTRING(REPEAT(LEFT(CRYPT_KEY(CHAR(65)||CHAR(69)||CHAR(83),NULL),0),500000
000),NULL) AND ‘hRZB‘=‘hRZB&[email protected]&password_reg=1&confirm_password_reg=1
---
[17:23:00] [INFO] the back-end DBMS is HSQLDB
back-end DBMS: HSQLDB >= 1.7.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
由 sqlmap 工具可以很快得到SQL註入的位置,是username_reg,即註冊時的用戶名。
? 確認Tom在數據庫中的名字
還有一個信息需要確認,即Tom在數據庫中的名字是什麽?
註冊一個名為『Tom』、『tom』或者『TOM』的人試試,即可。試的結果如下:
註冊名為『Tom』的人,顯示 『User Tom created, please proceed to the login page.』,而註冊名為『tom』的人,顯示『User tom already exists please try to register with a different username.』。所以,Tom在數據庫中的名字為『tom』。
★ 如何以Tom的身份登錄?
有2種可能性:
可能1:篡改Tom的密碼
需要做的是,找到Tom所在表名,再找到Tom的密碼的列名,然後通過堆疊查詢(stacked queries)來修改Tom的密碼。註入的數據是這樣的:hello‘; update XXX_TABLE set PASSWORD_COLUMN = ‘123456‘; --,然後以用戶名tom和密碼123456登錄即可。
這種情況通常是這樣處理的:
(1) 遍歷表名:select table_name from information_schema.tables where id=1,id要遍歷所有可能的值,例如id取值1到20,嘗試20個表。或者,采用limit offset, count來分別獲取每一個表的名字。
(2) 獲取某個表名的每一位字符是什麽:substring((select table_name from information_schema.tables where id=1), 1,1)=‘a‘,判斷表名中第一個字符是不是字符a,是不是b,是不是c,以此類推。然後接著判斷表名的第二個字符是不是a,是不是b,等等。最終得到表名的每一位。
(3) 然後獲取表中的列名:select column_name from information_schema.columns where table_name=‘剛剛獲取的表名‘ limit 0,1。每次只獲取一個列名,獲取下一個列名:limit 1,1,limit 2,1,以此類推。
(4) 獲取列名的每一位字符是什麽:substring((select column_name from information_schema.columns where table_name=‘剛剛獲取的表名‘ limit 0,1),1,1)=‘a‘,判斷列名第一個字符是不是a,然後判斷方法跟判斷表名是一樣的。純粹的暴力破解。
(5) 得到表名和列名之後,就可以篡改tom的密碼了。
可惜的是,對於此題,我沒有獲取到表名,我把獲取不到表名的問題提交到overflow上,目前沒人解答。所以,目前此路不通。
需要註意的是:substring獲取字符時,從下標1開始,即substring(xxx,1,1), substring(xxx,2,1)
可能2:暴力破解Tom的密碼
需要做的是,先猜出Tom的密碼的列名,然後通過暴力破解的方式獲取tom的密碼。此種方式可以不用獲取表名。我采用的是這種方式。
★ 猜tom密碼在表中的列名
這個過程全看運氣,假設我們運氣好,先猜password。
猜列名,可以使用:tom‘ or password=‘12345,結果沒有報錯,說明列名password是正確的(註意:不是說password的值是12345)。
如果使用tom‘ or password2=‘12345,則瀏覽器貌似沒有反應,通過Burpsuite的Proxy中的HTTP history,可以看到『java.sql.SQLSyntaxErrorException』的異常,原因是『user lacks privilege or object not found: PASSWORD2』,說明不存在 password2 這列。
猜中列名為password之後,就要進一步確認tom的密碼了,這之後就不是瞎猜了,而是暴力破解。
★ 選取註入的數據
再次說明,我們能利用的就是這兩種返回信息,來做布爾判斷(true/false)。
信息1:User xxx already exists please try to register with a different username.
信息2:User xxx created, please proceed to the login page.
可以采用的註入數據:tom‘ and true -- 和tom‘ and false --。
對於tom‘ and true --,一定會返回『User xxx already exists please try to register with a different username.』。
對於tom‘ and false --,相當於where false,所以一定查詢不到,一定會返回『User xxx created, please proceed to the login page』。
我們將true/false替換為合法的SQL語句:
substring(password,1,1)=‘a‘,這是判斷password的第一位是不是字符a,其結果是true/false。
我們也可以不用 tom‘ and true -- 這裏面的註釋--,完整的註入數據為:
tom‘ and substring(password,1,1)=‘a
如果返回的結果是『User xxx already exists』,那麽說明substring(password,m,1)=‘x表達式為true,即,password的第m位是x(x表示某個字符),以此來判斷password的每一位。
通過改變substring的第2個參數(password的下標)和後面的字符a(可以是a到z,A到Z,等等),遍歷password的每一位,從而獲取整個password的值。這個過程可以用Burpsuite的Intruder工具來完成。
★ 用Burpsuite的Intruder暴力破解密碼
設置如圖:
其中payload1是password的下標,取值從1到20(我第一次嘗試是20,不過沒有獲取密碼的所有位,之後又取了21到25,為的是盡可能的減少HTPP請求的數量。)。
payload2是可能的字符,取值:a到z,A到Z,0到9,等等。對於此題來說,取值a到z就夠了(偷看了webgoat的源代碼)。
payload1取值範圍1到20,payload2取值範圍a到z:26種可能。一共是520種可能(20*26=520)。
之後又將payload1的取值範圍設置為21到25,又增加了130種可能(5*26=130)。
找到所有結果是『User xxx already exists』的response,其實通過對response的大小進行排序就可以了。
Password前20位的內容如下:(如果把comments列加上註釋,就可以只篩出有效的http請求,從而對payload1排序就可以更清楚找到密碼。)
thisisasecretfortomo
Password的後3位為:nly
註:為了減少HTTP請求,payload2的取值範圍改為21到24了。
tom的完整的密碼是:thisisasecretfortomonly,然後就可以以tom的身份登錄了。
Congratulations!
1014.WebGoat SQL盲註 解題思路