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 {
      label: choice.name,
      value: choice._id_
    };
  });
  
  // Initialize the dropdown
  this.$("select").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:

$(...)
  .$("<option>")
  .attr("value", option.value)
  .html(option.label);

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");
          </script>`
  }
});

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(choice.name),
      value: choice._id_
    };
  });
  this.$("select").dropdown({
    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!

Method Auditing Revisited

Written by Pete Corey on Feb 15, 2016.

In the past I’ve written about how a potentially malicious user can view all of the isometrically defined methods in your Meteor application. By inspecting the Meteor.connection._methodHandlers object on the client, they can see all client-side executable instances of your methods, which in most cases are identical to your server-side executable methods. This lets an attacker identify weaknesses in your application security that may lead to an attack.

Meteor.connection._methodHandlers only poses a potential problem for methods defined in a shared location. Methods defined in a server.js file, or within a server/ folder will never be shipped to the client, and won’t appear in the _methodHandlers object.

However, defining methods in server-only locations doesn’t mean your methods are free from prying eyes! Depending on how your application is structured, it may still be possible for an attacker to find and exploit these methods.

Source Snooping

Even if a method is defined in a server-only location, it’s still accessible from the client. For example, if you have a method defined in a server.js file:

Meteor.methods({
  hiddenMethod: function(argument) {
    // Do secret things...
  }
});

That method can still be called from the client:

Meteor.call("hiddenMethod", argument);

This means that one way of discovering hidden methods within a Meteor application is to simple search the bundled application source for Meteor method calls.

When the Meteor application is minified, the Meteor object is often transformed into some other variable name, so rather than searching for /Meteor\.call("/, it’s better to search for /\.call("/:

For example, in the above screenshot we can see a call to a Meteor method called "increasePostViews". This method is taking two arguments. I wonder if both of those arguments are being checked?

Watching the Wire

An alternative to searching through the source for method calls is to simple watch all of the DDP requests that are sent over the websocket connection. Any calls to Meteor methods will be clearly marked, along with a list of arguments being sent to this method.

As you go about using the application, you can build up a list of Meteor methods, some of which may not have a corresponding handler in the Meteor.connection._methodHandlers object.

In the above screenshot, we can see a call being made to the "upvotePost" method with a single arguments.

Well Kept Secrets

These techniques will only reveal hidden methods that are being called by client-side or shared code. It’s still possible that the Meteor application may have other, completely hidden methods. These methods may be defined on the server, and only called by other server-only code.

In that case, the only way for a curious client to discover those truly hidden methods is make a call to every possible Meteor method to determine if it exists on the server. Thankfully, this kind of brute forcing is totally unfeasible, and would most likely never be worth an attacker’s time.

At the end of the day, it shouldn’t matter whether attackers know methods exist. Even your most secret of methods should be made secure. Always assume that all of your methods will be called by all users, because they can be!