Published: June 03 2015

C# - Incremental Delay to Prevent Brute Force or Dictionary Attack

This post contains an example of how to implement an incremental delay in C# to prevent brute force or dictionary attacks on an MVC login form, the code sample is from an ASP.NET MVC 5 web application that I recently developed for a law firm client in Sydney Australia.

Brute Force / Dictionary Attack

A brute force or dictionary attack is where a program or script is run by a hacker against the login page of a website, the program attempts to guess usernames and passwords over and over again until it finds one that works, it's possible for a program to guess tens of thousands to millions of combinations in a relatively short amount of time if the right measures aren't in place to prevent it.

Incremental Delay

An incremental delay is used to cause an exponentially increasing delay between failed login attempts, for example after the first failed login attempt there is a 1 second delay, after the second there is a 2 second delay, then 4, 8, 16, 32 and so on. This simple defense can reduce the number of guessed login attempts possible by a hacker from thousands per minute down to only a few before the delay becomes so long as to make it a pointless exercise, after 20 failed login attempts the delay is 6 days!

The Code

This sample code is from the Login action of an ASP.NET MVC controller, it uses async and await with Task.Delay() to perform the delay asynchronously without blocking the current thread on the web server. It's also possible to use Thread.Sleep() however this approach blocks the web server thread until the delay is completed which is bad for performance.

[HttpPost]
public async Task<ActionResult> Login(LoginViewModel viewModel, string returnUrl)
{
    // incremental delay to prevent brute force attacks
    int incrementalDelay;
    if (HttpContext.Application[Request.UserHostAddress] != null)
    {
        // wait for delay if there is one
        incrementalDelay = (int)HttpContext.Application[Request.UserHostAddress];
        await Task.Delay(incrementalDelay * 1000);
    }

    if (!ModelState.IsValid)
        return View();

    // authenticate user
    var user = _userService.Authenticate(viewModel.Username, viewModel.Password);

    if (user == null)
    {
        // login failed

        // increment the delay on failed login attempts
        if (HttpContext.Application[Request.UserHostAddress] == null)
        {
            incrementalDelay = 1;
        }
        else
        {
            incrementalDelay = (int)HttpContext.Application[Request.UserHostAddress] * 2;
        }
        HttpContext.Application[Request.UserHostAddress] = incrementalDelay;

        // return view with error
        ModelState.AddModelError("", "The user name or password provided is incorrect.");
        return View();
    }

    // login success

    // reset incremental delay on successful login
    if (HttpContext.Application[Request.UserHostAddress] != null)
    {
        HttpContext.Application.Remove(Request.UserHostAddress);
    }

    // set authentication cookie
    _formsAuthenticationService.SetAuthCookie(
        user.Username,
        viewModel.KeepMeLoggedIn,
        null);

    // redirect to returnUrl
    return Redirect(returnUrl);
}

 


Need Some ASP.NET Help?

Search fiverr for freelance ASP.NET developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by