Contents

网址中如何过滤反斜杠字符?

复习软件安全补考时,课件中过滤网址中 「\」 的相关问题困扰了笔者一些时间。在与同学探讨的同时对 Unicode 有了更深的理解,在此简单记录一下。

前置知识

我们都知道 ASCII 码是对字符很常见的编码方式,它只需要 7 比特便可以编码一个字符。

https://s2.loli.net/2022/02/04/iZUhA5JaQSKce2u.png

但问题是,中文怎么办?德语怎么办?日语怎么办?随着字符集的扩充,美国人提出了一套兼容所有语言的字符集:Unicode. 在这个字符集上,有不少编码方案:UTF-8,UTF-16,UTF-32. 其中 UTF-8 是当前互联网上最广泛的编码方案,它的出现十分惊艳。

阮行止:“unicode 是文字到整数的映射,utf8 是整数到字节序列的映射。”

换句话说,unicode 只是一个整数集合,将各国文字集映射为这个整数集合,utf8 是它的编码格式,将这个整数集合映射为字节序列集合,其中的每个字节序列便是 unicode 中所表示字符的字节表示形式。

UTF-8 的最大特点是长度可变。它可以使用 1-4 个字节来表示一个字符(可见编码集十分庞大,而实际上也是,汉字已经被完全覆盖),编码规则如下:

  • 对于单个字节就可以表示的字符,第一个比特设置为 0,后面的 7 比特就是该字符的 Unicode 码点。
  • 对于 N (2-4)个字节才能表示的字符,第一个字节前 N 比特设为 0,N+1 位为 1. 后续 N-1 字节的前 2 比特均设为 10,其余的比特则用 Unicode 码点填充。
Unicode 十六进制码点范围 UTF-8 二进制表示格式
0000 0000 - 0000 007F 0xxxxxxx
0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

对于 ASCII 码中的 0-127 号字符,它在 Unicode 中也被映射为了相同值的整数。

也就是说对于字符 「\」,它在 Unicode 中就是 0x2f . 而它本身可以用单字节表示,所以的 utf8 编码结果为 \x2f,可以看到 UTF-8 完全兼容了 ASCII 编码!

而对于字符「汉」,它在 Unicode 中被映射为了 0x6c49(0110 1100 0100 1001). 它位于 0000 0800 - 0000 FFFF ,所以需要三字节来表示,所以 UTF-8 编码为 \xe6\xb1\x89 (11100110 10110001 10001001)

解码过程也很简单:如果第一个字节第一位是 0 ,则说明只有一个字节;如果第一个字节是 1,则连续有多少个 1,就代表该字符一共有几个字节。确定了长度以后按编码格式解析即可。

问题背景

在某 web 应用中,用户可以部分控制网址,比如请求 xyz 子目录,会访问如下网址

http://192.168.0.27/Scripts/xyz

但什么保护措施都不做是肯定不行的,否则用户可以利用「\」(%2f )进行目录逃逸

http://192.168.0.27/Scripts/..%2f../winnt/system32/cmd.exe?/c+dir+d:\

于是此 web 应用通过检查字节序列 %2f 来尝试过滤掉反斜杠。

但有人造出了这个 payload,同样达到了目录逃逸的效果。

http://192.168.0.27/Scripts/..%c0%af../winnt/system32/cmd.exe?/c+dir+d:\

为什么会这样?

问题分析

原因很明显,字节序列 \xc0\xaf (11000000 10101111)同样会被解析成 「\」!

这不免引出下一个问题:Unicode 的 UTF-8 不是一一对应吗?众所周知字符编码如果不是一一对应,将会带来非常严重的编码问题。而事实上,\x2f 是「\」的 UTF-8 唯一正确格式。

Merrg1n:“utf8 2f 才是合法形式 因为 utf8 要求最短编码。但是一般的解析器都做的是按编码逻辑,而不会去判断是不是最短编码。所以你甚至可以构造 d0 80 af 来表示「\」。”

所以问题的根源是:UTF-8 编码器会选择最短编码,但是解码器不会。

Protoss X1do0:“这是否有点…”

Merrg1n:“解析器没有必要搞最短。你检查这个没有意义。对于正常程序来说 你要过滤「\」字符 你应该做的是在 str 层面去做 而不是 bytes 层面。现代程序请区分 str 和 byte !”

也就是说,该 web 程序在字节序列层面检查「\」是不明智的,因为 Unicode 解码器理论上的重叠现象。它要做的应该是直接字符串对比过滤「\」。

Protoss X1do0:“soga,卧槽,秀啊!Unicode 有亿点巧妙!”

Merrg1n:“你是不是 csapp 没听课?[黄豆流汗]”

True Keuin:“你是不是 csapp 翘课了?[黄豆招手] ”

Protoss X1do0 沉默。