Auth0 Security Bulletin for Rules

Published: January 10, 2019

Overview

This security bulletin covers several scenarios that customers should avoid in custom rule code, as they may create vulnerabilities in the authentication flow. A description of each issue and, where possible, an alternative way to write the same rule, is provided below.

  • Custom rule code containing conditional logic to enforce multi-factor authentication (MFA) may introduce the ability to bypass MFA altogether, especially when using silent authentication.

  • Custom rule code implementing authorization controls based on a specific substring without appropriate logic may result in elevated privileges.

  • Custom rule code sending diagnostic information to third-party services, rather than using Auth0's built-in debugging features, may result in sensitive information disclosure.

  • Custom rule code triggering paid services may introduce the ability for an adversary to incur unwanted billing charges.

  • Custom rule code granting authorization based on unverified email addresses may allow adversaries to acquire those privileges through a secondary connection type.

  • Custom rule code containing hardcoded secrets, such as API keys, rather than using the global configuration object, increases the risk of exposing such secrets.

Improper MFA rules

Silent authentication

Within a single-page application (SPA), silent authentication enables new access tokens to be issued without user interaction as long as the current session is valid against the authorization server.

Auth0 offers silent authentication through a special parameter which is added to the authorization endpoint as follows: /authorize?prompt=none

If MFA is added to the silent authentication process, however, then user interaction becomes necessary.

Conditional logic based on silent authentication to bypass MFA should not be used as a workaround. Rules such as this allow complete MFA bypass and should not be utilized:

function (user, context, callback) {
  if (context.request.query && context.request.query.prompt === 'none') {
  // skip MFA for silent token requests
  return callback(null, user, context);
  }
...
}

Silent authentication with redirection to custom MFA provider

Rules that determine whether to redirect to custom multi-factor authentication based on silent authentication may allow MFA bypass in some unusual circumstances. For example, avoid the following:

function (user, context, callback) {
  if (context.request.query && context.request.query.prompt === 'none') {
  //redirect to custom MFA
  context.redirect = {
    url: "https://example.com/"
  };
...
}

Improper verification

Utilizing rules to bypass multi-factor authentication given specific conditions may bypass MFA when the checks in place are inadequate for determining the veracity of a desired state.

In these cases, the root of the issue is that rules can never determine whether MFA was successfully executed, as MFA happens after rule execution. Rules configured in this manner (see examples below) prematurely update the user record under the incorrect assumption that MFA will be successfully executed while relying on the same user record to skip MFA under given conditions.

The examples below illustrate improper verification checks which can result in full multi-factor authentication bypass.

Device fingerprint

function (user, context, callback) {

  var crypto = require('crypto');

  var deviceFingerPrint = getDeviceFingerPrint();
  user.app_metadata = user.app_metadata || {};

  // Inadequate verification check
  if (user.app_metadata.lastLoginDeviceFingerPrint !== deviceFingerPrint) {
    user.app_metadata.lastLoginDeviceFingerPrint = deviceFingerPrint;
    context.multi-factor = {
      ...
    };
    ...
  }
  function getDeviceFingerPrint() {
    var shasum = crypto.createHash('sha1');
    shasum.update(context.request.userAgent);
    shasum.update(context.request.ip);
    return shasum.digest('hex');
  }
}

Country code

function (user, context, callback) {
  user.app_metadata = user.app_metadata || {};

  // Inadequate verification check
  if (user.app_metadata.last_location !== context.request.geoip.country_code) {
    user.app_metadata.last_location = context.request.geoip.country_code;
    context.multi-factor = {
    ...
    };
  }

Am I affected?

If you use any of the previously illustrated rule logic, an adversary who has successfully logged in with the first authentication factor may be able to bypass MFA on your application.

Mitigation steps

If you are affected by the silent authentication scenario, remove the conditional logic based on prompt === 'none'. This will trigger multi-factor authentication on each silent authentication call to check session status.

If you are affected by the silent authentication with redirection scenario, remove the conditional logic based on prompt === 'none' and switch to an Auth0-supported multi-factor authentication provider.

To avoid prompting the user for MFA too often, you can set the parameter allowRememberBrowser to true, which will enable end-users to check a box so they will only be prompted for multi-factor authentication every 30 days. For example:

  context.multi-factor = {
    provider: 'guardian',
    allowRememberBrowser: true
  };

You may want to notify your end users about checking the box to avoid being prompted too frequently.

If you are affected by rules utilizing improper verification for skipping MFA, ensure to use sound verification checks, discontinue MFA exceptions entirely, or use the allowRememberBrowser configuration option described above.

Will this update impact my users?

Depending on how you adjust your rules or configuration, this update may impact your users by prompting them for MFA more than usual.

Improper substring verification

If you have rule logic that requires access control based on an exact match of a particular string, such as an email domain, but you are only checking for a substring instead of an exact string match, your logic may not function as you intend. For example:

if( _.findIndex(connection.options.domain_aliases,function(d){ return user.email.indexOf(d) >= 0;

The above logic would return true given emails such as these:

  • user.domain.com@not-domain.com

  • "user@domain.com"@not-domain.com (quotes included)

Am I affected?

Only customers using conditional logic in rules as described above, are affected.

Mitigation steps

Utilize logic such as:

const emailSplit = user.email.split('@'); const userEmailDomain = emailSplit[emailSplit.length - 1].toLowerCase();

Please refer to the Check Domains Against Connection Aliases rule template for more information. Alternatively, in the Rules section of the Auth0 Dashboard, view the rule template named Check if user email domain matches configured domain.

Will this update impact my users?

Possibly. The recommended change to rules with logic based on substring checks may impact end users, though you will be the best judge of the intent of your rules and whether any recommended changes are likely to impact your end users.

Debugging using external services

If you have rule logic that sends the Auth0 context object to an external service, you are exposing items such as id_token or access_token associated with the request. For example:

  request.post({
    url: 'http://requestbin.fullcontact.com/YourBinUrl',
    json: {
      user: user,
      context: context,
    },
    timeout: 15000
  }, function(err, response, body){
    if (err) return callback(err);
    return callback(null, user, context);
  });

Am I affected?

Only customers with rule logic as described above are affected.

Mitigation steps

You should modify your rule so it no longer sends the entire context object to requestbin, as this may expose any id_token or access_token associated with each request. Instead, you should send a subset of attributes from the context object that are less sensitive.

Please refer to the Requestbin rule template for more information. Alternatively, in the Rules section of the Auth0 Dashboard, view the rule template named Dump rule variables to RequestBin.

Auth0 also offers built-in methods for debugging rules without sending information to external services.

Will this update impact my users?

No. The impacted rule was typically used for debugging purposes. Modification should not impact end users.

Rules using a paid service

If you have rule logic that involves a paid service, such as sending SMS messages via Twillio, as part of the authentication process, you may be vulnerable to increased costs because an adversary with a valid username and password may be able to trigger a call to the service and incur costs without having to complete the entire authentication process specified by rules. For example:

  //Sends user SMS via Twilio
  function notifyUser(done) {
    const request = require('request');
    const twilioAccount = 'YOUR_TWILIO_ACCOUNT';
    const twilioAuthToken = 'YOUR_TWILIO_AUTH_TOKEN';

    request.post({
      url: 'https://api.twilio.com/2010-04-01/Accounts/' + twilioAccount + '/Messages.json',
      auth: {
        'user': twilioAccount,
        'pass': twilioAuthToken,
      },
      form: {
        body: 'You\'ve logged in from a different device or location.',
        to: user.phone,
        from: '+18668888888'
      }
    }, function (error, response, body) {
      if (error) return done(error);
      if (response.statusCode !== 201) return done(new Error(response.statusCode));
      return done(null);
    });
  }

Am I affected?

Only customers with rule logic as described above, and whose rule logic can be triggered by any untrusted user, are affected.

Mitigation steps

To mitigate this risk, consider one or more of the following actions:

  • Disallow public sign-ups, if not needed, to reduce the numbers of users who can sign up and trigger calls to paid services.

  • Mitigate the risk of credential theft to avoid account takeover by hackers who might use hijacked accounts to trigger calls to paid services.

  • Ensure your users have strong passwords when using Database connections.

  • Ensure your users utilize multi-factor authentication.

  • Ensure that the rule only gets triggered for an authorized subset of users, or under other appropriate conditions. For example, you may wish to add logic that checks if a user has a particular email domain, role/group, or subscription level before triggering the call to the paid service.

Will this update impact my users?

The impact to end users depends on which of the options described above you choose to mitigate the issue.

Authorization based on unverified email address

If you have rule logic that requires access control based on a particular email or email domain, but you are failing to check whether that user has verified their email, an adversary could potentially gain additional privileges by using a second connection type. For example:

  var userHasAccess = allowList.some(function (email) {
    return email === user.email;
  });

The above logic would return true if an adversary could create an account using a different connection type (such as social) with an email address present in the allow-list. This happens because the same email can exist in different connection types.

Am I affected?

Only customers using conditional logic in rules as described above, and allowing for multiple connection types, are affected.

Mitigation steps

Whenever granting authorization based on email addresses, always start the rule with the following logic:

function (user, context, callback) {
  // Access should only be granted to verified users.
  if (!user.email || !user.email_verified) {
    return callback(new UnauthorizedError('Access denied.'));
  }

Sometimes even a verified email may not be an adequate check for the authorization you want to perform. To find out more about such cases, please read about the Email Verified Usage.

Will this update impact my users?

This will only affect existing users if they have not verified their email addresses.

Plaintext secrets within rule code

If you specify sensitive information, such as API keys, within rule code, you should configure those values in rule settings instead. For example:

const myApiKey = 'abcdefghijklmnopqrstuvwxyz';

Such sensitive values, being present in the rule code, will remain unencrypted in our systems and suffer the risk of being exposed.

Am I affected?

If your rules contain sensitive values within the rule code itself, you are affected.

Mitigation steps

Place the sensitive values in the Settings area of the main Rules section of the Auth0 Dashboard and then access them in your rule via the following:

const myApiKey = configuration.myApiKey;

This will ensure that all sensitive values are encrypted within Auth0's systems, reducing the risk of exposure.

Will this update impact my users?

This will not impact your users.