MENU

SQL 注入 报错注入

May 5, 2019 • Read: 2304 • SQL,Web阅读设置

在测试注入无法回显数据时,报错注入是一种很好的选择。

- 0x00 原理

构造 payload 让信息通过错误提示回显出来。

- 0x01 应用场景

  1. 查询不回显内容,会打印错误信息。
  2. update 、insert 等语句,会打印错误信息。
  • Sqli-labs Less-5 存在报错注入的代码:
//including the Mysql connect parameters.
include("../sql-connections/sql-connect.php");
error_reporting(0);
// take the variables
if(isset($_GET['id']))
{
$id=$_GET['id'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'ID:'.$id."\n");
fclose($fp);

// connectivity 


$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);

    if($row)
    {
      echo '<font size="5" color="#FFFF00">';    
      echo 'You are in...........';
      echo "<br>";
        echo "</font>";
      }
    else 
    {
    
    echo '<font size="3" color="#FFFF00">';
    print_r(mysql_error());
    echo "</br></font>";    
    echo '<font color= "#0000ff" font size= 3>';    
    
    }
}
    else { echo "Please input the ID as parameter with numeric value";}

有点复杂哈,那么浓缩下来看,就是存在以下代码:

    if($row)
    {
      echo 'You are in...........';
      }
    else 
    {
    print_r(mysql_error());
    }
然后利用产生错误信息的语句(函数),在其中插入我们想要执行的语句之后,从而实现报错注入。

- 0x02 利用

  • 如何利用报错注入
  1. 让错误信息中返回数据库中的内容。
  2. 构造语句,让错误信息中可以显示数据库内容。
  • 报错注入方法小归纳
凡是可以让错误信息显示的语句(函数),都能实现报错注入。
函数名称利用
floor()select count(*) from information _schema.tables group by concat((此处加入执行语句),0x7e,floor(rand(0)*2));
extractvalue()extractvalue(1,concat(0x7e,(此处加入执行语句),0x7e));
updatexml()select updatexml(1,concat(0x7e,(此处加入执行语句),0x7e),1);
Tips: 以 0x 开始的数据表示16进制,0x7e 表示为 ~ 符号。

ASCII 码对照表

- 0x03 实践

  • floor() 报错原理

研究了这个两天,参考了一些博客,发现一些大佬表达能力比我强太多,脑壳疼...
利用 floor() 函数使 SQL 语句报错,实际上是由 rand() , count() , group by 三个函数语句联合使用造成的。
首先,看一下语句中使用到的函数和子句:

  1. concat: 连接字符串功能
  2. floor: 取float的整数值(向下取整)
  3. rand: 取0~1之间的随机浮点值
  4. group by: 根据一个或多个列对结果集进行分组并有排序功能
  5. floor(rand(0)*2): 随机产生0或1

刚才已经说了 利用 floor() 的报错注入实际上是由 rand() , count() , group by 三个函数语句联合使用造成的,想要搞清楚原理我们首先来分析单个,
从 group by 子句开始,
这里我建了一个 person 表测试:
2.png
我们通过 page 年龄这个字段来对表中数据分组:
3.png
可以看到我们出现了一个新的数据表,有 page 、 count(*) 这两个字段,count(*) 字段下表示每个年龄人的数量,
可以联想到 group by 子句的执行流程,最初时,page-count(*)这个数据表是空的,通过一行一行读原数据表中的 page 字段,如果读到的 page 在 page-count(*) 数据表中不存在,就将它插入,并且将对应的 count(*) 赋为1,如果存在,就将其对应的 count(*) +1,直至扫完整个数据表。

Tips:这里有一点需要注意, group by 后跟的字段名是作为虚拟表的主键,主键不能重复,这也是报错的关键点。

报错的主要原因是虚拟表的主键重复,那我们来看一下是哪里,在什么情景下重复了。
这时候用到 rand() 函数了,继续来了解 rand() 函数的用法:

  1. 生成0~1之间的随机浮点值
  2. 可以给 rand() 传一个参数作为 rand() 的种子,然后 rand 函数会依据这个种子进行随机生成

这里加上 floor() 函数的用法:

  1. floor: 取float的整数值(向下取整)

所以,floor(rand()*2) 和 floor(rand(0)*2) 表示产生0或者1。
我们来比较下它们的区别:
4.png
5.png
可以看到 floor(rand()*2) 产生的数值没有规律,而floor(rand(0)*2) 的规律是01101,具体可以再多插入些数据测试下,肯定是这样的。

下面就是重中之重了,
回顾我们构建报错注入的语句:

select count(*) from information _schema.tables group by concat((select version()),0x7e,floor(rand(0)\*2));

执行前虚拟表为空,执行时, group by 根据 concat((select version()),0x7e,floor(rand(0)*2)) 分组,因为 floor(rand(0)*2) 是有规律的,所以第一次的生成结果为 5.7.24~0 ,以 5.7.24~0 查询虚拟表发现虚拟表中没有该值的主键,所以就将这条语句的结果放入虚拟表中。在放入虚拟表中 函数会再次运算,这时候变成 5.7.24~1 ,所以插入的值变成了 5.7.24~1
流程图.png

当原数据表只有一条时,显然 group by 只会分一组 主键唯一并不会报错,当数据变成两条以上时,查询第二条插入时运算如果结果变成 5.7.24~1 ,就会报错,而三条数据以上一定会报错。
floor(rand(0)*2) 函数的值是固定的,也就是01101... 等,具体分析下操作流程:
  1. 查询前,虚拟表为空。
  2. 查询第一条记录,执行 group by concat((select version()),0x7e,floor(rand(0)*2)) ,结果为 5.7.24~0 (第一次运算) ,查询虚拟表没有 5.7.24~0 的键值,插入时 group by concat((select version()),0x7e,floor(rand(0)*2)) 再次运算,结果为 5.7.24~1 (第二次运算),放在虚拟表中,此时表中
keycount(*)
5.7.24~11
  1. 查询第二条记录,再次执行 group by concat((select version()),0x7e,floor(rand(0)*2)) ,结果为 5.7.24~1 (第三次运算),查询发现 5.7.24~1 的键值存在,所以不会再次计算,直接 count(/*)+1 ,此时表中
keycount(*)
5.7.24~12
  1. 查询第三条记录,再次执行 group by concat((select version()),0x7e,floor(rand(0)*2)) ,结果为 5.7.24~0 (第四次运算),虚拟表中没有键值为 5.7.24~0 ,所以插入数据,在插入时再次计算,作为主键 5.7.24~1 ,而此时 5.7.24~1 已经存在在虚拟表中,主键必须唯一,所以产生报错。
Tips:综上所述,当数据表记录大于三时,使用 group by floor(rand(0)*2) 一定报错。
ERROR 1062 (23000): Duplicate entry '5.7.24~1' for key '<group_key>'

6.png

总结:利用 floor() 函数,group by 子句,rand() 函数联用的报错注入是因为 group by 子句在查询虚拟表和插入虚拟表时,两次相同语句执行的结果不一致就会引发错误。

  • extractvalue() 报错原理

函数解释:

  1. extractvalue():从目标 XML 中返回包含所查询值的字符串。
  2. EXTRACTVALUE (XML_document, XPath_string); 
  3. 第一个参数:XML_document是 String 格式,为 XML 文档对象的名称,文中为 Doc 。
  4. 第二个参数:XPath_string ( Xpath 格式的字符串)
  5. concat:返回结果为连接参数产生的字符串。

第一个参数表示目标 xml 文档,第二个参数表示 xml 路径。
我们主要利用在第二个参数的位置,当正常查询时,第二个参数应该是 /xxx/xxx/xxx/… 这种形式。
如果写成其他形式就会报错。
当正常查询时,即使查询不到也不会报错。
7.png
当我们尝试报错注入时:
测试代码:

select pname,page from person where pwd = '111' and (extractvalue('hhhhhhh',concat(0x7e,(select database()))));

8.png

Tips: extractvalue() 能查询字符串的最大长度为32,就是说如果我们想要的结果超过32,就需要用 substring() 函数截取。

测试代码:

select pname,page from person where pwd = '111' and (extractvalue('hhhhhhh',concat(0x7e,substring(hex((select database())),1,20))));

9.png
10.png

总结:利用 xpath 字符串格式报错进行注入,要注意查询字符长度限制。

  • updatexml() 报错原理

函数解释:

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

updatexml() 报错与 extractvalue() 报错类似,都是由于 xpath 格式错误报错,同样能查询字符串的最大长度为32,同样在第二个参数出插入我们需要的语句代码。
测试代码:

select pname,page from person where pwd = '111' and updatexml(1,concat(0x7e,(select database()),0x7e),1);

11.png

总结:利用 xpath 字符串格式报错进行注入,要注意查询字符长度限制。

漫长的整理写作过程,继续滚去学习了。。。


参考链接:

sql注入--双查询报错注入原理探索
双查询注入
报错注入
学习基于extractvalue()和updatexml()的报错注入
报错注入分析之Extractvalue分析
MySQL 5.1 提供XML内置支持(XPath)

原文作者:Keefe

原文链接:SQL 注入 报错注入

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

Last Modified: May 14, 2019