字符编码与乱码问题的本质
声明
本文版权归原作者所有,未经允许禁止转载。
平常在渗透或者攻防基本上都会遇到各种字符编码问题,很多时候解决了但并不知道其根本原因,于是我便简单花了点时间探究了一下,总算了解了”字符编码与乱码问题”的本质。
字符编码与解码
字符编码是将一个可读或者可见的字符根据特定的编码表转换为特定的字节或字节序列,简单举个例子:
A 和 B 需要使用 HTTP 协议进行聊天,A 发了一个包,Body 是”你好”,但是 HTTP 协议在网络传输过程中是原始字节也是二进制流,因此需要将我们的字符”你好”进行编码才能进行传输,UTF-8 编码后为:
e4 bd a0 e5 a5 bd
B 接收到后需要把字节内容还原为可读的字符,但是 B 不知道是什么编码,这个时候 HTTP 协议中的 Content-Type 头就起作用了,告诉对方这是什么类型的编码,B 就会按照特定的编码表进行解码,得到:
你好
乱码问题
大多数情况下可能乱码问题都来自于 BurpSuite。
- 中文乱码问题
可能在 Repeater 中或者 Proxy 显示中文乱码,原因是 BurpSuite 的 Message Editor 的 Character sets 设置为 Display as raw bytes 或者是 Recognize automatically based on message headers,分别说一下原因:
第一个 Display as raw bytes 顾名思义就是显示原始字节,其实本质是使用了 ISO-8859-1 编码,ISO-8859-1 编码是单字节编码,不支持中文,但它单个字节都可以转换为一个字符。
而 Recognize automatically based on message headers 顾名思义就是根据请求头自动识别,当你的请求头编码 charset=xxx 不支持中文时,也会显示乱码。
- 二进制流数据字节丢失问题
这也属于一种乱码,本质是字符编码转换中出现了字节丢失或字节错误,举个例子,比如使用 BurpSuite 打反序列化漏洞的时候,大多数情况下不行,因为反序列化载荷是二进制流数据,当你将它粘贴至 BurpSuite 时,如果 Message Editor 设置为了 UTF-8,那么 BurpSuite 其实本质上执行了以下代码:
// 读取文件
String filePath = "/tmp/payload";
byte[] bytes1 = Files.readAllBytes(Paths.get(filePath));
// 转为UTF-8
String showString = new String(bytes1, "UTF-8");
// 发送时编码为字节流传输
byte[] bytes2 = showString.getBytes("UTF-8");因为你的 payload 并不是 UTF-8 但被强制转为了 UTF-8 字符串,那么就会出现乱码,因为非法 UTF-8 字节会被替换为 �(U+FFFD),部分字节会被插入字节填充满足长度要求,其他编码类型也一样,那么在还原为字节流的时候就会出现丢失或错误的情况。
当你选择 Display as raw bytes 时,代码会变成这样:
// 读取文件
String filePath = "/tmp/payload";
byte[] bytes1 = Files.readAllBytes(Paths.get(filePath));
// 转为UTF-8
String showString = new String(bytes1, "ISO-8859-1");
// 发送时编码为字节流传输
byte[] bytes2 = showString.getBytes("ISO-8859-1");你输入的 payload 字节流数据按照 ISO-8859-1 进行编码,每个字节都对应有一个字符,一对一映射,显示在你的 BurpSuite 中,看起来是乱码其实都是正确字符,当你点击发送时会被转为原始字节流,由于是单字节一对一映射,不会出现错误或者填充的情况。
补充
如果你喜欢安装 BurpSuite 插件,那么你可能了解过 https://github.com/PortSwigger/hackvertor,它是可以帮助你编码解码的以及文件标签支持。
比如 base64 解码,在很多 payload 生成工具中默认都会有 base64 编码格式的,那么我们可以发送字节流载荷时使用 base64 解码标签进行自动解码,防止乱码的情况发生导致利用失败。
你可能会好奇为什么 base64 解码后不会被 BurpSuite 的 Message Editor 编码设置影响呢?
查看源码后发现,它将我们的 payload 字节流转为了 ISO-8859-1 编码,保证原始字节流不受编码影响:
@Override
public String bytesToString(byte[] bytes) {
return new String(bytes, StandardCharsets.ISO_8859_1);
}还有一个问题,当你使用 read_file 标签去读取一个字节流文件时,也会出现乱码问题,原因是默认它是 UTF-8 编码:
<@read_file('UTF-8',true,'xxx')>/tmp/payload</@read_file>
因此如果要是使用它读取字节流文件,需要替换为 ISO-8859-1 :
<@read_file('ISO-8859-1',true,'xxx')>/tmp/payload</@read_file>