关于报错注入的一些深入理解

·
SQL注入 no tag March 5, 2021

关于报错注入的一些深入理解

报错注入简介

关于报错注入一直是盲点。所以今天来简单总结一下报错注入的知识点。

报错注入分为很多种

  • 主键重复报错
  • 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则负责修改查询到的内容

image.png

extractvalue和updatexml

这两个大同小异,只不过参数extractvalue是两个参数,updatexml是三个参数

mysql中文文档https://www.docs4dev.com/docs/zh/mysql/5.7/reference/xml-functions.html

image.png

举个例子,这里先赋值,extractvalue第一个参数是xml格式的数据,第二个必须xpath语法的字符串。

这里的xpath字符串的意思是,@i是取值,在这里取为1 其实就是等于b[1]

取第一个b标签内的字符也就是X。

如果第二个标签不符合xpath语法,那么就会报错,且将查询结果放在报错信息内。

且报错注入只会显示特殊字符前后的字符串,所以前后要用两个0x7e包起来。

image.png

如果报错注入中显示不全,那么可以用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时,才会报错。

image.png

其实查询的时候可以按位取反,可以直接查到大数。

如果一个查询成功返回,且返回值位0,进行逻辑非运算后可得1。

image.png

关于exp函数的解释

而在mysql>5.5.53时,则不能返回查询结果

image.png

image.png

换了个版本,可以了。

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

image.png

image.png

虽然rand()和rand(0)都是产生随机数,但是rand()产生的随机数每次都不一样,但是rand(0)产生的随机数每次都一样。

image.png

image.png

进一步的,rand()产生的是随机的01序列,而rand(0)产生的是固定的01序列

接下来再看count()和group by函数。关于这两个函数的基础用法:

image.png

那么这个过程是怎么实现的呢?可在最初,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)被计算多次导致的

现在来看看报错注入的过程:

以这条语句为例

image.png

这里的错误主要是填虚表时造成的错误。

取第一条记录,第一次查询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));

image.png

参考文章 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/

  • Thinkphp-vuln RCE
  • D^3CTF 2021
取消回复

说点什么?
Title
extractvalue和updatexml

© 2023 Yang_99的小窝. Using Typecho & Moricolor.