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

来源:互联网 发布:xl player for mac 编辑:程序博客网 时间:2024/06/02 18:42

一:漏洞简介

    PHP的00截断是5.2.x版本的一个漏洞,当用户输入的url参数包含%00经过浏览器自动转码后截断后面字符。

二:漏洞示例代码

    例如url输入的文件名1.txt%00.jpg经过url转码后会变为1.txt\000.jpg,测试文件1.php如下
<?php    include "1.txt\000.jpg";?>
    测试文件1.txt如下
<?php    echo 'fireXXX';?>
    php5.2.x版本解析1.php时,会将1.txt\000.jpg解释为1.txt

三:漏洞源码分析

    php5.2.17代码分析:
    调用php去解析1.php文件。
    主要的执行流程在Zend/zend.c的zend_execute_scripts函数中。该函数首先通过zend_compile_file获取1.php文件的内容,然后调用zend_execute解析读取到的文件内容。
    zend_compile_file函数首先调用open_file_for_scanning去读取文件,然后通过zendparse去进行语法和词法解析,而zendparse是通过lex_scan去扫描出token并进行语法分析。可以通过调试器观察到include的文件名参数经过lex_scan后的数据:
    
    因此实际上解析到的文件名是1.txt,长度为10(因为lex_scan扫描到了1.txt\000.jpg实际是10个字符)
    zend_execute通过ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER函数来进行include的实际处理,即包含要包含的文件。首先通过compile_filename来读取要读取文件的内容,然后继续调用zend_execute去执行要包含文件的代码。针对include "1.txt";即读取1.txt的内容,然后执行echo 'fireXXX';代码。

四:修复代码分析

    可以从php5.3.24代码中找到修复的方案。修复的代码位于ZEND_INCLUDE_OR_EVAL_SPEC_CONST_HANDLER函数的开始处:
        if (Z_LVAL(opline->op2.u.constant) != ZEND_EVAL && strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename)) {if (Z_LVAL(opline->op2.u.constant)==ZEND_INCLUDE_ONCE || Z_LVAL(opline->op2.u.constant)==ZEND_INCLUDE) {zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, Z_STRVAL_P(inc_filename) TSRMLS_CC);} else {zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, Z_STRVAL_P(inc_filename) TSRMLS_CC);}}
    代码
strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename)
    中,Z_STRVAL_P(inc_filename)即上图中的val,即"1.txt",strlen取得长度为5,而Z_STRLEN_P(inc_filename)即上图中的len即10。
    一旦出现%00截断,include的文件名经过url转码由"1.txt%00.jpg"变为"1.txt\000.jpg",进入php语法词法分析器解析后会将这个字符串解析成一个字符串,并使用zend_scan_escape_string进行字符串转码,如图,进入zend_scan_escape_string的内容为:

    中间的\\000还被解析为4个字符,转码中会将他当作八进制数据转成一个字符\0,因此最终1.txt\000.jpg长度是10。
    只要比较发现文件名的strlen长度和语法分析出来的长度不一样,就说明内部存在截断的字符,因此输出了打开文件失败的信息。