utf8_decode的安全性
本文按署名·非商业用途·保持一致授权作者:
,发表于2008年10月10日14时55分
在80sec看到一篇和php有关的安全文章,提到:”在php里使用的某些编码函数在处理畸形的utf8序列时会产生不正确的结果,这样在某些情况下可能会引起应用程序的安全漏洞,绕过某些逻辑过滤等等”。
我想对这篇文章扩展一下,也纠正一下其中错误的观点。
首先,要了解utf-8对unicode字符的编码规则。
0000-007F | 0xxxxxxx
0080-07FF | 110xxxxx 10xxxxxx
0800-FFFF | 1110xxxx 10xxxxxx 10xxxxxx
10000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
其次,看看utf8_decode()这个函数到底做了什么。(这个函数可在php源代码的ext/xml/xml.c里找到)
这个函数,把utf-8串,还原成unicode的顺序,用一个2 bytes的变量来存储,在编码规则上类似utf-16。如果大于0xff,就返回’?'(0x3f);否则用char强制转换,返回低8位。
例如11010011 10001111,按上表,在第二行规则里,于是unicode就是00000 10011 001111,10011取自前一个byte,001111取自后一个byte。这个值是大于0xff的,所以返回问号?。测试代码:php -r ‘echo utf8_decode(chr(0xd3).chr(0x8f));’
单引号,这个经常被用来进行注入的符号,它的二进制表现为100111。也就是说,我只需要构造一个utf-8字符串,使得取出unicode后,这个unicode在存储为二进制时,后16个bits为00000000 00100111。这样,我就能在utf8_decode之后,获得一个单引号。
继续看上面的编码规则。
在0000-007F | 0xxxxxxx,这个单引号的utf-8就可以构造为00100111,这个是一个遵守规则的utf-8串。
0080-07FF | 110xxxxx 10xxxxxx,可以构造为11000000 10100111,这是一个非法的,按照编码规则的范围,不会出现这样的组合。
0800-FFFF | 1110xxxx 10xxxxxx 10xxxxxx,可以构造为11100000 10000000 10100111,这是一个非法的,理由同上。
10000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx,可以构造为11110xxx 10xx0000 10000000 10100111。这个情况下,因为unicode已经超出了2个bytes的范围,由于utf8_decode只有2个bytes的存储,所以即使是合法的utf-8串,也会导致出现返回’的情况。
所以utf8_encode所引发的问题,并非如80sec所说的“处理畸形的utf8序列”才会出现,如上面所示,如果传入的utf-8字符串,在unicode字符集里,如果是大于0xFFFF的,即使是合法的串,也有可能会引发这个问题。
utf8_encode的patch必须有两个地方,一个是存储utf-8字符的那个临时变量,必须由2bytes改为4bytes;第二个就是做utf-8字符串的合法性判断。
其实,从PHP层面来说,即使这个utf8_decode出现不合理的情况,也不应该成为应用程序出现漏洞的理由。来看这段代码。
$name=mysql_real_escape_string($name);
$name=utf8_decode($name);
$r=mysql_query("select * from u where n='".$name."' limit 1;");
这样糟糕的程序,必定会带来安全隐患,因为escape不是在最后处理,中间的过程,即使不是utf8_decode,而是其他函数,也同样会有不可预料的返回。改成如下的顺序,整个代码在安全性方面就无懈可击了,对于utf8_decode的返回,我们根本不需要担心。所以,我个人认为,如果一个程序需要去担心utf8_decode()会带来安全隐患,那整个程序,安全隐患就太多了。
$name=utf8_decode($name);
$name=mysql_real_escape_string($name);
$r=mysql_query("select * from u where n='".$name."' limit 1;");
更多阅读:
unicode及其相关的编码

2008-10-10 17:08:00
最近有遇到类似的问题,实际情况比较复杂,需要 update 进去的值里包括 ? 这种字符,用 mysql_real_escape_string 做了安全性处理,但在存储时,mysql 会自动去掉 ? 以及之后的值。导致bbcode 的正解反解出错。
2008-10-10 20:11:12
2008-10-13 12:25:20
$name=utf8_decode($name);
$name=mysql_real_escape_string($name);
$r=mysql_query(”select * from u where n=’”.$name.”‘ limit 1;”);
程序员能这么考虑是最好的了,但是很多的程序通常是模拟出一个magic quote为on的php环境,这样来全局处理变量,这样实际环境中这样的decode就可能带来安全问题,如果php能够正处处理这些非法的utf-8字符的话,就不会有安全问题了,譬如像iconv函数一样来处理utf-8字符
2008-10-15 23:48:22
magic quote是个糟糕的东西。PHP6将会抛弃这个东西,真好。
magic quote想在整个程序运行之前就对所有输入的数据做安全性过滤,这个想法太扯了,误导了php4,php5时代的程序员。首先是数据在被存储或者输出前可能发生改变;其次是不同的数据处理,安全性要求也不一定相同。例如MySQL可能需要转义’,而XXSQL可能就要转义其他的了。