Stored XSS and Unexpected Unsafe-Eval

Written by Pete Corey on Mar 14, 2016.

In a previous post, I discussed the possibility of exposing Cross Site Scripting (XSS) vulnerabilities in your Meteor application through the use of jQuery components.

I gave an example where a malicious string with a <script> tag was being injected into the DOM through a call to $.html. For example:


My recommendation in that post was to sanitize the string with Blaze._encode before injecting it into the DOM.

Another potential solution to this problem is to use the browser-policy Meteor package to establish a Content Security Policy (CSP) within your application. However, this solution comes with its share of quirks.

When CSP Falls Short

A Content Security Policy is used to tell the browser, among other things, what types of Javascript is allowed to run within a client’s browser, and what should be blocked.

Many applications instruct their Content Security Policy to disallow inline scripts (unsafe-inline). Inline scripts refer to any javascript that executes within an HTML element’s event handler attributes, within a <script> tag, or through the URL with a javascript: protocol.

It may seem like disallowing inline scripts would prevent our Cross Site Scripting issue. After all, the malicious Javascript is running from an inline <script> tag that’s being injected into the DOM.

However, in the eyes of the Content Security Policy, a <script> tag injected through a call to $.html is not considered an inline script.

Unexpected Unsafe-Eval

If your Content Security Policy disallows inline scripts, but allows Javascript evaluation (unsafe-eval), your application would still be vulnerable to this particular type of Cross Site Scripting attack.

Under the hood, this after-the-fact injection of a <script> tag, and its subsequent execution is considered an eval statement. Only by disallowing unsafe-eval can you prevent this type of XSS attack.

This is incredibly unintuitive and may lead to dangerous misunderstandings about what types of Javascript your application is allowed to execute. For a variety of reasons, some applications require the use of unsafe-eval. Without understanding the subtleties of what is considered an eval by your Content Security Policy, you may be vulnerable to severe Cross Site Scripting attacks.

It’s important to remember that a Content Security Policy is not a panacea against all front-end attacks. Instead, it’s a helpful safeguard that can be used in conjunction with other safeguards like properly sanitizing data, and validating user input.

Cross Site Scripting Through jQuery Components

Written by Pete Corey on Mar 7, 2016.

In the past, I’ve talked about tracking down Cross Site Scripting (XSS) vulnerabilities within your Meteor application by hunting for triple-brace injections. I argued that XSS was relatively uncommon because you needed to explicitly use this special injection syntax to inject raw HTML into the DOM.

While this is mostly true, there are other ways for XSS to rear its ugly head in your Meteor application.

Imagine the following situation. You have a template that uses data from a MongoDB collection to populate a dropdown. To render the dropdown, you’re using a jQuery plugin. This plugin expects you to provide the dropdown options as an argument, rather than through the DOM:

Template.choices.onRendered(function() {

  // Build our dropdown options from the Choices collection
  let options = Choices.find().fetch().map(choice => {
    return {
      value: choice._id_
  // Initialize the dropdown
    options: options

If you took the time to look at how the jQuery plugin works, you would notice that it’s taking the options we’re providing it, and dumping them directly into the DOM:

  .attr("value", option.value)

This plugin is making no attempt to sanitize the values or labels that are being injected into the DOM. Its operating under the assumption that the data will already be sanitized by the time it reaches the plugin.

Unfortunately, this opens the door to a Stored Cross Site Scripting vulnerability in our application. Because neither the jQuery plugin nor our application are sanitizing the values pulled from the database before injecting them into the DOM, a malicious user could easily take advantage of the situation.

Imagine that an attacker were to change the name of one of the Choice documents to a string containing some malicious <script> tag:

Choices.update(..., {
  $set: {
    name: `<script>
             Roles.addUsersToRoles("${Meteor.userId()}", "admin");

Now, whenever that option is rendered in the dropdown, that malicious Javascript will be executed.

Interestingly, if another user were to use the dropdown, the malicious Javascript would run on their behalf. This means that if an Administrator were to open the dropdown and render this malicious option, they would unintentionally give the admin role to the attacking user.

This is a bad thing.

Thankfully, the solution to this issue is relatively straight-forward. Before passing our options into the jQuery plugin, we should sanitize them to prevent malicious tags from being inserted into the DOM as HTML.

The Blaze package comes with a handy utility called Blaze._escape that does just that. It takes in any string and escapes any HTML special characters into their corresponding HTML encoded form.

We can incorporate Blaze._escape into our previous example like so:

Template.choices.onRendered(function() {
  let options = Choices.find().fetch().map(choice => {
    return {
      label: Blaze._escape(,
      value: choice._id_
    options: options

This would transform the malicious name into a benign string that could safely be injected into the DOM:

'&lt;script&gt;Roles.addUsersToRoles("...", "admin");&lt;/script&gt;'

When injected into the DOM, this would be interpreted as plain text, rather than a <script> tag. This means that the malicious Javascript would not be executed, and the Cross Site Scripting vulnerability would no longer exist!

It’s important to always take responsibility for the safety of your application. When using third-party plugins or packages, never make assumptions about what they are, or are not doing - especially when it comes to security.

When in doubt, dig into the source and find out exactly what’s going on!

Why You Should Always Check Your Arguments

Written by Pete Corey on Feb 29, 2016.

Last October, I was lucky enough to attend the first ever Meteor Space Camp! True to its “unconference” roots, the weekend was punctuated by fantastic talks on a variety of Meteor topics, by a collection of amazing Meteor developers.

I decided to give a talk on Meteor security with a heavy focus on the importance of making assertions about user-provided data. The talk was called, “Why You Should Always Check Your Arguments”, and it’s available on Youtube!

Take a look, and be sure to always check your arguments!