Yet Another Joomla PHP Object Injection Vulnerability

Last week I have disclosed KIS-2013-04, another PHP Object Injection vulnerability which affects the Joomla CMS. I had initially reported this vulnerability to the Joomla Security Strike Team in December last year, within an e-mail reply about the KIS-2013-03 vulnerability: “Furthermore, I would suggest you to investigate other potentially vulnerable unserialize() calls, for example the plgSystemRemember::onAfterInitialise() method uses the unserialize() function with user input passed through cookies, but I’m not sure it may be exploitable, due to the encryption system”.

In early February have been released new Joomla updates, which included corrections for three security issues, among which the “highlighter” PHP Object Injection vulnerability. However, this updates have not solved the “remember me” vulnerability, for this reason, on February 12, I sent another e-mail to the Joomla Security Strike Team: “I noticed that the unserialize() call is still present within the plgSystemRemember::onAfterInitialise() method in versions 3.0.3 and 2.5.9, so I was looking a bit deeper, in order to understand if I was wrong when I said «I’m not sure it may be exploitable, due to the encryption system», and sadly I was wrong!”. The day after I sent them a proof of concept code able to exploit the unserialize() call located within the “remember me” plugin. Let’s have a look at the vulnerable code:

21	public function onAfterInitialise()
22	{
23		$app = JFactory::getApplication();
25		// No remember me for admin
26		if ($app->isAdmin())
27		{
28			return;
29		}
31		$user = JFactory::getUser();
32		if ($user->get('guest'))
33		{
34			$hash = JApplication::getHash('JLOGIN_REMEMBER');
36			if ($str = JRequest::getString($hash, '', 'cookie', JREQUEST_ALLOWRAW | JREQUEST_NOTRIM))
37			{
38				// Create the encryption key, apply extra hardening using the user agent string.
39				// Since we're decoding, no UA validity check is required.
40				$privateKey = JApplication::getHash(@$_SERVER['HTTP_USER_AGENT']);
42				$key = new JCryptKey('simple', $privateKey, $privateKey);
43				$crypt = new JCrypt(new JCryptCipherSimple, $key);
44				$str = $crypt->decrypt($str);
45				$cookieData = @unserialize($str);

The vulnerability exists because user-supplied input passed through the “remember me” cookie is not properly sanitized before being used in a call to the unserialize() function at line 45. Initially I was doubtful about the exploitability of this vulnerability, because the plugin uses an encryption method to read the cookie value, but after a brief analysis I found a way which may allow an attacker to bypass this encryption system. The cookie value is read at line 36, calling the JRequest::getString() method and passing to it the $hash variable as first parameter. That’s the name of the input variable which is being fetched from the $_COOKIE array, and then it’s stored into the $str variable, which is later passed to the unserialize() function after being decrypted using a JCrypt object. So, there are basically two information items which an attacker would need to know in order to exploit this vulnerability, the first is the “secure hash” used to read the cookie vlaue:

34			$hash = JApplication::getHash('JLOGIN_REMEMBER');

And the second one is the private key used to encrypt/decrypt the cookie value:

40			$privateKey = JApplication::getHash(@$_SERVER['HTTP_USER_AGENT']);

But what if an attacker uses the string “JLOGIN_REMEMBER” as user-agent in the HTTP request? In this case the encryption key and the “secure hash” will have the same value. Therefore, the only thing an attacker needs to know is the value returned by calling the JApplication::getHash() method with “JLOGIN_REMEMBER” as parameter, and that’s the reason why a successful exploitation of this vulnerability requires authentication, because that value can be read only after being succesfully logged in to the system, it’s in fact returned to the user by the JApplication::login() method:

665				// Set the remember me cookie if enabled.
666				if (isset($options['remember']) && $options['remember'])
667				{
668					// Create the encryption key, apply extra hardening using the user agent string.
669					$privateKey = self::getHash(@$_SERVER['HTTP_USER_AGENT']);
671					$key = new JCryptKey('simple', $privateKey, $privateKey);
672					$crypt = new JCrypt(new JCryptCipherSimple, $key);
673					$rcookie = $crypt->encrypt(serialize($credentials));
674					$lifetime = time() + 365 * 24 * 60 * 60;
676					// Use domain and path set in config for cookie if it exists.
677					$cookie_domain = $this->getCfg('cookie_domain', '');
678					$cookie_path = $this->getCfg('cookie_path', '/');
679					setcookie(self::getHash('JLOGIN_REMEMBER'), $rcookie, $lifetime, $cookie_path, $cookie_domain);
680				}

There are basically three steps to follow in order to exploit this vulnerability:

  • The attacker send a login request with the “remember me” parameter enabled. If the login was successful then he can read the value of the “secure hash” through the Set-Cookie header of the HTTP response (line 679 of the above code).
  • Once the “secure hash” was obtained, the attacker may use this value as key to encrypt a specially crafted serialized Joomla object using the same algorithm of the JCrypt class, which in this case is a simple XOR cipher.
  • Finally, the attacker send an HTTP request having the string “JLOGIN_REMEMBER” as user-agent, and a cookie like [secure hash]=[encrypted payload], where “secure hash”, or rather the name of the cookie, is the value found in the first step, while the value of the cookie is the string obtained by the previous step.

Unlike the “highlighter” PHP Objection Injection vulnerability, this one cannot be exploited leveraging magic methods of third-party extensions, because this time the vulnerable unserialize() is called within an onAfterInitialise() method. However, this vulnerability can be exploited through magic methods of some Joomla core classes. Therefore, as already explained in my previous blog post, this vulnerability can be abused to conduct Denial of Service and SQL Injection attacks.