安全矩阵

 找回密码
 立即注册
搜索
查看: 2461|回复: 0

PHP文件上传流式表示WAF跨越

[复制链接]

145

主题

192

帖子

817

积分

高级会员

Rank: 4

积分
817
发表于 2022-7-7 10:09:15 | 显示全部楼层 |阅读模式
本帖最后由 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 示例代码
    1. <?php var_dump ( $_FILES ); ?>
    复制代码


  •         文件解析的简要流程如下
编辑
技巧前向截断
  •         \和/名处理文件名进行类似向截断info.txt/info.php的文件经php后会info.php
编辑

  •         调用栈如下

编辑

  •         其中有一段注释如下,其本意是为了解决IE上传文件时所有路径名的问题
    1.      /* 只有在* 它是有效路径分隔符的情况下,win32 系统才需要在技术上进行 \ 检查。然而,IE 总是     在用户的文件系统上发送文件的完整路径,这意味着除非     用户执行 basename(),否则他们会得到一个虚假的文件名。在 IE 的用户群将     * 降至零或问题得到修复之前,此代码必须为所有系统保持启用状态。*/
    复制代码


  •         关键函数在php_ap_basename,该函数会寻找\和/显示最后出现的位置,并从该位置截断断字,导致造成了前向的截断
  1. static  char  * php_ap_basename ( const  zend_encoding  * encoding ,  char  * path ) {
  2.     char  * s  =  strrchr ( path ,  '\\' );
  3.     char  * s2  =  strrchr (路径,  '/' );
  4.     if  ( s  &&  s2 )  {
  5.         if  ( s  >  s2 )  {
  6.             ++ s ;
  7.         } 其他 {
  8.             s  =  ++ s2 ;
  9.         }
  10.         返回 s ;
  11.     }  else  if  ( s )  {
  12.         return  ++ s ;
  13.     }  else  if  ( s2 )  {
  14.         return  ++ s2 ;
  15.     }
  16.     返回 路径;}
复制代码


后向截断
  •         00的名字处理文件名进行后向截断类似info.php(00)xxx的文件经php之后会变成info.php

编辑

  •         在解析header的时候,只是对内存进行了拷贝,内存视图变成了

编辑

  •         解析了中的时间长度,即表示表示,在filename使用时,内存中的字符串结束了,导致造成的断断续续strlenfilenamestrlen\000
    1. 头文件:#include  < string . h >strlen ()函数用来计算字符串的长度,其原型为:
    2.     unsigned  int  strlen  ( char  * s );【参数说明】s为指定的字符串。strlen ()用于计算指定的字符串的 长度,不包括结束字符“ \0 ” 。
    复制代码



  •         
    的,00可以对$_POST断变量名也可以进行截断,对$GET,$_COOKIE等变量名00会引发400错误
  •         
    示例代码
            
    1. <?php var_dump ( $_POST );
    复制代码

    •                 正常请求
            
编辑


  •         在名称添加后,可以看到$_POST变量中00postxxxpost
编辑

  •         在$_POST变量值添加00不影响,但会中的长度则增加1

编辑
文件名的有意的\会被触发
  •         服装展示
编辑

  •         php_ap_getword中的关键函数,当出现\+quote这样的两个事件时,会触发\只取quote的值
  1. static  char  * php_ap_getword ( const  zend_encoding  * encoding ,  char  ** line ,  char  stop ) {
  2.     char  * pos  =  * line ,  quote ;
  3.     字符 * res ;
  4.     while  ( * pos  &&  * pos  !=  stop )  {
  5.         if  (( quote  =  * pos )  ==  '"'  ||  quote  ==  '\'' )  {
  6.             ++ pos ;
  7.             while  ( * pos  &&  * pos  !=  quote )  {
  8.                 // 这里会发射 \ 字符
  9.                 if  ( * pos  ==  '\\'  &&  pos [ 1 ]  &&  pos [ 1]  == 报价)  {
  10.                     pos  +=  2 ;
  11.                 } 其他 {
  12.                     ++位置;
  13.                 }
  14.             }
  15.             if  ( * pos )  {
  16.                 ++ pos ;
  17.             }
  18.         } 其他 ++位置;
  19.     }
  20.     if  ( * pos  ==  '\0' )  {
  21.         res  =  estrdup ( * line );
  22.         * line  +=  strlen ( * line );
  23.         返回 资源;
  24.     }
  25.     res  =  estrndup ( * line ,  pos  -  * line );
  26.     while  ( * pos  ==  stop )  {
  27.         ++ pos ;
  28.     }
  29.     *线 = 位置;
  30.     返回 资源;}
复制代码


;可以影响文件名解析的结果
  •         类似filename=info.php;.txt;这样的字符串经过PHP处理后,会变成info.php,注意filename的值没有用双引号失败,双引号包裹会导致

编辑

  •         在解析时,Content-Disposition会先进行分;词,使用=进行分词。所以,然后,类似的字符串第一次分filename=info.php;.txt;词后的结果是这样的filename=info.php/txtfilenameinfo.php
  1. SAPI_API  SAPI_POST_HANDLER_FUNC ( rfc1867_post_handler )  /* {{{  */ {
  2.     //...
  3.     // 使用 ; 进行分词
  4.     while  ( * cd  &&  ( pair  =  getword ( mbuff -> input_encoding ,  & cd ,  ';' )))
  5.     {
  6.         //...
  7.         // 按照 = 进行解析
  8.         if  ( strchr ( pair ,  '=' ))  {
  9.          // ...   
  10.         }
  11.         // ...
  12.     }
  13.     // ... }
复制代码


双写filename
  •         php解析Content-Disposition时,按照从前的顺序,结果有不同的变量名,如果之后进行值的覆盖,关键代码

编辑
失败的上传 - 1
  •         filename首00字符为时,上传会失败。如下所示,在filename首字符前插入00,导致上传失败
编辑

  •         关键码
    1.     if  (文件名[ 0 ]  ==  '\0' )  { #if DEBUG_FILE_UPLOAD
    2.                     sapi_module . sapi_error ( E_NOTICE ,  "没有文件上传" ); #endif
    3.                     cancel_upload  =  UPLOAD_ERROR_D ;
    4.                 }
    复制代码



失败的上传 - 2
  •         当name首]字符为时,则导致上传失败,如下所示

编辑

  •         关键代码*tmp == ']'当时,skip_upload = 1促成了处理,当时,加载了上传的
  1. while  ( * tmp )  {

  2.     if  ( * tmp  ==  '[' )  {
  3. c ++ ;
  4.    
  5. }  else  if  ( * tmp  ==  ']' )  {

  6.         c -- ;

  7.         if  ( tmp [ 1 ]  &&  tmp [ 1 ]  !=  '[' )  {

  8.             skip_upload  =  1 ;

  9.             休息;

  10.         }

  11.     }

  12.     如果 ( c  <  0)  {

  13.         跳过上传 =  1 ;

  14.         休息;

  15.     }
  16.    
  17. tmp ++ ;
  18. }
复制代码


总结
  •         实战灵活时,以上各种技巧可以组合
  •         以上的技巧基于y4tacker的文章以及php源码得来,相信深读源码的话,会有更多的技巧
参考

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 20:45 , Processed in 0.013305 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表