宽字节注入漏洞的利用与学习
基础知识
字符、字符集与字符序
字符(character)是组成字符集(character set)的基本单位。对字符赋予一个数值(encoding)来确定这个字符在该字符集中的位置。
字符序(collation)指同一字符集内字符间的比较规则。
宽字节
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象。
GBK编码取值范围
GBK采用双字节表示,总体编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间
GB2313编码取值范围
gb2312编码的取值范围。它的高位范围是0xA1~0xF7,低位范围是0xA1~0xFE
MYSQL的字符集转换过程
MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
进行内部操作前将请求数据从character_set_connection转换为内部操作字符集,其确定方法如下:
- 使用每个数据字段的CHARACTER SET设定值;
- 若上述值不存在,则使用对应数据表的DEFAULT CHARACTER SET设定值(MySQL扩展,非SQL标准);
- 若上述值不存在,则使用对应数据库的DEFAULT CHARACTER SET设定值;
- 若上述值不存在,则使用character_set_server设定值。
将操作结果从内部操作字符集转换为character_set_results。
重点:宽字节注入发生的位置就是PHP发送请求到MYSQL时字符集使用character_set_client设置值进行了一次编码。
addslashes()
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
语法:
1 | addslashes(string) |
参数说明:
参数 | 描述 |
---|---|
string | 必需。规定要转义的字符串。 |
mysql_real_escape_string()
mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
下列字符受影响:
1 | \x00 |
语法:
1 | mysql_real_escape_string(string,connection) |
参数说明:
参数 | 描述 |
---|---|
string | 必需。规定要转义的字符串。 |
connection | 可选。规定 MySQL 连接。如果未规定,则使用上一个连接。 |
incov()
(PHP 4 >= 4.0.5, PHP 5, PHP 7)
iconv — 字符串按要求的字符编码来转换
语法:
1 | string iconv ( string $in_charset , string $out_charset , string $str ) |
将字符串 str 从 in_charset 转换编码到 out_charset。
参数说明:
参数 | 描述 |
---|---|
in_charset | 输入的字符集。 |
out_charset | 输出的字符集。 |
str | 要转换的字符串。 |
相关字符集设置
- character_set_client:客户端发送过来的SQL语句编码,也就是PHP发送的SQL查询语句编码字符集。
- character_set_connection:MySQL服务器接收客户端SQL查询语句后,在实施真正查询之前SQL查询语句编码字符集。
- character_set_database:数据库缺省编码字符集。
- character_set_filesystem:文件系统编码字符集。
- character_set_results:SQL语句执行结果编码字符集。
- character_set_server:服务器缺省编码字符集。
- character_set_system:系统缺省编码字符集。
- character_sets_dir:字符集存放目录,一般不要修改
宽字节注入原理
GBK 占用两字节
ASCII占用一字节
PHP中编码为GBK,函数执行添加的是ASCII编码,MYSQL默认字符集是GBK等宽字节字符集。
输入%df和函数执行添加的%5C,被合并成%df%5C。由于GBK是两字节,这个%df%5C被MYSQL识别为GBK。导致本应的%df\变成%df%5C。%df%5C在GBK编码中没有对应,所以被当成无效字符。
%DF’ :会被PHP当中的addslashes函数转义为“%DF\’” ,“\”既URL里的“%5C”,那么也就是说,“%DF’”会被转成“%DF%5C%27”倘若网站的字符集是GBK,MYSQL使用的编码也是GBK的话,就会认为“%DF%5C%27”是一个宽字符。也就是“縗’”
MySQL中的宽字节注入
测试代码
1 | <?php |
利用过程
SQL语句是SELECT * FROM news WHERE tid=’{$id}’,就是根据文章的id把文章从news表中取出来。
在这个sql语句前面,使用了一个addslashes函数,将$id的值转义。这是通常cms中对sql注入进行的操作,只要输入参数在单引号中,就逃逸不出单引号的限制,无法注入,
一般绕过addslashes的方式就是,想办法处理\’前面的\:
- 1.想办法给\前面再加一个\(或单数个即可),变成\’,这样\被转义了,’逃出了限制
- 2.想办法把\弄没有。
宽字节注入是利用mysql的一个特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ascii码要大于128,才到汉字的范围)。如果输入%df’看会怎样:
其中的�\是一个汉字 我们可以改成其他的。根据gbk编码,第一个字节ascii码大于128,基本上就可以了。比如我们不用%df,用%dd也可以:
为什么从刚才到现在,只是在’也就是%27前面加了一个%df就报错了?而且从图中可以看到,报错的原因就是多了一个单引号,而单引号前面的反斜杠不见了。
这就是mysql的特性,因为gbk是多字节编码,他认为两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“輁”,而’逃逸了出来。
因为两个字节代表一个汉字,所以我们可以试试%df%df%27:
不报错了。因为%df%df是一个汉字,%5c%27不是汉字,仍然是\’。
我们可以利用宽字节注入的特点进行手注,也可以在url后面加上%df’丢给sqlmap1
http://localhost/1/0x01/index.php?id=1%df'
GB2312与GBK的不同
GB2312也是属于宽字节,那么使用GB2312连接数据库,看能否进行宽字节注入
结果就是不能注入了:
gb2312编码的取值范围。它的高位范围是0xA1~0xF7,低位范围是0xA1~0xFE,而\是0x5c,是不在低位范围中的。所以,0x5c根本不是gb2312中的编码,所以自然也是不会被吃掉的。
宽字符注入的修复
- 将character_set_client设置为binary(二进制)
只需在所有sql语句前指定一下连接的形式是二进制:1
SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary
当mysql接受到客户端的数据后,会认为他的编码是character_set_client,然后会将之将换成character_set_connection的编码,然后进入具体表和字段后,再转换成字段对应的编码。
然后,当查询结果产生后,会从表和字段的编码,转换成character_set_results编码,返回给客户端。
所以,将character_set_client设置成binary,就不存在宽字节或多字节的问题了,所有数据以二进制的形式传递,就能有效避免宽字符注入。
已经不能注入了
inconv导致的致命后果
测试代码
1 | <?php |
可以看到,它在sql语句执行前,将character_set_client设置成了binary,所以可以避免宽字符注入的问题。但之后其调用了iconv将已经过滤过的参数$id给转换了一下。
给id参数一个值:錦’
报错了。说明可以注入。
“錦“这个字,它的utf-8编码是0xe98ca6,它的gbk编码是0xe55c。
\的ascii码正是5c。那么,当我们的錦被iconv从utf-8转换成gbk后,变成了%e5%5c,而后面的’被addslashes变成了%5c%27,这样组合起来就是%e5%5c%5c%27,两个%5c就是\,正好把反斜杠转义了,导致’逃逸出单引号,产生注入。
正利用了绕过addslashes的两种方式的第一种:将\转义掉。