High
CVE-2023-25135
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below:
Add References:
CVE-2023-25135
MITRE ATT&CK
Collection
Command and Control
Credential Access
Defense Evasion
Discovery
Execution
Exfiltration
Impact
Initial Access
Lateral Movement
Persistence
Privilege Escalation
Topic Tags
Description
vBulletin before 5.6.9 PL1 allows an unauthenticated remote attacker to execute arbitrary code via a crafted HTTP request that triggers deserialization. This occurs because verify_serialized checks that a value is serialized by calling unserialize and then checking for errors. The fixed versions are 5.6.7 PL1, 5.6.8 PL1, and 5.6.9 PL1.
Add Assessment
Ratings
-
Attacker ValueHigh
-
ExploitabilityVery High
Technical Analysis
Overview
This is a pretty cool vulnerability in vBulletin version 5.6.9, 5.6.8 and 5.6.7 prior to PL1 for each respective version. The vulnerability occurs due to improper handing of non-scalar data in vBulletin’s Object-Relational Mapper (ORM), which leads to deserialization of user input without appropriate validation.
There is a great writeup on this bug at https://www.ambionics.io/blog/vbulletin-unserializable-but-unreachable but I’ll try summarize some of the important points here; I’d recommend reading the writeup though as its fairly short and to the point; something that is rare for a technical post of this nature.
The gist of what is going on here is that vBulletin stores non-scalar data in its database in a serialized format using functions like serialize()
, and then will call unserialize()
to unserialize that data when it needs to retrieve it from the database and use it. Each item to be serialized or deserialized will declare a 3 field structure stating its class, whether its a required field for that class, and a function to verify that the value is correct and modify it if necessary.
The researchers found that the searchprefs
property of the vB_DataManager_User
class is verified by the verify_serialized()
function and is of type vB_Cleaner::TYPE_NOCLEAN
, meaning it has no type restricitons. Looking at verify_serialized()
we see the following code:
function verify_serialized(&$data) { if ($data === '') { $data = serialize(array()); return true; } else { if (!is_array($data)) { $data = unserialize($data); // <--------- PROBLEM HERE!!!! if ($data === false) { return false; } } $data = serialize($data); } return true; }
The problem in this code is that to verify that the data is actually serialized, we take the untrusted user data in the $data
variable and just check that its not an array or a blank string, and if we pass this criteria then we blindly pass it into an unserialize()
call, before checking that unserialize()
didn’t return false
. If unserialize()
didn’t return false
then its assumed everything went okay and we reserialize the data using serialize()
, save that into $data
and return true
.
The issue here is that no validation is actually done to ensure the serialized data is using expected classes and isn’t just a malicious serialized object. Additionally as we will see later on, just checking that unserialize()
doesn’t return false
isn’t sufficient; we should also be checking that the object returned is of the expected type.
Whilst the researchers tried to exploit this vulnerability using their normal methods of gadget chains and abusing the applications code, they found this was an issue as vBulletin has a lot of vB_Trait_NoSerialize
traits on objects, making them impossible to deserialize without raising an exception. Additionally the one library that they did find some gadget chains in isn’t loaded by default, since the googlelogin
package isn’t enabled by default in vBulletin despite it being installed, so they couldn’t use they normal Monolog chain without a bit of tweaking.
What tweaking you may ask? Well it turns out that unserialize()
has some interesting behavior. If the class name it receives isn’t valid, then it will return a __PHP_Incomplete_Class
object. This will not cause the above code to fail though since it isn’t a false
value. Noticing this, the researchers then took a look into the autoloader behavior in vBulletin. The code for the autoloader can be seen below:
spl_autoload_register(array('vB', 'autoload')); class vB { public static function autoload($classname, $load_map = false, $check_file = true) { $fclassname = strtolower($classname); // [0] $segments = explode('_', $fclassname); // [0-1] switch($segments[0]) // [1] { case 'vb': $vbPath = true; $filename = VB_PATH; // ./vb/ break; case 'vb5': $vbPath = true; $filename = VB5_PATH; // ./vb5/ break; default: $vbPath = false; $filename = VB_PKG_PATH; // ./packages/ break; } if (sizeof($segments) > ($vbPath ? 2 : 1)) { $filename .= implode('/', array_slice($segments, ($vbPath ? 1 : 0), -1)) . '/'; // [2] } $filename .= array_pop($segments) . '.php'; // [3] if(file_exists($filename)) require($filename); // [4] } }
They noticed that vBulletin has an autoloader at vB::autoload()
which will be called whenever an unknown class is attempted to be accessed during deserialization. This code will take in a classname, split it on the _
character, check what the first part of the path contains and will append the appropriate directory name to the beginning of the path, and then takes the rest of the path minus the last segment, and squishes it together, using /
to separate each part of the final path name. Finally it takes the last item of the split and uses this as the filename to be accessed, appending .php
to the end of it before adding it to the final path name. If a file exists at this resulting path, it is then loaded using a require()
statement. No validation is done to see if this is an expected file path or similar though, so as long as the file exists in one of the three expected directories (./vb/
, ./vb5/
, or ./packages/
), it will be possible to load it via require()
with this code.
The researchers then realized that they could abuse this to load the autoloader of the Monolog library that they had a gadget chain in, such that the autoloader would be loaded into memory allowing them to use any classes within Monolog since any unknown ones will now be loaded by the Monolog autoloader. Keep in mind this is possible because the plugin is normally disabled but still installed, so all that’s needed to use it is for some code to load some of its initializers into memory so that PHP knows where to find the classes in the deserialization gadget chain. With the Monolog autoloader now in place to help load any Monolog classes that the gadget chain may need that aren’t already in memory, the researchers now had everything they needed to make their Monolog deserialization gadget chain work again.
PoC Code
The final PoC can be seen over at https://github.com/ambionics/vbulletin-exploits/blob/main/vbulletin-rce-cve-2023-25135.py and looks roughly like the following code:
POST /ajax/api/user/save HTTP/1.1 Host: 172.17.0.2 Content-Type: application/x-www-form-urlencoded Content-Length: 666 securitytoken=guest &options= &adminoptions= &userfield= &userid=0 &user[email]=pown@pown.net &user[username]=toto &password=password &user[password]=password &user[searchprefs]=a:2:{i:0;O:27:"googlelogin_vendor_autoload":0:{}i:1;O:32:"Monolog\\Handler\\SyslogUdpHandler":1:s:9:"\x00*\x00socket";O:29:"Monolog\\Handler\\BufferHandler":7:{s:10:"\x00*\x00handler";r:4;s:13:"\x00*\x00bufferSize";i:-1;s:9:"\x00*\x00buffer";a:1:{i:0;a:2:{i:0;s:[LEN]:"[COMMAND]";s:5:"level";N;}}s:8:"\x00*\x00level";N;s:14:"\x00*\x00initialized";b:1;s:14:"\x00*\x00bufferLimit";i:-1;s:13:"\x00*\x00processors";a:2:i:0;s:7:"current";i:1;s:6:"system";}}}}
Final Notes
I was unfortunately unable to determine what user the code will ultimately end up executing as; I presume it would be the user that vBulletin is running as though. However the fact that this is a unauthenticated deserialization bug that can be remotely exploited with no prior knowledge of the target makes it a pretty severe issue. The one saving grace is that it appears this bug may have been limited to only three editions of vBulletin, however if your running any of these versions its highly advisable to upgrade and to also perform a check to see if you have potentially been compromised by this vulnerability. I expect to see more widespread exploitation of this bug in the future given its ease of exploitation.
Would you also like to delete your Exploited in the Wild Report?
Delete Assessment Only Delete Assessment and Exploited in the Wild ReportCVSS V3 Severity and Metrics
General Information
Vendors
- vbulletin
Products
- vbulletin 5.6.7,
- vbulletin 5.6.8,
- vbulletin 5.6.9
References
Additional Info
Technical Analysis
Report as Emergent Threat Response
Report as Exploited in the Wild
CVE ID
AttackerKB requires a CVE ID in order to pull vulnerability data and references from the CVE list and the National Vulnerability Database. If available, please supply below: