前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mysql字符编码利用技巧

Mysql字符编码利用技巧

作者头像
phith0n
发布2020-10-15 11:33:42
1.6K0
发布2020-10-15 11:33:42
举报
文章被收录于专栏:离别歌 - 信息安全与代码审计

0x01 由某CTF题解说起

小密圈里有人提出的问题,大概代码如下:

看了一下,明显考点是这几行:

代码语言:javascript
复制
<?php
if ($username === 'admin') {
    if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1') {
        die('Permission denied!');
    }
}
$result = $mysqli->query("SELECT * FROM z_users where username = '{$username}' and password = '{$password}'");

这个if语句嫌疑很大,大概是考我们怎么登陆admin的账号,请先看这一篇文章 https://cloud.tencent.com/developer/article/1717427

本文中利用Â等latin1字符来绕过php的判断。这个CTF也是用同样的方法来解决:

可见,我传入的username=admin%c2,php的检测if ($username === 'admin')自然就可以绕过的,在mysql中可以正常查出username='admin'的结果。

0x02 Trick复现

那么,为什么执行SELECT * FROM user WHERE username='admin\xC2' and password='admin'却可以查出用户名是admin的记录?

刚好这段时间有人问我为什么在他的计算机上无法复现,我们来深入研究研究。

编写如下代码:

代码语言:javascript
复制
<?php
$mysqli = new mysqli("localhost", "root", "root", "cat");

/* check connection */
if ($mysqli->connect_errno) {
    printf("Connect failed: %s\n", $mysqli->connect_error);
    exit();
}

$mysqli->query("set names utf8");

$username = addslashes($_GET['username']);

/* Select queries return a resultset */
$sql = "SELECT * FROM `table1` WHERE username='{$username}'";

if ($result = $mysqli->query( $sql )) {
    printf("Select returned %d rows.\n", $result->num_rows);

    while ($row = $result->fetch_array(MYSQLI_ASSOC))
    {
        var_dump($row);
    }

    /* free result set */
    $result->close();
} else {
    var_dump($mysqli->error);
}

$mysqli->close();

然后在数据库cat中创建表table1

代码语言:javascript
复制
CREATE TABLE `table1` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) COLLATE latin1_general_ci NOT NULL,
  `password` varchar(255) COLLATE latin1_general_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

我特地将字符集设置为latin1,其实默认情况下,Mysql的字符集就是latin1,没必要写明。

插入一个管理员账户:

代码语言:javascript
复制
INSERT `table1` VALUES (1, 'admin', 'admin');

然后,我们访问http://localhost/test.php?username=admin%c2,即可发现%c2被忽略,Mysql查出了username=admin的结果:

假设我们将table1表的字符集换成utf8,就得不到结果了。

0x03 Mysql字符集转换

经过0x02中对该Mysql Trick的复现,大概也能猜到原理了。

造成这个Trick的根本原因是,Mysql字段的字符集和php mysqli客户端设置的字符集不相同

set names utf8 的意思是将客户端的字符集设置为utf8。我们打开mysql控制台,依次执行SHOW VARIABLES LIKE 'character_set_%';set names utf8;SHOW VARIABLES LIKE 'character_set_%';,即可得到如下结果:

如上图,在默认情况下,mysql字符集为latin1,而执行了set names utf8;以后,character_set_clientcharacter_set_connectioncharacter_set_results等与客户端相关的配置字符集都变成了utf8,但character_set_databasecharacter_set_server等服务端相关的字符集还是latin1。

这就是该Trick的核心,因为这一条语句,导致客户端、服务端的字符集出现了差别。既然有差别,Mysql在执行查询的时候,就涉及到字符集的转换。

2008年鸟哥曾在博客中讲解了Mysql字符集:

  1. MySQL Server收到请求时将请求数据从character_set_client转换为character_set_connection;
  2. 进行内部操作前将请求数据从character_set_connection转换为内部操作字符集

在我们这个案例中,character_set_clientcharacter_set_connection被设置成了utf8,而内部操作字符集其实也就是username字段的字符集还是默认的latin1。于是,整个操作就有如下字符串转换过程:

utf8 --> utf8 --> latin1

最后执行比较username='admin'的时候,'admin'是一个latin1字符串。

0x04 漏洞成因

那么,字符集转换为什么会导致%c2被忽略呢?

说一下我的想法,虽然我没有深入研究,但我觉得原因应该是,Mysql在转换字符集的时候,将不完整的字符给忽略了

举个简单的例子,这个汉字的UTF-8编码是\xE4\xBD\xAC,我们可以依次尝试访问下面三个URL:

代码语言:javascript
复制
http://localhost:9090/test.php?username=admin%e4
http://localhost:9090/test.php?username=admin%e4%bd
http://localhost:9090/test.php?username=admin%e4%bd%ac

可以发现,前两者都能成功获取到username=admin的结果,而最后一个URL,也就是当我输入字完整的编码时,将会被抛出一个错误:

为什么会抛出错误?原因很简单,因为latin1并不支持汉字,所以utf8汉字转换成latin1时就抛出了错误。

那前两次为什么没有抛出错误?因为前两次输入的编码并不完整,Mysql在进行编码转换时,就将其忽略了。

这个特点也导致,我们查询username=admin%e4时,%e4被省略,最后查出了username=admin的结果。

0x05 为什么只有部分字符可以使用

我在测试这个Trick的时候发现,username=admin%c2时可以正确得到结果,但username=admin%c1就不行,这是为什么?

我简单fuzz了一下,如果在admin后面加上一个字符,有如下结果:

  1. \x00~\x7F: 返回空白结果
  2. \x80~\xC1: 返回错误Illegal mix of collations
  3. \xC2~\xEF: 返回admin的结果
  4. \xF0~\xFF: 返回错误Illegal mix of collations

这就涉及到Mysql编码相关的知识了,先看看维基百科吧。

UTF-8编码是变长编码,可能有1~4个字节表示:

  1. 一字节时范围是00-7F
  2. 两字节时范围是C0-DF
  3. 三字节时范围是E0-EF80-BF
  4. 四字节时范围是F0-F780-BF

然后根据RFC 3629规范,又有一些字节值是不允许出现在UTF-8编码中的:

所以最终,UTF-8第一字节的取值范围是:00-7F、C2-F4,这也是我在admin后面加上80-C1、F5-FF等字符时会抛出错误的原因。

关于所有的UTF-8字符,你可以在这个表中一一看到: http://utf8-chartable.de/unicode-utf8-table.pl

0x06 Mysql UTF8 特性

那么,为什么username=admin%F0也不行呢?F0是在C2-F4的范围中呀?

这又涉及到Mysql中另一个特性:Mysql的utf8其实是阉割版utf-8编码,Mysql中的utf8字符集最长只支持三个字节

所以,我们回看前文列出的UTF-8编码第一字节的范围,

三字节时范围是E0-EF80-BF 四字节时范围是F0-F780-BF

F0-F4是四字节才有的,所以我传入username=admin%F0也将抛出错误。

如果你需要Mysql支持四字节的utf-8,可以使用utf8mb4编码。我将原始代码中的set names改成set names utf8mb4,再看看效果:

已经成功得到结果。

0x07 总结

本文深入研究了Mysql编码的数个特性,相信看完本文,对于第一章中的CTF题目也没有疑问了。

通过这次研究,我有几个感想:

  1. 研究东西还是需要深入,之前写那篇文章的时候并没有深入研究原理,所以心里总是很迷糊
  2. 维基百科上涵盖了很多知识,有必要的时候也可以多看看
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0x02 Trick复现
  • 0x03 Mysql字符集转换
  • 0x04 漏洞成因
  • 0x05 为什么只有部分字符可以使用
  • 0x06 Mysql UTF8 特性
  • 0x07 总结
相关产品与服务
云数据库 SQL Server
腾讯云数据库 SQL Server (TencentDB for SQL Server)是业界最常用的商用数据库之一,对基于 Windows 架构的应用程序具有完美的支持。TencentDB for SQL Server 拥有微软正版授权,可持续为用户提供最新的功能,避免未授权使用软件的风险。具有即开即用、稳定可靠、安全运行、弹性扩缩等特点。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档