Friday 11 December 2020

Timing attacks on login

In designing an authentication server one of the attacks we try to defend against are timing attacks.

One area where timing attacks can be used is to find a discrepancy factor in the login verification code to distinguish between non-existing user accounts and accounts that do exist but for which a wrong password is provided.

The OWASP Authentication Cheatsheet provides examples as well as a hint on how to deal with this, however, just using a secure password checker might not be enough as illustrated in the code shown below:

The checkpassword() function in our code uses a safe digest compare algorithm but the additional activities we perform for successfully authenticated users (illustrated by the placeholder comments) is more than for non-authenticated users and could be used to mount a timing attack.

with timing_equalizer(1.0):
    if user:
        logger.info(f"valid user found {user.email}")
        if checkpassword(req.params['password'], user.password):
            # delete old sessions
            # create new session
            # set session cookie
        else:
            logger.info(f'user authentication failed for known user {user.email}')
    else:
        logger.info(f'user authentication failed for unknown user {email}')

Solution

The simple approach I chose to mitigate those timing attacks is to enclose the code in a context manager that makes sure that all code takes at least a given amount of time, no matter which path through the code is chosen.

This is shown in the first line where we call the timing_equalizer context manager with an argument that tells it to make sure executing the context takes at least one second.

This slows down the code but logins happen infrequently and a 1 second delay is considered acceptable (and if the activities for an authenticated user could be proven to be faster, we could even lower this time). Also note that this does not limit the speed overall: different users logging in parallel are served by separate threads or processes and will each have their own delay.

The code for the context manager is rather straight forward:


from time import sleep, time
from contextlib import contextmanager
from loguru import logger

@contextmanager
def timing_equalizer(delta):
    """
    Make sure that code in context takes
    at least `delta` seconds.
    """
    mark = time() + delta
    try:
        yield mark
    finally:
        wait = mark - time()
        logger.info(f'waiting for {wait} seconds')
        if wait > 0:
            sleep(wait)

This context manager might also be used for other code that might reveal a discrepancy. for example the forgot password and register new account (where there is check if an account is already in use).

Tuesday 20 October 2020

Web applications, registration and confirmation

If we want to be able to manage sessions for people who have logged in successfully, we need a way to register those users.
We want to make sure people do use a name (email address) that is already taken and we need to verify that the mail address is valid.
This all means that we will not register them right away but store their email address and password along with a confirmation key in a temporary table. We then send them this key to their email address in the form of a clickable link. If this link is clicked within an acceptable time (like 15 minutes or so) their registration is made permanent and they are redirected to the login page.

The preliminary workflow looks like this

Note that we do not have a separate process that looks for expired requests but that we deal with those during the request or verification event itself. The idea being that registration and verification are relatively rare events and running a separate process the remove expired requests would just waste cpu resources.
The picture isn't complete yet because we also need to create a workflow for resetting passwords that people have forgotten.
On top of that we also need to implement some rate limiting because now it is pretty easy to swamp a mailer by flooding the server with registration requests.

Saturday 17 October 2020

Web applications, authentication, authorization and auditing (AAA)

Formally this blog is about Docker, or precisely, about creating a dockerized web application. However, without thinking about security and access control, building a web application is a disaster waiting to happen.

Now security is a rather broad concept that for example also includes things like availability, disaster recovery and secure deployment but today we will focus on access control, specifically controlling access to our application.

So let's discuss briefly what we want to achieve. The title of this article mentions three things but actually there are four things we want to achieve:

  • Identification to determine who the person is who is accessing our app
  • Authentication to prove someone's identity
  • Authorization to assign privileges based on someone's identity
  • Auditing to keep a record of someone's activity

The first two things are closely related: we identify someone by their username (or email address)  but anybody can claim any identity so without proof this is almost useless. If we do not verify the claim, anybody can claim to be someone else.

Proving someone's identity is often based on asking for something that someone knows or has, for example a secret, like a password, or perhaps a fingerprint, and compare this to something we have on file, like a password database.

Once we know for sure that someone is who they claim to be, we can determine what they are allowed to do in our app. Sometimes this authorization is implicit, a user may only access their own information for example, sometimes this is role based authorization, where we assign a role to a user (like 'administrator' or 'regular user') and each role has a set of allowed actions. 

The final part of application access control is auditing: we might want to keep track of some or all activity of user, perhaps to spot suspicious behavior or simply to identify usage patterns and improve the app (the latter is often referred to as 'metrics' or 'instrumentation'.

Implementation

In practical terms that means that our app will need a login page to let people identify themselves and authenticate them using a password. In order to authorize activitities inside the app we need to keep track of them once they have succesfully logged in. And for good measure they also need to be able to log out.

To keep track we issue a session cookie once people are logged in. This cookie is an unguessable random number that we store in a database along with the username and send back in the response to the browser. The browser will subsequently send this cookie along with every request, so the only thing we have to do on the server side is to check if we have this session cookie in our database. If we do, we know the user and can determine if they are allowed to do what they are requesting. If we don't have the session cookie in our database we deny the request and redirect the user to the logon page.

Schematically this looks like the diagram below where the large blue arrows on the left depict access requests to html pages or resources.


Session lifecycle

Sessions do not live forever and the creation, expiration and deletion are depicted below


Of course there is more to this subject, like how to create new passwords and how to offer a reset option to a user, but that's for a future article.