乐趣区

挖洞姿势-深度聊聊PHP下的截断问题

0×01 起因

学弟有天在群里说起上传的 %00 截断的一些问题,就想起之前自己在这个问题踩过坑,想起了自己曾经的 flag 说要写文章,一直没写,现在来填坑了。

0×02 经过

源码理解:

//test.php<?php    include”1.txt000.jpg”;?> 
//1.txt<?php    echo’helloworld’;?>

上面的示例代码在 php 版本小于 5.3.4 的情况下回输出 helloworld。从 php 的内核执行过程来看,PHP 通过 php_execute_script 来执行 PHP 的脚本,这里选取部分有关代码,具体可以看这里:

在 第 10 行我们看到,他调用 zend_execute_scripts 来针对脚本进行解析,而这个函数是在 Zend/zend.c 里面,截取部分相关代码如下:

从 PHP 内核开来实际上是分为两块部分,一个是 compile 编译过程,另一个是 execute 执行过程。

第一部分:compile 编译过程

我们可以看到这里的代码逻辑通过 zend_compile_file 获取文件的内容,zend_compile_file 是一个函数指针,其声明在 /Zend/zend_compile.c 中

  ZEND_API zend_op_array (zend_compile_file)(zend_file_handle *file_handle, int type TSRMLS_DC);  

在引擎初始化的时候,会将 compile_file 函数的地址赋值给 zend_compile_file。

compile_file 函数定义在 /Zend/zend_language_scanner.l,截取部分相关代码。

简单总结一下上面部分代码的逻辑:

zend_compile_file 函数首先调用 open_file_for_scanning 去读取文件,然后通过第 17 行的 zendparse 去进行语法和词法解析。而 zendparse 是通过 lex_scan 去扫描出 token 并进行语法分析。

第二部分:execute 执行过程

zend_execute 也是一个函数指针,其声明在 /Zend/zend_execute.h 中。

ZEND_API externvoid(zend_execute)(zend_op_array op_array TSRMLS_DC);

在引擎初始化的时候,会将 execute 函数的地址赋值给 zend_execute。

而 execute 的定义在 /Zend/zend_vm_execute.h

根据我们的了解,zend_execute 通过 ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER 函数来进行 include 的实际处理,即包含要包含的文件。

对比修复代码找到漏洞触发点:

摘出部分修复代码:

我看下存在漏洞的调试运行结果:

修复代码的 Z_STRVAL_P(inc_filename) 即上图中的 val,即”1.txt”,strlen 取得长度为 5,而 Z_STRLEN_P(inc_filename) 即上图中的 len 即 10。这里实际上解析到的文件名是 1.txt。

不存在漏洞的调试运行结果:

一旦出现 %00 截断,include 的文件名经过 url 转码由”1.txt%00.jpg”变为”1.txt000.jpg”,进入 php 语法词法分析器解析后会将这个字符串解析成一个字符串,并使用 zend_scan_escape_string 进行字符串转码,如图,进入 zend_scan_escape_string 的内容为:

只要比较发现文件名的 strlen 长度和语法分析出来的长度不一样,就说明内部存在截断的字符,因此输出了打开文件失败的信息。

利用方式

划重点 PHP 版本低于 5.3.4

%00 截断有这么 2 种利用状况

1. 在 url 中加入 %00,如 http://xxxx/shell.php%00.jpg

2. 在 burpsuite 的 16 进制编辑工具将”shell.php .jpg”(带空格的)中间的空格由 20 改成 00

在 1 中,url 中的 %00(形如 %xx),web server 会把它当作十六进制处理,然后将该十六进制数据 hex(00)“翻译”成统一的 ascii 码值“NUL(null)”,实现了截断。

在 2 中,burpsuite 用 burp 自带的十六进制编辑工具将”shell.php .jpg”(中间有空格) 中的空格由 20 改成 00,如果 burp 中有二进制编辑工具。

延伸一下

其实关于截断相关问题,还有个很有趣的函数,iconv() 函数:

在了解 iconv() 函数漏洞之前,可能需要一点前置知识,

在 php 中,所有的字符都是二进制的串,PHP 本身并不认识任何编码,只是根据编码来显示内容。PHP 中的 chr() 函数从指定的 ASCII 值返回字符。ASCII 值可被指定为十进制值、八进制值或十六进制值。八进制值被定义为带前置 0,而十六进制值被定义为带前置 0x。

而在 php5.4 之前,iconv() 函数在转换编码的时候,遇到不合法的字符串的时候会将其截断。

<?php 
for($k=0;$k<=255;$k++) 
{
$a=’shell.php’.chr($k).”1.jpg”; 
echo’k:’$k.’   ‘.’$a:’.$a.’   ‘.’iconv(“UTF-8″,”gbk”,$a):’.iconv(“UTF-8″,”gbk”,$a).”n”; 

?>

通过 fuzz 发现,其中 iconv(“UTF-8″,”gbk”,$a) 或是 iconv(“UTF-8″,”gb2313″,$a) 都会在 chr(128) 到 chr(255) 之间截断,使结果为 shell.php

在 php5.4.0 版本的时候就不存在这个问题了。

实际例子 

关于我们刚刚说的 iconv() 截断的问题,其实 sitestar pro 就是个典型例子,我们看个例子:

漏洞触发点在 module/mod_tool.php 中的 img_create() 函数,截取部分代码如下:

这部分中有段代码吸引了我的注意力

if(!preg_match(‘/.(‘.PIC_ALLOW_EXT.’)$/i’, $file_info[“name”])) {
Notice::set(‘mod_marquee/msg’, __(‘File type error!’)); 
Content::redirect(Html::uriquery(‘mod_marquee’, ‘upload_img’)); 
        }

跟进一下这个 PIC_ALLOW_EXT 参数,发现其在数据库中写死了,只允许 gif|jpg|png|bmp 这类文件。即如果文件名最后不是 gif|jpg|png|bmp, 则提示文件类型错误。

所以这部分的正则表达式的功能应该是针对文件后缀名进行检查。

继续跟进这部分代码,有一串代码如下:

if (!$this->_savelinkimg($file_info)) {
            Notice::set(‘mod_marquee/msg’, __(‘Link image upload failed!’)); 
            Content::redirect(Html::uriquery(‘mod_marquee’, ‘upload_img’)); 
        }

这部分代码应该是进行文件保存的功能,这个有个核心函数_savelinkimg,跟进这个函数。

第二行问题出现了

  $struct_file[‘name’] = iconv(“UTF-8”, “gb2312”, $struct_file[‘name’]);

在 iconv 转码的过程中,utf->gb2312(其他部分编码之间转换同样存在这个问题) 会导致字符串被截断,如:$filename=”shell.php(hex).jpg”; (hex 为 0×80-0×99),经过 iconv 转码后会变成 $filename=”shell.php“;

所以,经过 iconv 后 $struct_file[‘name’]) 为 shell.php,于是我们利用这个逻辑缺陷可以成功的上传 shell.php(前提是上传的文件名为 shell.php{%80-%99}.jpg )。

0×03 结果

总结一下截断大概可以在以下情况适用

1.include(require);

2.file_get_contents;

3.file_exists;

4. 所有 url 中参数可以用 %00 控制。

0×04 参考文献

从源码级别了解 PHP %00 截断原理

潜伏在 PHP Manual 背后的特性及漏洞(看雪峰会议题)

作者:CanMengBlog
来源:CSDN
原文:https://blog.csdn.net/weixin_…
版权声明:本文为博主原创文章,转载请附上博文链接!

退出移动版