Loading...

Follow Meteor Blog on Feedspot

Continue with Google
Continue with Facebook
or

Valid

This guest post is by longtime Meteor community member Pete Corey, who is an independent consultant, web developer, and writer.

For the past three years I’ve been living and breathing Meteor security, and I’m excited to say I’ve managed to compile everything I’ve learned into a single comprehensive guide to securing your Meteor application. To celebrate the newly released Secure Meteor, I thought we could talk about one of the most prevalent and dangerous vulnerabilities that developers commonly introduce into their Meteor applications.

Let’s talk about NoSQL Injection!

To set the scene, let’s pretend that we’re building a Meteor-powered online storefront. Within our application, we’ve built a shopping cart system to let our users track and save the items they’re interested in purchasing. Like any other shopping cart system, we give our users the ability to empty their cart.

When a user clicks the “empty cart” button, their client makes a call to the emptyCart Meteor method, passing up the shopping cart’s MongoDB identifier. The emptyCart method simply removes the items (or “line items”) in that cart from the database, effectively leaving the cart empty:

Meteor.methods({
emptyCart(cartId) {
LineItems.remove({ cartId });
}
});

It’s important to realize that while our Meteor method assumes that cartId is a string, it’s never explicitly stating that assumption or making any assertion about cartId’s type.

A potentially malicious user could easily violate our assumption by manually calling the emptyCart method from their browser’s console and passing in any JSON-serializable value for cartId:

Meteor.call("emptyCart", { $gte: "" });

In this example, our malicious user has called the emptyCart method and specified a cartId of { $gte: ""}. Placing this value of cartId into our LineItems.remove query results in the following expression being executed on our server:

LineItems.remove({ cartId: { $gte: "" } });

Rather than deleting a single user’s items from their shopping cart, this removes every item from every shopping cart in the database, whether they belong to the user calling the method or not.

Manually calling our Meteor method from the browser.

The object the malicious user passed into the emptyCart method is a MongoDB query operator. Specifically, a “greater than or equal to an empty string” query operator. When dropped into our LineItems.remove query, it’s essentially telling the database to remove all line items in the database whose _id is “greater than or equal to an empty string.” Every line item’s _id will be greater than an empty string, so every line item will be removed.

Devastating.

This type of attack is known as a “NoSQL Injection” attack. NoSQL Injection vulnerabilities are incredibly dangerous and are particularly prevalent and relevant in Meteor applications, due to Meteor’s tight coupling to MongoDB and its seamless communication of disparate types through its DDP protocol.

Ultimately, NoSQL Injection attacks make their way into your application through missing, incomplete, or incorrect checking of user-provided data.

In this example, we could have easily prevented this NoSQL Injection attack by explicitly voicing the assumption we’re making about the cartId argument of the emptyCart method. Let’s add a check that asserts that cartId is, in fact, the string we’re assuming it to be:

Meteor.methods({
emptyCart(cartId) {
check(cartId, String);
LineItems.remove({ cartId });
}
});

By making an assertion about the type of cartId, we’ve completely prevented the possibility of a NoSQL Injection attack. If our malicious user tries to pass in their MongoDB query operator, they’ll receive a match error in response.

Match error when calling our Meteor method.

Let’s consider another example.

Imagine that we’ve decided to implement a user profile system in our shopping application. Letting our users tell us more about themselves might give us some valuable insights into their shopping habits!

We decided that the easiest route for implementing this feature was to add a set of “profile fields” to each user document, which they can freely edit to better express themselves. However, it’s important to remember that some fields on the user document are sensitive, and should only be updated by administrators.

Our first attempt at implementing this feature was with an editProfile Meteor method that accepts an update from the client, asserts that the client is able to perform the update, and then makes the modification to the current user’s document:

Meteor.methods({
editProfile(edit) {
if (edit.$set.isAdmin && !Meteor.user().isAdmin) {
throw new Meteor.Error("Not authorized.");
}
return Meteor.users.update(
{
_id: this.userId
},
edit
);
}
});

We’re making the assertion that only administrators are allowed to $set the isAdmin field on their user document. We wouldn’t want non-administrators escalating their privileges.

At first glance, this seems like a fine home-rolled solution.

Unfortunately, or fortunately, depending on your perspective, MongoDB gives us many ways of accomplishing the same task. While a malicious user might be forbidden from setting their isAdmin field to true directly, they can achieve the same result through other means.

Imagine our malicious user runs the following query from their browser’s console:

Meteor.call("editProfile", { $inc: { isAdmin: 1 } });

Our $set guard is bypassed because the malicious user is passing up an $inc update operator, so our editProfile method happily runs their query. Rather than setting their isAdmin field to a specific value, this query increments their isAdmin field, essentially turning it into a truthy value.

Our malicious user has succeeded in elevating their privileges to the administrator level with a simple NoSQL Injection attack.

There are many ways of fixing this vulnerability, and they all boil down to making assertions about the type and shape of our user-provided inputs. For example, we could check that edit is an object with a $set field, and $set is an object that optionally contains some known set of editable fields:

Meteor.methods({
editProfile(edit) {
check(edit, {
$set: {
phone: Match.Optional(String),
address: Match.Optional(String)
}
});
return Meteor.users.update(
{
_id: this.userId
},
edit
);
}
});

Now if anyone tries to use our editProfile to modify some unexpected fields, or pass up an unexpected MongoDB update operator, they’ll receive a match error and their method call will fail.

I hope that these examples have demonstrated that NoSQL Inject vulnerabilities can be both incredibly diverse in their effects, and incredibly devastating on your application.

At the end of the day, the root cause of NoSQL Injection attacks is always improperly validating user-provided input. It’s incredibly important to make strong assertions about the shape and type of any data provided by the client, especially if that data is going to be used to construct queries against your database. Failure to do so can result in data breaches, privilege escalations, and even full-scale Denial of Service attacks against your application!

If you’re eager to learn more about NoSQL Injection and other vulnerabilities that might be lurking inside your Meteor application, be sure to check out Secure Meteor.

I wrote Secure Meteor with the intention of sharing everything I’ve learned about Meteor security from my time spent working with amazing teams to better secure their Meteor applications. If this article, or Secure Meteor in its entirety helps you squash even one vulnerability in your application, I’ll chalk that up as a win.

Putting the Brakes on NoSQL Injection with Secure Meteor was originally published in Meteor Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

This is a guest post by Timo Horstschaefer, co-founder of Ledgy.com, the missing accounting software for your cap table—built with Meteor.

At Ledgy, we deeply care about our customer’s security and privacy. This is why we implemented a new two-factor authentication (2FA) system for Meteor.

This two-factor authentication system is based on the Time-based One-Time Password algorithm (TOTP), which is supported by a number of popular mobile apps like Google Authenticator, Duo and Authy.

Our implementation uses the fairly new otplib and adds a new login method to Meteor in order to pass the TOTP token along with the username and password.

This tutorial covers only the backend-code, meaning the methods for Meteor in order to activate 2FA and perform the login. The front-end is highly custom for each application and thus not covered by this tutorial.

The workflow for two-factor authentication goes as follows:

Setup
  1. Generate a secret key on the server, store it in the user’s profile and send it to the client.
  2. The user has to scan a QR code containing the secret with their 2FA app.
  3. The user enters a token generated by their 2FA app to confirm the setup.
Login
  1. The client calls the login method with a username and password.
  2. If the login is successful, but 2FA is enabled, the server returns 'two-factor-required', indicating that it needs further information.
  3. The client calls the login method again, now with username, password, and the time-dependent token from their 2FA app.
The Code

The first method we need to implement handles generating the secret and storing it in the user’s profile as services.twoFactorSecret:

The Ledgy interface for activating two-factor authentication.

In the next step, using their 2FA app, the user scans the QR code which contains the secret key along with some metadata generated by otplib. If you use React, qrcode.react might be a good choice for generating the QR code on the client and prevent the browser from keeping the generated image in the cache. This isn’t a fully functional example, but on the front-end, this could look something like:

import otplib from 'otplib';
import QRCode from 'qrcode.react';
const render = () => {
const otpauth = // String must be URI-encoded
otplib.authenticator.keyuri('Elon%20Must', 'Ledgy', secret);
  return (
<QRCode value={otpauth} level="H" size={256} />
);
}

In order to finalize 2FA activation, we’ll create a new server method that accepts a six-digit code from the user’s newly-activated 2FA app and, if correct when checked by the server, marks the account as “two-factor enabled”. This last verification step is important to ensure that the user has a functioning 2FA application, otherwise we’d end up locking them out of their account! We’ll store the fact that their account is two-factor enabled using a read-only boolean on the user’s record called twoFactorEnabled (i.e. Meteor.user().twoFactorEnabled).

We will never send the actual secret back to a client after the initial setup, so once the QR code is gone, it should never be shown to the user again.
We strongly recommend, that the actual image is generated on the client side to prevent potentially dangerous caching by the browser.
Logging In

Now that the setup part is done, let’s look at the login. Since Meteor doesn’t support a login method which accepts a TOTP code along with the password, we need to register our own. This is the centerpiece and most critical part of the two-factor authentication:

Note: For simplicity, this method only allows login by email. It currently does not support login by username or userId.

The only missing piece now is to call our new login method on the client. For this, we use the function Accounts.callLoginMethod(). The code will look somewhat like this (but again, it depends on your front-end):

Well, that’s it! Now you have modern, two-factor authentication using TOTP.

Screenshot from the Ledgy login form prompting the user to enter the 2FA token.

If you manage a startup company and want to use an online cap table tool, then you can see this code in a real application. Just visit our website Ledgy.com and sign up. I hope you enjoyed the tutorial!

The missing accounting software for your cap table

Tutorial: Two-factor authentication with Meteor and TOTP was originally published in Meteor Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Hear about Meteor 1.7, cutting-edge Meteor tooling, and production stories

It’s been a minute, but Meteor Night is back! Huge thanks to Lucas Hansen and Qualia, who are going to start hosting the event regularly. The first one is coming up next week, and Ben Newman from the Meteor team and prolific package developer Theodor Diaconu will be flying into San Francisco to speak at the event!

Here’s what you’ll hear about:

  • Lucas Hansen, CTO of Qualia: “10,000 Hours Into Meteor”. You’ll hear about Qualia’s extensive experience running Meteor in production, and some of the awesome tooling they’ve built for it in-house.
  • Theodor Diaconu, CEO of Cult of Coders: “Redis Oplog, Grapher, and Apollo Live”. Theodor and Cult of Coders have built some of the coolest new tools for the Meteor community, and you’ll get to hear all about them!
  • Ben Newman, Tech Lead of the Meteor: “Meteor 1.7”. Meteor 1.7 is one of the most exciting releases ever, with a new system that eliminates some of the tradeoffs between compatibility and performance that developers have had to make in their apps for years. Hear all about it at Meteor Night!

As always, there will be food, drinks, and hanging out! The event is at Heavybit at 325 9th Street in San Francisco.

Sign up on Meetup if you’re going:

Meteor Night

You don’t want to miss this meetup to learn more about what’s going on in the Meteor community, and what cool new stuff is coming up. I’ll be there, and hope to see you and talk about Meteor!

Come to Meteor Night on May 30 in San Francisco! was originally published in Meteor Blog on Medium, where people are continuing the conversation by highlighting and responding to this story.

Read Full Article
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Separate tags by commas
To access this feature, please upgrade your account.
Start your free month
Free Preview