Analysis of the Joomla PHP Object Injection Vulnerability

Today I have disclosed KIS-2013-03, a PHP Object Injection vulnerability which affects the Joomla CMS. I have reported this vulnerability to the Joomla Security Strike Team only some months ago, but to be honest I have noticed that vulnerable unserialize() call a long time before. The only one reason why I have not notified them before is because I thought that it wasn’t exploitable: I had not noticed any useful magic method which could be abused to conduct malicious attacks, so I have come to the conclusion that it wasn’t an actual security vulnerability.

Some time later, after the release of Joomla 3.0, I thought to look again at the Joomla source code in order to see if some useful magic method was added. I didn’t find so much new PHP classes or magic methods compared to those present in Joomla 2.5, but I have noticed a little change inside a destructor method which was the key for me to understand that the vulnerability actually exists and it can be exploited through (but not only) this magic method. I was talking about the destructor method of the plgSystemDebug class, which in Joomla 2.5 begins in this way:

74	public function __destruct()
75	{
76		// Do not render if debugging or language debug is not enabled
77		if (!JDEBUG && !JFactory::getApplication()->getCfg('debug_lang'))
78		{
79			return;
80		}

While in Joomla 3.0 it was changed to:

84	public function __destruct()
85	{
86		// Do not render if debugging or language debug is not enabled
87		if (!JDEBUG && !$this->debugLang)
88		{
89			return;
90		}

The System Debug plugin is enabled by default configuration, but both the Debug System and the Debug Language options are disabled. It means that in Joomla 2.5 this magic method may be abused only if one or both of those options are enabled, while in Joomla 3.0 it could always be abused because the if condition checks the debugLang object’s property. After a first sight at the source code of the plgSystemDebug::__destruct() method, it might sound no useful to conduct any kind of malicious attack, but there is a line of code which calls the get() method of the object stored into the params property:

1		$filterGroups = (array) $this->params->get('filter_groups', null);

That property is a member of the JPlugin class and is intended to be a JRegistry object which contains the parameters of the plugin. However, an attacker could set the params property to an arbitrary PHP object, so I looked for useful get() methods and I found two different attack vectors.

• Arbitrary Directory Deletion

The first attack vector could allow an attacker to delete arbitrary directories where the web-server has write permissions. This is possible by abusing two different methods, the first one is JInput::get():

157	public function get($name, $default = null, $filter = 'cmd')
158	{
159		if (isset($this->data[$name]))
160		{
161			return $this->filter->clean($this->data[$name], $filter);
162		}

At line 161 is called the clean() method of the object stored into the filter property, which is intended to be a JFilterInput object. But, as already explained, this property can be set to an arbitrary PHP object, so this time I had to look for useful clean() methods. The result was the JCacheStorageFile::clean() method:

182	public function clean($group, $mode = null)
183	{
184		$return = true;
185		$folder = $group;
187		if (trim($folder) == '')
188		{
189			$mode = 'notgroup';
190		}
192		switch ($mode)
193		{
194			case 'notgroup':
195				$folders = $this->_folders($this->_root);
196				for ($i = 0, $n = count($folders); $i < $n; $i++)
197				{
198					if ($folders[$i] != $folder)
199					{
200						$return |= $this->_deleteFolder($this->_root . '/' . $folders[$i]);
201					}
202				}
203				break;
204			case 'group':
205			default: 
206				if (is_dir($this->_root . '/' . $folder))
207				{
208					$return = $this->_deleteFolder($this->_root . '/' . $folder);
209				}

Since the $mode variable will be set to “cmd”, the switch statement at line 192 will break on the default case, then the JCacheStorageFile::_deleteFolder() method will be called at line 208 and, as the name suggests, this method tries to recursively delete a directory. The attacker does not have to necessarily know the full path of the directory, because relative paths can be used, for instance, he may set to “./” the _root property of the injected JCacheStorageFile object in order to try to delete the Joomla webroot, and this may result in a Denial of Service (DoS) condition.

• Blind SQL Injection

The second attack vector could allow an attacker to carry out SQL Injection attacks. This is possible by abusing the get() method of the JCategories class:

166	public function get($id = 'root', $forceload = false)
167	{
168		if ($id !== 'root')
169		{
170			$id = (int) $id;
172			if ($id == 0)
173			{
174				$id = 'root';
175			}
176		}
178		// If this $id has not been processed yet, execute the _load method
179		if ((!isset($this->_nodes[$id]) && !isset($this->_checkedCategories[$id])) || $forceload)
180		{
181			$this->_load($id);
182		}

At line 181 is called the JCategories::_load() method, which is intended to fetch a specific category from the back-end database. Within this method is possible to inject arbitrary SQL commands through different object properties, such as _table, _field, _key and _statefield.

• Moreover…

Like I mentioned at the beginning of this post, the plgSystemDebug::__destruct() method is not the only one magic method which could be abused to exploit this vulnerability. I have noticed another interesting magic method which was added in Joomla 3.0 and it’s the __toString() method of the JViewHtml class, which calls the JViewHtml::render() method:

135	public function render()
136	{
137		// Get the layout path.
138		$path = $this->getPath($this->getLayout());
140		// Check if the layout path was found.
141		if (!$path)
142		{
143			throw new RuntimeException('Layout Path Not Found');
144		}
146		// Start an output buffer.
147		ob_start();
149		// Load the layout.
150		include $path;

The JViewHtml::getPath() method may return a string which can be fully controlled by an attacker, and this can results in a PHP file inclusion attack at line 150. However, JViewHtml is an abstract class which is intended to be used by third-party extensions, hence this attack vector can be exploited only if is installed an extension containing a class which extends JViewHtml.

Nevertheless, this interesting piece of code gave me a hint to let me understand that magic methods of third-party extensions could be abused as well. This is possible because the vulnerable unseralize() is called within an onAfterDispatch() method, which is triggered after that Joomla has processed the request and mapped it to a component, so all the classes of the selected component are declared when the method is triggered. It means that other attack vectors may be possible leveraging magic methods of third-party Joomla extensions.