0%

Mysql字符集转换漏洞

测试代码大体如下:

1
2
3
4
5
mysql_query("set names utf8");
if ($username === 'admin') {
die('Permission denied!');
}
$result = $mysqli->query("SELECT * FROM z_users where username = '{$username}' ");

利用ASCii码来理解

我们想要绕过,我们可以靠输入16进制进去,然后MySQL无法解析的字符会自动省略。 值得注意的是,00-1F 是自动忽略,而后面的特殊字符会进行截断
测试发现我们输入不可显示的字符的16进制可以绕过,如payload:

1
http://127.0.0.1/test/mysql_char.php?username=admin%11

当然这里的%11可以为其它任何的不可显示的字符的十六进制。 【ASCII码】

其实理解为 ASCII码也不能算错,但是这边还有一种理解是关于编码问题的。

字符集转换漏洞

 因为字符集不同 躲过关键词过滤 进入mysql后产生的截断 恢复关键词查询。

MySQL语句

  1. 查看字符集 : show variables like 'character_set_%'
  2. php中设置 客户端字符集 :set names utf8
    这个语句会修改如下客户端设置:
    1
    2
    3
    character_set_client=utf8
    character_set_connection=utf8
    character_set_results=utf8
    然后下面这些服务端是不会修改的:
    1
    2
    3
    4
    character_set_database
    character_set_server
    character_set_filesysytem
    character_set_system
    这样就造成了 服务端与客户端 不匹配
    从而会造成 字符集转换漏洞

整体理解一遍

当我们的mysql接受到客户端的数据后,会认为他的编码是character_set_client,然后会将其将换成character_set_connection的编码,然后进入具体表和字段后,再转换成字段对应的编码。
当查询结果产生后,会从表和字段的编码,转换成character_set_results编码,返回给客户端。但是编码间的转换是会忽略掉无法解析的数据的,所以就造成了字符集转换漏洞。比如说 Latin1(系统默认) 他是无法处理中文的,但是utf-8 等编码会。

于是我们又做出了如下测试:

1
2
3
username=admin%e4     #在mysql中utf8转为latin1被截断为admin
username=admin%e4%bd #在mysql中utf8转为latin1被截断为admin
username=admin%e4%bd%ac #在mysql中utf8转为latin1失败,出错

这里截断原因是,MySQL在字符集转换的时候会自动将不完整的字符忽略。
转换出错的原因是,Latin1 不支持汉字。

关于编码

这里我们还了解一下 【编码理解】
顺便贴一下 【UTF-8查询表】

提取出重点,UTF-8是变长编码,有可能是1~4字节表示:

  • 一个字节时,字节的范围是[00-7F]
  • 两个字节时,字节的范围是[C0-DF] [80-BF]
  • 三个字节时,字节的范围是[E0-EF] [80-BF] [80-BF]
  • 四个字节时,字节的范围是[F0-F7] [80-BF] [80-BF] [80-BF]

mysql UTF-8 特性

Mysql的utf8其实是阉割版utf-8编码,Mysql中的utf8字符集最长只支持三个字节
utf8mb4编码才是包含完整的UTF8

所以相对来说,对于类似的绕过利用

  • 对于UTF8,可以利用第一个字节范围[00-7F] [C2-EF]
  • 对于UTF8mb4,可以利用第一个字节范围[00-7F] [C2-F4]

其实即使我的所有字符集设置都为 utf8 但是也是存在这个问题的,我在mysql中测试等出的结论就是会忽略那些 不太正常的字符 也有些是截断。

利用 两个字节的 字符进行绕过

如下代码:

1
2
3
4
5
6
function check_password($str){
if(preg_match("/admins/i",$str)){
return 1;
}
return 0;
}

放回 1 就是触发waf了,那么我们怎么绕过?
我们可以使用一些特别的字符进行代替 【utf8表】 如这个链接里的一些字符就可以代替 a (mysql将它识别为a)截个图:

比如这里的这几个就可以代替a,如我们输入以下测试:

1
http://127.0.0.1/test/mysql_char.php?username=%C3%A3dmin

也是可以通过的(还是文章最前面那个环境) 。
这里除了a以为其它许多字符都基本可以找到代替的(b我没有找到),找的话就找长的像的然后一个一个测试。如:

记一个 payload

这边题目给的源码是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
mysql_query("set names utf8");
function check_username($str){
if(preg_match("/^admin$/i",$str)){
return 1;
}
for($i=195;$i<240;$i++){
if(preg_match("/".chr($i)."/i",$str)){
return 1;
}
}
return 0;
}
function check_password($str){
if(preg_match("/admins/i",$str)){
return 1;
}
return 0;
}

$sql="select * from user where username='$username' and password='$password'";

但是我觉得正在的源码不是这样的,肯定还有其它的过滤 waf 。利用上面的知识测试和尝试最后得到一个payload(不唯一)

1
?username=admin%c2&password=%C3%A3dmins

主要是这个 username 绕过,讲道理 我 %80 等等都可以过的,但是这里就%c2 可以过,应该还是内部控制了其范围。
这就记住了 测试就用%C2 (先用它测)。


-------------本文结束感谢您的阅读-------------