新闻中心
轻量级博客系统Typecho install.php 反序列化导致任意代码执行
作者 / 无忧主机 时间 2017-12-07 21:32:35
Typecho是一个轻量级的开源博客系统,近日有网友爆出该系统的安装文件install.php存在反序列化可控点,能导致任意代码执行。 快速修补方法: 删除install.php及install目录 漏洞细节: 在 install.php 文件的 288-235行
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
$db->addServer($config, Typecho_Db::READ | Typecho_Db::WRITE);
Typecho_Db::set($db);
第230行获取cookie中的__typecho_config值base64解码,然后反序列化。想要执行,只需isset($_GET['finish'])并且__typecho_config存在值。
反序列化后232行把$config['adapter']和$config['prefix']传入Typecho_Db进行实例化。然后调用Typecho_Db的addServer方法,调用Typecho_Config实例化工厂函数对Typecho_Config类进行实例化。
跟踪 Typecho_Db 类,构造方法,Db.php 114-135行
public function __construct($adapterName, $prefix = 'typecho_')
{
/** 获取适配器名称 */
$this->_adapterName = $adapterName;
/** 数据库适配器 */
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
if (!call_user_func(array($adapterName, 'isAvailable'))) {
throw new Typecho_Db_Exception("Adapter {$adapterName} is not available");
}
$this->_prefix = $prefix;
/** 初始化内部变量 */
$this->_pool = array();
$this->_connectedPool = array();
$this->_config = array();
//实例化适配器对象
$this->_adapter = new $adapterName();
}
发现第120行对传入的$adapterName进行了字符串的拼接操作。那么如果$adapterName传入的是个实例化对象,就会触发该对象的__toString()魔术方法。
全局搜索__toString():发现三处,跟进发现 Typecho_Query类的__toString()魔术方法,Query.php 488-519行:
public function __toString()
{
switch ($this->_sqlPreBuild['action']) {
case Typecho_Db::SELECT:
return $this->_adapter->parseSelect($this->_sqlPreBuild);
case Typecho_Db::INSERT:
return 'INSERT INTO '
. $this->_sqlPreBuild['table']
. '(' . implode(' , ', array_keys($this->_sqlPreBuild['rows'])) . ')'
. ' VALUES '
. '(' . implode(' , ', array_values($this->_sqlPreBuild['rows'])) . ')'
. $this->_sqlPreBuild['limit'];
case Typecho_Db::DELETE:
return 'DELETE FROM '
. $this->_sqlPreBuild['table']
. $this->_sqlPreBuild['where'];
case Typecho_Db::UPDATE:
$columns = array();
if (isset($this->_sqlPreBuild['rows'])) {
foreach ($this->_sqlPreBuild['rows'] as $key => $val) {
$columns[] = "$key = $val";
}
}
return 'UPDATE '
. $this->_sqlPreBuild['table']
. ' SET ' . implode(' , ', $columns)
. $this->_sqlPreBuild['where'];
default:
return NULL;
}
}
第492行$this->_adapter调用parseSelect()方法,如果该实例化对象在对象上下文中调用不可访问的方法时触发,便会触发__call()魔术方法。
继续跟进发现Typecho_Plugin类的__call()魔术方法存在回调函数,Plugin.php 479-494行:
public function __call($component, $args)
{
$component = $this->_handle . ':' . $component;
$last = count($args);
$args[$last] = $last > 0 ? $args[0] : false;
if (isset(self::$_plugins['handles'][$component])) {
$args[$last] = NULL;
$this->_signal = true;
foreach (self::$_plugins['handles'][$component] as $callback) {
$args[$last] = call_user_func_array($callback, $args);
}
}
return $args[$last];
}
$component是调用失败的方法名,$args是调用时的参数。均可控,但是根据上文,$args必须存在array('action'=>'SELECT'),然后加上我们构造的payload,最少是个长度为2的数组,但是483行又给数组加了一个长度,导致$args长度至少为3,那么call_user_func_array()便无法正常执行。所以此路就不通了。
继续跟进Typecho_Feed类的__toString()魔术方法,Feed.php 340-360行
} else if (self::ATOM1 == $this->_type) {
$result .= '_baseUrl . '"
>' . self::EOL;
$content = '';
$lastUpdate = 0;
foreach ($this->_items as $item) {
$content .= '' . self::EOL;
$content .= '<![CDATA[' . $item['title'] . ']]>' . self::EOL;
$content .= '' . self::EOL;
$content .= '' . $item['link'] . '' . self::EOL;
$content .= '' . $this->dateFormat($item['date']) . '' . self::EOL;
$content .= '' . $this->dateFormat($item['date']) . '' . self::EOL;
$content .= '
' . $item['author']->screenName . '
' . $item['author']->url . '
' . self::EOL;
第358行$item['author']调用screenName属性,如果该实例化对象用于从不可访问的属性读取数据,便会触发__get()魔术方法。
全局搜索__get():
发现了几处,最终确定Typecho_Request类存在可利用的地方
__get()魔术方法调用get()方法,Request.php 293-309行:
public function get($key, $default = NULL)
{
switch (true) {
case isset($this->_params[$key]):
$value = $this->_params[$key];
break;
case isset(self::$_httpParams[$key]):
$value = self::$_httpParams[$key];
break;
default:
$value = $default;
break;
}
$value = !is_array($value) && strlen($value) > 0 ? $value : $default;
return $this->_applyFilter($value);
}
308行调用_applyFilter()方法,传入的$value是$this->_params[$key]的值,$key就是screenName。
跟进_applyFilter(),Request.php 159-171行:
private function _applyFilter($value)
{
if ($this->_filter) {
foreach ($this->_filter as $filter) {
$value = is_array($value) ? array_map($filter, $value) :
call_user_func($filter, $value);
}
$this->_filter = array();
}
return $value;
}
第163行array_map和164行call_user_func均可造成任意代码执行。
本文地址:https://www.51php.com/safety/25764.html
上一篇: ECTouch幻灯片设置
下一篇: ECTouch如何去除版权信息


