Skip to content

Instantly share code, notes, and snippets.

@joelpittet
Last active December 17, 2015 08:35
Show Gist options
  • Save joelpittet/a19fb33bf5896fa72d8d to your computer and use it in GitHub Desktop.
Save joelpittet/a19fb33bf5896fa72d8d to your computer and use it in GitHub Desktop.
Drupal 8 Theming - Security

Security:

Best practices for secure Drupal 8 theming

History

Prior to Drupal 8, we relied on manual escaping variables by our developers and themers. Every variable was either run through check_plain(), filter_xss() or another filter function manually or it was assumed to be safe. You can see where this assumption can easily be overlooked/forgotten and we end up with a security vulnerability usually in the form of an XSS attack.

One of the major decisions for choosing Twig in Drupal 8 was due to its autoescape security feature which all variables can be escaped automatically while they are printed. This alleviates a bunch of concerns and burden on the Security Team as well as protect Drupal Sites by default. This is equivalent to Drupal 7 writing <?php print check_plain($variable); ?> on each variable printed.

Flipping our security on its head aka Security Thought Process

Where as before Drupal 8 we were anticipating a variable was coming from an unsafe source and running it through a filtering function to ensure its output is safe for HTML. Now we no longer have to think about when or where to escape a variable as all variables are escaped on output in the template automatically while they are printed.

A new issue arises from this blessing of automatic safety. If you intend the variable's content to contain HTML it will now be escaped as HTML entities or double escaped if it was previously escaped. Before we needed to anticipate the unsafe variable now we have to react to unintentional escaping.

Instead of thinking about when to escape you now need to know when it's safe to mark a variable as safe.

Marking as Safe

Drupal and Twig each provide a couple of methods for telling the template the variable is safe. Carefully read the warnings above before using any of these methods. Note the subtle differences between the methods, and especially note that since Twig's current methods do not cover some important scenarios, they require extra caution.

Twig

Namely Twig_Markup() object and |raw filter. By passing a string variable wrapped in a Twig_Markup class to the template, the autoescaping mechanism will avoid escaping it on print. Or by sending the variable through the |raw filter in the template it will also print the raw unescaped string variable.

** Warning!** We highly discourage the use of |raw in your templates because you could unwillingly open a security vulnerability if the source of that printed variable originates from an unsafe source like $_GET for example. If you do use them, please thoroughly document why you think the variable is safe so others can incorporate your reasoning into their decisionmaking.

Drupal

Drupal provides a helper class called SafeMarkup or by building a renderable array. Also Drupal will mark output as safe if it's been through the t() function as well as one the following SafeMarkup static methods: SafeMarkup::checkPlain SafeMarkup::escape SafeMarkup::checkAdminXss SafeMarkup::placeholder

And depending on the variables passed in being safe also:

SafeMarkup::format SafeMarkup::setMultiple

Warning! We highly discourage the use of Drupal's SafeMarkup::set() These methods could inadvertantly open a security vunerablilty if the source of that printed variable originates from an unsafe source such as $_REQUEST. If you do use them, please thoroughly document why you think the variable is safe so others can incorporate your reasoning into their decisionmaking.

Know your source!

Knowing where a variable is coming from is the only way to know if it can be truly safe and that can be a difficult task sometimes while it travels through different Drupal subsystems. For that reason, we highly discourage marking a variable as safe further down the pipeline if it indeed needs to be unescaped markup.

So again, avoid at all costs using |raw and SafeMarkup::set(). And if you do use them please document thoroughly for others why you broke this rule and why you think the variable is safe.

Always look up the pipeline from the template to the preprocess to the render array that is building template and it's variables to find the top most place to mark a variable as safe to see where the source of the variable is truly coming from.

SafeMarkup vs Twig_Markup

We attempted to use Twig_Markup while enabled auto-escaping but there were many cases where wrapping a string in an object failed. When we try to concatenate two Safe strings wrapped in objects they would cease to be safe even if both Strings were originally safe. Also many variables were used as keys as well and array keys are not allowed to be Objects in PHP. To work around these we decided to make strings safe by storing them in an array of known safe strings. Still concatenation will break their safety but we can now return strings after we've stored them as safe and no longer have to deal with problems of strings wrapped as objects as is Twig_Markup.

One thing to note with this approach, NEVER store an opening unsafe tag as safe without closing it or an unsafe character as you can unintentionally make a security vulnerability this way.

Let there be Markup!

  1. First and foremost, put markup in a template if at all possible. That's where it belongs, avoid writing markup in PHP, this includes avoid writing theme functions as well.
  2. Use SafeMarkup::format or #type => inline_template renderable array if you deem it to much of a burden to create a new template.
  3. Filter your variables through SafeMarkup::checkAdminXss() or Xss::filterAdmin() if you need a limited HTML set and for admin only. Or Xss::filter to reduce the set of tags.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment