Joomla! 官方12月21号发布了3.4.7 新版程序,其中修复了Session序列化和一处SQL注入。

反序列化漏洞修复分析

前一阵子 Joomla 的对象注入很火,而官方3.4.6的修复仅仅是严格过滤了X_FORWARDED_FOR、注释了USER_AGENT存入SESSION那一句,见:https://github.com/joomla/joomla-cms/commit/995db72ff4eaa544e38b4da3630b7a1ac0146264#diff-aba80b5850bf0435954b29dece250cbfL1021这样只是指哪补哪,治标不治本。看来官方上次的修复只是临时解决方案,这次的更新(3.4.7)算是彻底解决了此问题。

上次的对象注入,需要满足三个条件:

  1. 自己实现session的处理方式,重新实现了 session 存储的read()和write()方法,但是并没有对 session 的值进行安全处理。
  2. Mysql非strict mode下,使用utf8mb4字符 \\xF0\\x9D\\x8C\\x86 来截断。
  3. PHP <= 5.6.13 session中反序列化解析的BUG。

Joomla 官方也只能解决第一个,也就是改进session的处理方式。这次更新,在 libraries/cms/version/version.php 中,将SESSION存储在内部的Registry类对象中,弃用了以前使用 $_SESSION[$namespace][$name] 的方式:

$this->data = new \\Joomla\\Registry\\Registry;

并且,在写SESSION的时候会先做base64_encode:

public function close(){
	if ($this->_state !== 'active'){
		// @TODO :: generated error here
		return false;
	}
	$session = JFactory::getSession();
	$data    = $session->getData();

	// Before storing it, let's serialize and encode the JRegistry object
	$_SESSION['joomla'] = base64_encode(serialize($data));

	session_write_close();
	return true;
}

这样,$_SESSION 就只剩下了$_SESSION[‘joomla’],而且$_SESSION[‘joomla’] 只存储了Registry的对象$data,在执行read()和write()时候,SESSION是经过base64_encode后的数据,就不会存在read()之后自动反序列化而导致对象注入了。

在反序列化的时候也不存在unserialize参数可控的情况。(可控的只是$data的成员变量)

if (isset($_SESSION['joomla']) && !empty($_SESSION['joomla'])){
    $data = $_SESSION['joomla'];
    $data = base64_decode($data);
    $this->data = unserialize($data);
}

Joomla官方这次的解决方案比较好,不像上次那样治标不治本,这样的态度值得称赞。反观Apache对struts2 漏洞的修复…就不说了。

SQL注入漏洞分析

漏洞分析

代码位于,administrator/components/com_categories/models/category.php,save()函数内:

$assoc = $this->getAssoc();

if ($assoc)
{
	// Adding self to the association
	$associations = $data['associations'];

	foreach ($associations as $tag => $id)
	{
		if (empty($id))
		{
			unset($associations[$tag]);
		}
	}

	// Detecting all item menus
	$all_language = $table->language == '*';

	if ($all_language && !empty($associations))
	{
		JError::raiseNotice(403, JText::_('COM_CATEGORIES_ERROR_ALL_LANGUAGE_ASSOCIATED'));
	}

	$associations[$table->language] = $table->id;

	// Deleting old association for these items
	$db = $this->getDbo();
	$query = $db->getQuery(true)
		->delete('#__associations')
		->where($db->quoteName('context') . ' = ' . $db->quote($this->associationsContext))
		->where($db->quoteName('id') . ' IN (' . implode(',', $associations) . ')');
	$db->setQuery($query);
	$db->execute();

	if ($error = $db->getErrorMsg())
	{
		$this->setError($error);

		return false;
	}

	if (!$all_language && count($associations))
	{
		// Adding new association for these items
		$key = md5(json_encode($associations));
		$query->clear()
			->insert('#__associations');

		foreach ($associations as $id)
		{
			$query->values($id . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key));
		}

		$db->setQuery($query);
		$db->execute();

		if ($error = $db->getErrorMsg())
		{
			$this->setError($error);

			return false;
		}
	}
}
Powered by Fruition