[TOC]
multipart/form-data
http请求体的入口函数在SAPI_POST_HANDLER_FUNC
<?php var_dump($_FILES); ?>
\
和/
会对文件名进行前向截断,类似info.txt/info.php
的文件名经php处理后会变成info.php
/* The \ check should technically be needed for win32 systems only where * it is a valid path separator. However, IE in all it's wisdom always sends * the full path of the file on the user's filesystem, which means that unless * the user does basename() they get a bogus file name. Until IE's user base drops * to nill or problem is fixed this code must remain enabled for all systems. */
\
和/
字符最后出现的位置,并从该位置截断字符串,从而造成了前向的截断static char *php_ap_basename(const zend_encoding *encoding, char *path) { char *s = strrchr(path, '\\'); char *s2 = strrchr(path, '/'); if (s && s2) { if (s > s2) { ++s; } else { s = ++s2; } return s; } else if (s) { return ++s; } else if (s2) { return ++s2; } return path; }
00
会对文件名进行后向截断,类似info.php(00)xxx
的文件名经php处理过后会变成info.php
header
的时候,仅对内存进行了copy,内存视图如下filename
时候使用strlen
获取filename
长度,strlen
原型如下,在遇到\0
,即内存中的00
时,认为字符串结束了,也就造成了截断头文件:#include <string.h> strlen()函数用来计算字符串的长度,其原型为: unsigned int strlen (char *s); 【参数说明】s为指定的字符串。 strlen()用来计算指定的字符串s 的长度,不包括结束字符"\0"。
同样的,00
可以对$_POST
变量名也可以进行截断,对$GET
,$_COOKIE
等变量名添加00
会导致400
错误
$_POST
变量名中添加00
后,可以看到postxxx
变为了post
$_POST
变量值中添加00
则不受影响,但是会使得字符串长度加1
\
字符会被忽略\+quote
这样相连的两个字符时,会忽略\
只取quote
的值static char *php_ap_getword(const zend_encoding *encoding, char **line, char stop) { char *pos = *line, quote; char *res; while (*pos && *pos != stop) { if ((quote = *pos) == '"' || quote == '\'') { ++pos; while (*pos && *pos != quote) { // 此处会忽略 \ 字符 if (*pos == '\\' && pos[1] && pos[1] == quote) { pos += 2; } else { ++pos; } } if (*pos) { ++pos; } } else ++pos; } if (*pos == '\0') { res = estrdup(*line); *line += strlen(*line); return res; } res = estrndup(*line, pos - *line); while (*pos == stop) { ++pos; } *line = pos; return res; }
;
可以影响文件名解析的结果filename=info.php;.txt;
这样的字符串经过PHP处理后,会变成info.php
,注意filename
的值没有用双引号包裹,用双引号包裹会导致失败Content-Disposition
时,会先使用;
符号进行分词,然后使用=
进行分词。所以,类似filename=info.php;.txt;
这样的字符串第一次分词后结果为filename=info.php
和/txt
,第二次分词时就将filename
解析为了info.php
,大致流程如下SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) /* {{{ */ { //... // 使用 ; 进行分词 while (*cd && (pair = getword(mbuff->input_encoding, &cd, ';'))) { //... // 按照 = 进行解析 if (strchr(pair, '=')) { // ... } // ... } // ... }
filename
Content-Disposition
时,按照从前到后的顺序,如果后面有相同的变量名,则会进行值的覆盖,关键代码filename
首字符为00
时,上传会失败。如下所示,在filename
首字符前插入00
,导致上传失败if (filename[0] == '\0') { #if DEBUG_FILE_UPLOAD sapi_module.sapi_error(E_NOTICE, "No file uploaded"); #endif cancel_upload = UPLOAD_ERROR_D; }
name
首字符为]
时,也会导致上传失败,如下所示*tmp == ']'
时,skip_upload = 1
,导致了后续处理时,忽略了上传的文件while (*tmp) { if (*tmp == '[') { c++; } else if (*tmp == ']') { c--; if (tmp[1] && tmp[1] != '[') { skip_upload = 1; break; } } if (c < 0) { skip_upload = 1; break; } tmp++; }