浅析白盒审计中的字符编码及SQL注入(1)
尽管现在呼吁所有的程序都使用unicode编码,所有的网站都使用utf-8编码,来一个统一的国际规范。但仍然有很多,包括国内及国外(特别是非英语国家)的一些cms,仍然使用着自己国家的一套编码,比如gbk,作为自己默认的编码类型。也有一些cms为了考虑老用户,所以出了gbk和utf-8两个版本。
我们就以gbk字符编码为示范,拉开帷幕。gbk是一种多字符编码,具体定义自行百度。但有一个地方尤其要注意:
通常来说,一个gbk编码汉字,占用2个字节。一个utf-8编码的汉字,占用3个字节。在php中,我们可以通过输出
echo strlen("和");
来测试。当将页面编码保存为gbk时输出2,utf-8时输出3。
除了gbk以外,所有ANSI编码都是2个字节。ansi只是一个标准,在不用的电脑上它代表的编码可能不相同,比如简体中文系统中ANSI就代表是GBK。
以上是一点关于多字节编码的小知识,只有我们足够了解它的组成及特性以后,才能更好地去分析它身上存在的问题。
说了这么多废话,现在来研究一下在SQL注入中,字符编码带来的各种问题。
0×01 MYSQL中的宽字符注入
这是一个老话题了,也被人玩过无数遍。但作为我们这篇文章的序幕,也是基础,是必须要提的。
我们先搭建一个实验环境。暂且称之为phithon内容管理系统v1.0,首先先新建一个数据库,把如下压缩包中的sql文件导入:
测试代码及数据库:http://pan.baidu.com/s/1eQmUArw 提取密码:75tu
之后的phithon内容管理系统会逐步完善,但会一直使用这个数据表。
源码很简单(注意先关闭自己php环境的magic_quotes_gpc):
//连接数据库部分,注意使用了gbk编码,把数据库信息填写进去
- <?php
- //连接数据库部分,注意使用了gbk编码,把数据库信息填写进去
- $conn = mysql_connect('localhost', 'root', 'toor!@#$') 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()); //sql出错会报错,方便观察
- ?>
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="gbk" />
- <title>新闻</title>
- </head>
- <body>
- <?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把文章从news表中取出来。
在这个sql语句前面,我们使用了一个addslashes函数,将$id的值转义。这是通常cms中对sql注入进行的操作,只要我们的输入参数在单引号中,就逃逸不出单引号的限制,无法注入,如下图:
那么怎么逃过addslashes的限制?众所周知addslashes函数产生的效果就是,让’变成\’,让引号变得不再是“单引号”,只是一撇而已。一般绕过方式就是,想办法处理\’前面的\:
1.想办法给\前面再加一个\(或单数个即可),变成\\’,这样\被转义了,’逃出了限制
2.想办法把\弄没有。
我们这里的宽字节注入是利用mysql的一个特性,mysql在使用GBK编码的时候,会认为两个字符是一个汉字(前一个ascii码要大于128,才到汉字的范围)。如果我们输入%df’看会怎样:
我们可以看到,已经报错了。我们看到报错,说明sql语句出错,看到出错说明可以注入了。
为什么从刚才到现在,只是在’也就是%27前面加了一个%df就报错了?而且从图中可以看到,报错的原因就是多了一个单引号,而单引号前面的反斜杠不见了。
这就是mysql的特性,因为gbk是多字节编码,他认为两个字节代表一个汉字,所以%df和后面的\也就是%5c变成了一个汉字“運”,而’逃逸了出来。
因为两个字节代表一个汉字,所以我们可以试试“%df%df%27”:
不报错了。因为%df%df是一个汉字,%5c%27不是汉字,仍然是\’。
那么mysql怎么判断一个字符是不是汉字,根据gbk编码,第一个字节ascii码大于128,基本上就可以了。比如我们不用%df,用%a1也可以:
%a1%5c他可能不是汉字,但一定会被mysql认为是一个宽字符,就能够让后面的%27逃逸了出来。
于是我可以构造一个exp出来,查询管理员账号密码: