关于报错注入的一些深入理解
关于报错注入的一些深入理解
报错注入简介
关于报错注入一直是盲点。所以今天来简单总结一下报错注入的知识点。
报错注入分为很多种
- 主键重复报错
- xpath语法错误报错
- 数据溢出报错
- 其他报错
一般xpath语法错误报错会比较多。
先来几个payload
select updatexml(1,concat(0x7e,(select version()),0x7e),1);
select extractvalue(1,concat(0x7e,(select version()),0x7e));
select exp(~(select*from(select user())x));
select (select(!x-~0)from(select(select user())x)a);
select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2));
xpath语法错误
关于xpath语法
报错注入中最常见的应该就是xpath语法错误
常用函数extractvalue
和updatexml
。这两个函数是xml查询和修改的函数。
extractvalue
负责在xml文档中按照xpath
语法查询节点内容,updatexml
则负责修改查询到的内容
extractvalue和updatexml
这两个大同小异,只不过参数extractvalue是两个参数,updatexml是三个参数
mysql中文文档https://www.docs4dev.com/docs/zh/mysql/5.7/reference/xml-functions.html
举个例子,这里先赋值,extractvalue
第一个参数是xml格式的数据,第二个必须xpath
语法的字符串。
这里的xpath
字符串的意思是,@i是取值,在这里取为1
其实就是等于b[1]
取第一个b标签内的字符也就是X。
如果第二个标签不符合xpath
语法,那么就会报错,且将查询结果放在报错信息内。
且报错注入只会显示特殊字符前后的字符串,所以前后要用两个0x7e包起来。
如果报错注入中显示不全,那么可以用left()
函数和right()
函数分别查询。然后把查到的结果拼起来就好
EXP
select updatexml(1,concat(0x7e,(select version()),0x7e),1);
select extractvalue(1,concat(0x7e,(select version()),0x7e));
数据溢出报错
在这里可以看mysql是怎么处理整形数据的
在mysql5.5之前,整形溢出是不会报错的,只有版本号大于5.5.5时,才会报错。
其实查询的时候可以按位取反,可以直接查到大数。
如果一个查询成功返回,且返回值位0,进行逻辑非运算后可得1。
关于exp函数的解释
而在mysql>5.5.53时,则不能返回查询结果
换了个版本,可以了。
EXP
select exp(~(select*from(select user())x));
select (select(!x-~0)from(select(select user())x)a);
主键重复
这里用到了count()
和group by
在遇到rand()
时产生的重复值时的报错的思路。网上比较
先看看函数的作用
1. concat: 连接字符串功能
2. floor(): 取float的整数值(向下取整)
3. rand(): 取0~1之间的随机浮点值
4. group by: 根据一个或多个列对结果集进行分组并有排序功能
5. floor(rand(0)*2): 随机产生0或1
虽然rand()
和rand(0)
都是产生随机数,但是rand()
产生的随机数每次都不一样,但是rand(0)
产生的随机数每次都一样。
进一步的,rand()
产生的是随机的01序列,而rand(0)
产生的是固定的01序列
接下来再看count()
和group by
函数。关于这两个函数的基础用法:
那么这个过程是怎么实现的呢?可在最初,username,count(*)这个表是空的,通过一行一行的读取原数据表中的username
字段,如果得到的数据在空表中不存在,就将他插入,并且将对应的count(*)
赋值为1,如果存在,就将count(*)+1
,直到扫完整个数据表。
那么来看看如何造成报错注入:
在查询的时候如果多次使用rand()
的话,该值会被计算多次。
我们来看这一条
mysql> select floor(rand()*2) from user;
+-----------------+
| floor(rand()*2) |
+-----------------+
| 1 |
| 1 |
| 0 |
| 1 |
| 0 |
| 0 |
+-----------------+
6 rows in set (0.00 sec)
刚开始floor(rand()*2)
是一个空表,在使用group by
的时候,floor(rand(0)*2)
会被计算一次。在插入第一条数据(1)
时,如果虚表没有记录1,那么rand()
又会被计算一次。所以在查询时候顺序为定值011011。报错注入其实就是floor(rand(0)*2)
被计算多次导致的
现在来看看报错注入的过程:
以这条语句为例
这里的错误主要是填虚表时造成的错误。
取第一条记录,第一次查询floor(rand(0)*2)
结果为0,查询虚拟表,此时虚拟表为空,发现0键值不存在,则floor(rand(0)*2)
会被再计算一次,结果为1,插入虚表,第一条记录查询完毕
取第二条记录,计算floor(rand(0)*2)
发现结果为1(第三次计算),查询虚表,发现1的键值存在,所以不会计算第二次,直接在键值为1的count(*)那里加1,第二条结果查询完毕。
取第三条数据,计算floor(rand(0)*2)
发现结果为0(第四次计算),查询虚表,发现没有键值0,则尝试插入一条新的数据,那么插入时floor(rand(0)*2)
会被再次计算(第五次计算)新值为1,然而这个主键已经存在虚拟表中,但是主键的值是唯一的,所以就报错了。
整个查询过程floor(rand(0)*2)
被计算了5次,查询原数据表三次,这就是为什么数据表中为什么至少要三条数据,才可以报错的原因。
如果有序列开头为0,1,0或者1,0,1那么永远都不会报错,因为两个主键分别为0,1那么后门就直接count(*)+1
了。
floor报错通用payload
select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2));
参考文章 https://xz.aliyun.com/t/253/
https://www.v0n.top/2019/08/12/SQL%E6%8A%A5%E9%94%99%E6%B3%A8%E5%85%A5/