CodeIgniter框架内核设计缺陷可能导致任意代码执行

2016-03-11T00:00:00
ID SSV:96217
Type seebug
Reporter Root
Modified 2016-03-11T00:00:00

Description

简要描述:

为准备乌云深圳沙龙,准备几个0day做案例。 官方承认这个问题,说明会发布补丁,但不愿承认这是个『漏洞』……不过也无所谓,反正是不是都没美刀~

详细说明:

CI在加载模板的时候,会调用 $this->load->view('template_name', $data); 内核中,查看view函数源码: /system/core/Loader.php

public function view($view, $vars = array(), $return = FALSE) { return $this->_ci_load(array('_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return)); } ... protected function _ci_load($_ci_data) { // Set the default data variables foreach (array('_ci_view', '_ci_vars', '_ci_path', '_ci_return') as $_ci_val) { $$_ci_val = isset($_ci_data[$_ci_val]) ? $_ci_data[$_ci_val] : FALSE; } $file_exists = FALSE; // Set the path to the requested file if (is_string($_ci_path) && $_ci_path !== '') { $_ci_x = explode('/', $_ci_path); $_ci_file = end($_ci_x); } else { $_ci_ext = pathinfo($_ci_view, PATHINFO_EXTENSION); $_ci_file = ($_ci_ext === '') ? $_ci_view.'.php' : $_ci_view; foreach ($this->_ci_view_paths as $_ci_view_file => $cascade) { if (file_exists($_ci_view_file.$_ci_file)) { $_ci_path = $_ci_view_file.$_ci_file; $file_exists = TRUE; break; } if ( ! $cascade) { break; } } } if ( ! $file_exists && ! file_exists($_ci_path)) { show_error('Unable to load the requested file: '.$_ci_file); } // This allows anything loaded using $this->load (views, files, etc.) // to become accessible from within the Controller and Model functions. $_ci_CI =& get_instance(); foreach (get_object_vars($_ci_CI) as $_ci_key => $_ci_var) { if ( ! isset($this->$_ci_key)) { $this->$_ci_key =& $_ci_CI->$_ci_key; } } /* * Extract and cache variables * * You can either set variables using the dedicated $this->load->vars() * function or via the second parameter of this function. We'll merge * the two types and cache them so that views that are embedded within * other views can have access to these variables. */ if (is_array($_ci_vars)) { $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); } extract($this->_ci_cached_vars); /* * Buffer the output * * We buffer the output for two reasons: * 1. Speed. You get a significant speed boost. * 2. So that the final rendered template can be post-processed by * the output class. Why do we need post processing? For one thing, * in order to show the elapsed page load time. Unless we can * intercept the content right before it's sent to the browser and * then stop the timer it won't be accurate. */ ob_start(); // If the PHP installation does not support short tags we'll // do a little string replacement, changing the short tags // to standard PHP echo statements. if ( ! is_php('5.4') && ! ini_get('short_open_tag') && config_item('rewrite_short_tags') === TRUE) { echo eval('?>'.preg_replace('/;*\s*\?>/', '; ?>', str_replace('<?=', '<?php echo ', file_get_contents($_ci_path)))); } else { include($_ci_path); // include() vs include_once() allows for multiple views with the same name } log_message('info', 'File loaded: '.$_ci_path); // Return the file data if requested if ($_ci_return === TRUE) { $buffer = ob_get_contents(); [@ob_end_clean](/ob_end_clean)(); return $buffer; } /* * Flush the buffer... or buff the flusher? * * In order to permit views to be nested within * other views, we need to flush the content back out whenever * we are beyond the first level of output buffering so that * it can be seen and included properly by the first included * template and any subsequent ones. Oy! */ if (ob_get_level() > $this->_ci_ob_level + 1) { ob_end_flush(); } else { $_ci_CI->output->append_output(ob_get_contents()); [@ob_end_clean](/ob_end_clean)(); } return $this; }

且看这一段:

if (is_array($_ci_vars)) { $this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars); } extract($this->_ci_cached_vars);

这个extract将导致变量覆盖漏洞。 $this->_ci_cached_vars是来自$_ci_vars,而$_ci_vars是来自用户传给view方法的第二个参数。(正常情况下是开发者传给模板的变量) 而我们看到extract后面:

extract($this->_ci_cached_vars); ob_start(); // If the PHP installation does not support short tags we'll // do a little string replacement, changing the short tags // to standard PHP echo statements. if ( ! is_php('5.4') && ! ini_get('short_open_tag') && config_item('rewrite_short_tags') === TRUE) { echo eval('?>'.preg_replace('/;*\s*\?>/', '; ?>', str_replace('<?=', '<?php echo ', file_get_contents($_ci_path)))); } else { include($_ci_path); // include() vs include_once() allows for multiple views with the same name }

include($_ci_path),$_ci_path是模板地址,因为之前的变量覆盖,将会导致任意文件包含漏洞,进而getshell。 所以,只要我们可以控制view的第二个参数的『键值』,传入 _ci_path=file:///etc/passwd ,在被extract后覆盖原来的模板地址,将可以包含/etc/passwd。 这个漏洞和 http://.../bugs/wooyun-2014-051906 有点类似,就是在assign(CI里叫$this->load->vars或是$this->load->view)的时候传入数组导致的。

漏洞证明:

如下Controller将可导致漏洞:

``` <?php defined('BASEPATH') OR exit('No direct script access allowed'); class Welcome extends CI_Controller {

public function index()
{
    $data = $this-&gt;input-&gt;post();
    $this-&gt;load-&gt;view('welcome_message', $data);
}

} ```

<img src="https://images.seebug.org/upload/201603/10234508a539fe133c71549a3e3e065b4e3beeb6.png" alt="QQ20160310-6@2x.png" width="600" onerror="javascript:errimg(this);">

这个也类似:

public function index() { $data = $this-&gt;input-&gt;post('info'); $this-&gt;load-&gt;vars($data); $this-&gt;load-&gt;view('welcome_message'); }

<img src="https://images.seebug.org/upload/201603/10234717fa8031f2b29d01ec4b9c2a50800a2f26.png" alt="QQ20160310-7@2x.png" width="600" onerror="javascript:errimg(this);">

当开启了远程文件包含的情况下,也可以直接包含php://input

<img src="https://images.seebug.org/upload/201603/10234835f4897a08e6395be8bf52f95d12344c97.png" alt="QQ20160310-8@2x.png" width="600" onerror="javascript:errimg(this);">