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