by [email protected]

前几天提到CVE-2012-1172这个漏洞,今天来把分析过程写下。
其实PHP在处理文件上传的时候,曾经发生过多个类似的漏洞,我们先列举几个:

2004年:PHP处理RFC1867 MIME格式导致数组错误漏洞
http://www.nsfocus.net/index.php?act=ma … w&mid=2368

2009年:PHP Bug #49683:$_FILES overwrite
https://bugs.php.net/bug.php?id=49683

2011年:File path injection in PHP  5.3.6 file upload (CVE 2011-2202)
http://blog.kotowicz.net/2011/06/file-p … -file.html

CVE-2012-1172实际上和上面的漏洞比较相似,我们先看poc

 
 

我们同时上传一个jpg文件和一个txt文件,返回结果如下:

Array
 (
 [file] => Array
 (
 [name] => Array
 (
 [[type] => text/plain
 [[name] => test.txt
 [[tmp_name] => C:\Windows\Temp\phpAFF2.tmp
 [[error] => 0
 [[size] => 5231
 )

[type] => Array
 (
 [[type] => image/jpeg
 )

[tmp_name] => Array
 (
 [[type] => C:\Windows\Temp\phpAFE1.tmp
 )

[error] => Array
 (
 [[type] => 0
 )

[size] => Array
 (
 [[type] => 5145
 )

明显的处理异常发生了变量覆盖的情况,因为如果是正常的文件上传,应该是返回类似如下结果的数组

Array
 (
 [file] => Array
 (
 [name] => Array
 (
 [type] => 11.jpg
 [name] => test.txt
 )

[type] => Array
 (
 [type] => image/jpeg
 [name] => text/plain
 )

[tmp_name] => Array
 (
 [type] => C:\Windows\Temp\phpD4C7.tmp
 [name] => C:\Windows\Temp\phpD4C8.tmp
 )

[error] => Array
 (
 [type] => 0
 [name] => 0
 )

[size] => Array
 (
 [type] => 5145
 [name] => 5231
 )

)

)

下面我们来分析下漏洞是怎么产生的。查看文件 rfc1867.c中对于是否为合法上传的判断:

992 if (!skip_upload) {
 993 char *tmp = param;
 994 long c = 0;
 995
 996 while (*tmp) {
 997 if (*tmp == ‘[') {
 998 c++;
 999 } else if (*tmp == ']‘) {
 1000 c–;
 1001 if (tmp[1] && tmp[1] != ‘[') {
 1002 skip_upload = 1;
 1003 break;
 1004 }
 1005 }
 1006 if (c < 0) {
 1007 skip_upload = 1;
 1008 break;
 1009 }
 1010 tmp++;
 1011 }
 1012 }

这地方没有考虑到c>0的可能,也就是说我们提交的变量名称中允许出现[比]数量多的情况,比如可以为file[[type]或者file[name][的形式。正是由于这两种格式的变量处理不当才导致了变量覆盖的问题。继续看代码:

1146 /* is_arr_upload is true when name of file upload field
    1147 * ends in [.*]
    1148 * start_arr is set to point to 1st [ */
    1149 is_arr_upload = (start_arr = strchr(param,'[')) && (param[strlen(param)-1] == ‘]’);

这个地方判断是否为批量上传的比较关键,需要以]结尾的才认为是批量上传,具体来说file[[type]可以使is_arr_upload为1而 file[name][则使is_arr_upload为0,之后处理的方式完全不同

   1151 if (is_arr_upload) {
    1152 array_len = strlen(start_arr);
    1153 if (array_index) {
    1154 efree(array_index);
    1155 }
    1156 array_index = estrndup(start_arr + 1, array_len - 2);
    1157 }
 ……

1225 /* Add $foo[name] */
    1226 if (is_arr_upload) {
    1227 snprintf(lbuf, llen, “%s[name][%s]“, abuf, array_index);
    1228 } else {
    1229 snprintf(lbuf, llen, “%s[name]“, param);
    1230 }

……

1260 /* Add $foo[type] */
    1261 if (is_arr_upload) {
    1262 snprintf(lbuf, llen, “%s[type][%s]“, abuf, array_index);
    1263 } else {
    1264 snprintf(lbuf, llen, “%s[type]“, param);
    1265 }

file[[type]按照is_arr_upload为1的方式处理,产生的变量依次为file[name][[type]和
file[type][[type]。

file[name][按照is_arr_upload为0的方式处理,产生的变量依次为 file[name][[name]和
file[name][[type]。

按照注册的顺序,后者会覆盖前者产生的相同的变量。

漏洞分析就到这里,具体在实战中的运用还要看应用的代码,在一定条件下是可以下载文件或者上传非法文件的。

官方修复的方式也比较简单

— main/rfc1867.c 2012/01/01 23:51:21 321663
 +++ main/rfc1867.c 2012/01/01 23:54:25 321664
 @@ -942,6 +942,10 @@
 }
 tmp++;
 }
 +    /* Brackets should always be closed */
 +    if(c != 0) {
 +     skip_upload = 1;
 +    }
 }

参考:

https://nealpoole.com/blog/2011/10/dire … e-uploads/

http://lxr.php.net/opengrok/xref/PHP_5_2/main/rfc1867.c

转自:http://bbs.wolvez.org/viewtopic.php?id=267

源链接

Hacking more

...