Sentry

Author:Chris AtLee <chris@atlee.ca>
Date:June 26, 2008

About

Sentry is a python module that can be used to authenticate users against existing databases / repositories such as PAM, LDAP, or MySQL databases.

Status

Sentry is currently very much in the alpha stage. You can get it from http://atlee.ca/software/sentry/dist/0.1

Background

Security is hard. It’s even harder to get right. For web applications, security consists of two main parts: authentication and authorization.

Authentication is the act of verifying that a person is who he claims to be. In the real world we do this by checking written signatures, drivers licenses, passports, etc. Computer systems do this typically with a username / password combination. Ideally only the user knows the password. Not even the server should know what the password is.

Authorization then is the act of determining if a person is allowed to see or do the thing he’s trying to see or do. In the real world, you may be carrying a valid drivers license and are you who are claiming to be, but you still won’t be allowed into that bank vault. On a computer, an administrative user (like “root”) is allowed to install packages, change system files, add and delete users, but non-administrative users are not allowed to do these things.

Currently, Sentry is primarily concerned with authentication. Specifically, Sentry aims to give you all the tools you need to authenticate users against existing authentication backends, such as PAM, LDAP, or a database that stores username and password information. A wide variety of password encryption schemes is supported via the sentry.hashers module.

Password protection, hashes, encryption, etc.

The claim that only the user should know the password seems like it would make authentication impossible.

In come cryptographic hashes (hereafter just called hashes). A hash function is a one way function that takes some data and returns a hash value in a predictable way. Common examples of hashes are MD5 and SHA-1. The key point about hashes is that it should be impractical for an attacker to guess the initial data if he’s just given the hash.

How do we use this to do authentication where the server doesn’t know what the password is? A simple first step would be for the server to store a hash of the password, and not the password itself. Now, the browser just has to calculate the hash of the user’s password, and send that hash to the server. The server can compare that hash against the one in the database.

There are a few problems with this setup though. The first is that we’ve effectively substituted the real password for a hash of the password. This means that although an attacker can’t easily determine the password from the hash, as long as he has the hash he can still authenticate himself. And if the hash is being transmitted over an insecure connection the attacker can get the hash easily. All is not lost though, at least the original password is protected, right? The attacker won’t be able to use the hash to guess a user’s password for other websites, right?

This is the second problem with the above setup. A simple hash is vulnerable to dictionary attacks as well as to rainbow tables. These are methods which an attacker can use to guess the original password used to generate the hash. For example, lets use a password of “sesame” and use a simple MD5 hash to protect it:

>>> import md5
>>> md5.new("open sesame").hexdigest()
'54ef36ec71201fdf9d1423fd26f97f6b'

This again is trivial for an attacker to decode. There are many tools available to do dictionary attacks or lookups in rainbow tables. See for example http://md5.thekaine.de. Enter in the hash from above and see how it is able to give back the original password.

Luckily there is a simple solution to this: salt. Adding salt to a hash is just adding some random data into the mix, which means that you’ll get a different hash every time you generate the salted hash for a password. And adding random data to the hash makes it much more difficult for a dictionary attack or rainbow table to work.

How you add salt to the hash is important though. A good way of applying salt to a hash is by using the HMAC algorithm with the salt as the key.

DO NOT simply prefix the salt to your data, as this opens the door for a length extension attack.

TODO: Mention wireless networks, example of presentation where a guy’s gmail session was hacked.

TODO: explain nonce

TODO: examples

So your options are basically:

  • Always login over SSL or other secure channel. Passwords can be transmitted as clear text. Validated against salted hashes on server. You could also do two-stage login over SSL so that the password is sent as HMAC(H(password, salt), nonce), but it’s not really necessary.
  • Two-stage login. User enters username, and is sent back his salt and a nonce. User enters password, and sends back HMAC(H(password, salt), nonce) to the server. Server has stored salt, H(password, salt) and nonce, so can verify that HMAC(H(password, salt), nonce) is valid. This is more cumbersome (requires two steps to login), but the passwords are well protected. If the server is compromised, an attacker will not be easily able to determine the original passwords, although he will be able to fake logins to the server since he knows H(password, salt).
  • Single-stage login with common salt. All users share a common salt. Since we know the salt is the same for each user, it can be sent along with the nonce. This protects against pre-computed dictionary attacks and rainbow tables, but not against a targeted attack since the attacker just has one salt to deal with.
  • Single-stage login. Server sends a nonce. User enters password and sends back HMAC(H(password), nonce) to the server. Server has H(password) and nonce stored and so can verify that HMAC(H(password),nonce) is valid. If the server is compromised, passwords are vulnerable since no salt has been used.

Authentication in Web Applications

There are several stages to authenticating a user in a web application.

  • Collecting the authentication information from the user (done by the browser)
  • Transmitting the authentication information to the server (done by the browser)
  • Verifying the authentication information (done by the server)

Because HTTP is a stateless protocol, these steps need to be performed every time the user requests a resource requiring authentication.

Summary of authentication mechanisms

  • HTTP Basic Authentication

    The username and password are combined by the browser into a single string joined by a ‘:’, and then base64-encoded, and sent to the server. It is trivial for somebody who can snoop your traffic to get access to your username and password.

    e.g. if an attacker snooped the Authorization header as QWxhZGRpbjpvcGVuIHNlc2FtZQ==, the username and password can be retrieved in a single line of python:

    >>>"QWxhZGRpbjpvcGVuIHNlc2FtZQ==".decode("base64").split(":")
    ['Aladdin', 'open sesame']
    

    Unless you’re connecting over a sercure channel (SSL), don’t use this type of authentication. Please.

  • HTTP Digest Authentication

    This is much more secure than HTTP Basic Authentication. The user’s password is never sent across the network in a decipherable form.

  • Form-based logins

    • Clear text

      Here there’s just a plain <input type=”password” /> field. When the HTML form is submitted, the password is sent across the network in the clear. If it’s sent over an unencrypted connection, then anybody with access to that network can see the username / password.

      This is just as insecure as HTTP Basic Authentication. Don’t use it unless you’re connecting over a secure channel (e.g. using SSL).

    • JavaScript-assisted - HMAC’ed or other client-side hashing

  • Cookies

  • OpenID