Craft CMS affected by server side template injection

Abstract

It was discovered that Craft CMS is vulnerable to server-side template injection. An authenticated attacker can exploit this issue to compromise Craft CMS, for example by retrieving sensitive data from configuration files.

Tested versions

All versions of Craft CMS prior to build 2791 are affected by this vulnerability.

Fix

Pixel & Tonic, Inc. released Craft CMS build 2791 that resolves this vulnerability. This build can easily be installed through the Control Panel. After the fix is applied the rendering of templates is globally limited in TemplatesService.php and TwigEnvironment.php.

Introduction

Craft CMS is a Content Management System comparable to WordPress or Drupal. Craft CMS is written in PHP and uses the Yii and Twig frameworks. When users update their profile, they are redirected to (parts of) the Control Panel.

The (Twig) template that needs to be loaded is user-controllable and is vulnerable to server-side template injection. An authenticated attacker can exploit this issue to compromise Craft CMS, for example by retrieving sensitive data from configuration files.

Details

The vulnerability exists in the base controller, responsible for redirecting the user to the correct Control Panel page. The affected code is listed below.

BaseController.php:

public function redirectToPostedUrl($object = null, $default = null)
{
	**$url = craft()->request->getPost('redirect');**
  	if ($url === null)
  	{
    	if ($default !== null)
    	{
      		$url = $default;
    	}
    	else
    	{
      		$url = craft()->request->getPath();
    	}
  	}
  	if ($object)
  	{
  		**$url = craft()->templates->renderObjectTemplate($url, $object);**
  	}
  	$this->redirect($url);
}

The renderObjectTemplate method is located in TemplatesService.php, which will render a template to access properties of a single object. As the code shows, any template is accepted, rendering this code vulnerable to server-side template injection.

TemplatesService.php:

public function renderObjectTemplate($template, $object)
{
	// If there are no dynamic tags, just return the template
  	if (strpos($template, '{') === false)
  	{
    	return $template;
  	}
  	// Get a Twig instance with the String template loader
  	$twig = $this->getTwig('Twig_Loader_String');
  	// Have we already parsed this template?
  	if (!isset($this->_objectTemplates[$template]))
  	{
	    // Replace shortcut "{var}"s with "{{object.var}}"s, without affecting normal Twig tags
    	$formattedTemplate = preg_replace('/(?<![\{\%])\{(?![\{\%])/', '{{object.', $template);
    	$formattedTemplate = preg_replace('/(?<![\}\%])\}(?![\}\%])/', '|raw}}', $formattedTemplate);
    	$this->_objectTemplates[$template] = $twig->loadTemplate($formattedTemplate);
  	}
  	// Temporarily disable strict variables if it's enabled
 	$strictVariables = $twig->isStrictVariables();
  	if ($strictVariables)
  	{
  		$twig->disableStrictVariables();
  	}
  	// Render it!
  	$lastRenderingTemplate = $this->_renderingTemplate;
  	$this->_renderingTemplate = 'string:'.$template;
  	$result = $this->_objectTemplates[$template]->render(array(
  	'object' => $object
  	));
  	$this->_renderingTemplate = $lastRenderingTemplate;
  	// Re-enable strict variables
  	if ($strictVariables)
  	{
    	$twig->enableStrictVariables();
  	}
  	return $result;
}

Unfortunately, Twig does not limit what is available to the Twig context with regards to security. As there is no restriction set up, Twig will accept and query every template it receives. By manipulating the template a logged in user can retrieve objects in files present on a web server.

Proof of Concept

In the following screenshot it can be seen that it is possible to retrieve the database password (password object) from the configuration file db.php by setting the redirect POST parameter to: {{craft.config.get('password','db')}}

Figure 1: retrieving the database password through template injection

Vragen of feedback?