Edit

Timing attacks, everywhere

Ever heard of timing attacks? Turns out timing attack vulnerabilities are pretty common. It’s unclear to me how practical those attacks are, but I wouldn’t risk it.

Finding timing attack vulnerabilities

The gist of a timing attack is an observable timing discrepancy in access control code:

  • Some code compares two values in order to determine whether something is authorised or not, for example comparing the user input with a stored plaintext password, or even comparing a hashed version of the user input against a password hash.
  • Turns out, the programming language executing that comparison is smart enough to stop the comparison as early as one of the characters stops matching – instead of having to compare all of the characters of the two strings.
  • If the execution time of the comparison can be observed – then an attacker can determine whether their supplied input is getting closer to the compared value, by seeing the execution take gradually longer.

What does this look like in practice? This:

class PasswordViewRestrictionForm(forms.Form):
    [...]

    def clean_password(self):
        data = self.cleaned_data['password']
        if data != self.restriction.password:
            raise forms.ValidationError(_("The password you have entered is not correct. Please try again."))

or that:

def authorize(request, configured_username, configured_password):
    # [...]
    if username == configured_username and password == configured_password:
        return True

…or in Ruby (Rails),

module ClassMethods
  def http_basic_authenticate_with(options = {})
    before_filter(options.except(:name, :password, :realm)) do
      authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
        name == options[:name] && password == options[:password]
      end
    end
  end
end

or in Node (Express),

const app = protect(
  "/admin",
  (user, pass) => user === "admin" && pass === "admin",
  {
    directory: __dirname + "/_static",
    realm: "now-basic-auth.node-static-auth",
    onAuthFailed: (res) => {
      res.end("Restricted area, please login (admin:admin).");
    },
  }
);

Once you know what to look for, timing attack vulnerabilities really are everywhere. In basic auth code. In more basic auth code, and also in password-protected content.

For me, this came up because I was looking for a good Express basic auth implementation, stumbled upon express-basic-auth, which works well, and then wondered – “oh, are other libraries using a timing-safe comparison function?” Lots weren’t.

Protecting yourself

As mentioned above, all sensitive comparison checks should use timing-safe comparison functions such as Node’s crypto.timingSafeEqual or Python 3.4+’s hmac.compare_digest (or Django’s crypto.constant_time_compare).

Proactively looking for similar issues

You will want to look for all access control code that uses the programming language’s built-in comparison operators (== or !=). I’m very excited about the potential of platforms like LGTM and their CodeQL language to find vulnerabilities like this automaticaly. Here is a sample query:

import python

from Compare eq
where eq.getAComparator().toString().toLowerCase().matches("%key%") or eq.getAComparator().toString().toLowerCase().matches("%password%") or eq.getAComparator().toString().toLowerCase().matches("%auth%") or eq.getAComparator().toString().toLowerCase().matches("%sign%")
select eq

This is a relatively naive query, but it helped me identify issues like Wagtail’s bypass-able image serve signature check.

If you want to try this out for yourself, GitHub’s Security Labs have the most extensive documentation on how to use this.

References