|
本帖最后由 littlebird 于 2022-7-7 10:13 编辑
PHP文件上传流式表示WAF跨越
简介- PHP文件上传实现规范为RFC1867
- 实验环境为PHP 7.3.4 + nginx 1.20.1,关于上传部分的相关源码在github,PHP解析multipart/form-data请求体的入口函数在SAPI_POST_HANDLER_FUNC
- PHP 调试环境参考
- PHP 示例代码
- <?php var_dump ( $_FILES ); ?>
复制代码
编辑
技巧前向截断- \和/名处理文件名进行类似向截断info.txt/info.php的文件经php后会info.php
编辑
编辑
- 其中有一段注释如下,其本意是为了解决IE上传文件时所有路径名的问题
- /* 只有在* 它是有效路径分隔符的情况下,win32 系统才需要在技术上进行 \ 检查。然而,IE 总是 在用户的文件系统上发送文件的完整路径,这意味着除非 用户执行 basename(),否则他们会得到一个虚假的文件名。在 IE 的用户群将 * 降至零或问题得到修复之前,此代码必须为所有系统保持启用状态。*/
复制代码
- 关键函数在php_ap_basename,该函数会寻找\和/显示最后出现的位置,并从该位置截断断字,导致造成了前向的截断
- static char * php_ap_basename ( const zend_encoding * encoding , char * path ) {
- char * s = strrchr ( path , '\\' );
- char * s2 = strrchr (路径, '/' );
- if ( s && s2 ) {
- if ( s > s2 ) {
- ++ s ;
- } 其他 {
- s = ++ s2 ;
- }
- 返回 s ;
- } else if ( s ) {
- return ++ s ;
- } else if ( s2 ) {
- return ++ s2 ;
- }
- 返回 路径;}
复制代码
后向截断- 00的名字处理文件名进行后向截断类似info.php(00)xxx的文件经php之后会变成info.php
编辑
- 在解析header的时候,只是对内存进行了拷贝,内存视图变成了
编辑
- 解析了中的时间长度,即表示表示,在filename使用时,内存中的字符串结束了,导致造成的断断续续strlenfilenamestrlen\000
- 头文件:#include < string . h >strlen ()函数用来计算字符串的长度,其原型为:
- unsigned int strlen ( char * s );【参数说明】s为指定的字符串。strlen ()用于计算指定的字符串的 长度,不包括结束字符“ \0 ” 。
复制代码
编辑
- 在名称添加后,可以看到$_POST变量中00postxxxpost
编辑
- 在$_POST变量值添加00不影响,但会中的长度则增加1
编辑
文件名的有意的\会被触发 编辑
- php_ap_getword中的关键函数,当出现\+quote这样的两个事件时,会触发\只取quote的值
- static char * php_ap_getword ( const zend_encoding * encoding , char ** line , char stop ) {
- char * pos = * line , quote ;
- 字符 * res ;
- while ( * pos && * pos != stop ) {
- if (( quote = * pos ) == '"' || quote == '\'' ) {
- ++ pos ;
- while ( * pos && * pos != quote ) {
- // 这里会发射 \ 字符
- if ( * pos == '\\' && pos [ 1 ] && pos [ 1] == 报价) {
- pos += 2 ;
- } 其他 {
- ++位置;
- }
- }
- if ( * pos ) {
- ++ pos ;
- }
- } 其他 ++位置;
- }
- if ( * pos == '\0' ) {
- res = estrdup ( * line );
- * line += strlen ( * line );
- 返回 资源;
- }
- res = estrndup ( * line , pos - * line );
- while ( * pos == stop ) {
- ++ pos ;
- }
- *线 = 位置;
- 返回 资源;}
复制代码
;可以影响文件名解析的结果- 类似filename=info.php;.txt;这样的字符串经过PHP处理后,会变成info.php,注意filename的值没有用双引号失败,双引号包裹会导致
编辑
- 在解析时,Content-Disposition会先进行分;词,使用=进行分词。所以,然后,类似的字符串第一次分filename=info.php;.txt;词后的结果是这样的filename=info.php/txtfilenameinfo.php
- SAPI_API SAPI_POST_HANDLER_FUNC ( rfc1867_post_handler ) /* {{{ */ {
- //...
- // 使用 ; 进行分词
- while ( * cd && ( pair = getword ( mbuff -> input_encoding , & cd , ';' )))
- {
- //...
- // 按照 = 进行解析
- if ( strchr ( pair , '=' )) {
- // ...
- }
- // ...
- }
- // ... }
复制代码
双写filename- php解析Content-Disposition时,按照从前的顺序,结果有不同的变量名,如果之后进行值的覆盖,关键代码
编辑
失败的上传 - 1- filename首00字符为时,上传会失败。如下所示,在filename首字符前插入00,导致上传失败
编辑
- 关键码
- if (文件名[ 0 ] == '\0' ) { #if DEBUG_FILE_UPLOAD
- sapi_module . sapi_error ( E_NOTICE , "没有文件上传" ); #endif
- cancel_upload = UPLOAD_ERROR_D ;
- }
复制代码
失败的上传 - 2
编辑
- 关键代码*tmp == ']'当时,skip_upload = 1促成了处理,当时,加载了上传的
- while ( * tmp ) {
- if ( * tmp == '[' ) {
- c ++ ;
-
- } else if ( * tmp == ']' ) {
- c -- ;
- if ( tmp [ 1 ] && tmp [ 1 ] != '[' ) {
- skip_upload = 1 ;
- 休息;
- }
- }
- 如果 ( c < 0) {
- 跳过上传 = 1 ;
- 休息;
- }
-
- tmp ++ ;
- }
复制代码
总结- 实战灵活时,以上各种技巧可以组合
- 以上的技巧基于y4tacker的文章以及php源码得来,相信深读源码的话,会有更多的技巧
参考
|
|