2 min read

Enumeration Attacks are a type of attack in which the attacker tries to guess or validate a data set with the goal of extracting more information than they had to start out. For example, finding out if a user is a member of the site by trying to log in with millions of email addresses and checking if the page responds differently to an account existing but with a bad password vs not existing in the system.

Also, check out Authentication Ideas for some things you can do to protect your login page.

Protect Against Enumeration Attacks in C#

How an Enumeration Attack Works

Enumeration is a type of cyber attack that involves using trial and error to find valid usernames or passwords, typically by trying many different combinations.

Enumeration attacks are often used in brute force attacks. The attacker uses a program to automatically try different passwords until they find the right one. The more guesses an attacker can make, the more likely they are to succeed at their goal.

How to Prevent and Mitigate an Enumeration Attack In C#

The first step in defending against enumeration attacks is making it infeasible to extract the data they’re after by limiting the number of attempts they can make, and monitoring user activity closely so you know when someone might be trying to break. This could be a limit on the number of attempts per account for login, or from a given IP address. Google Captcha and Cloudflare bot fighting mode can assist in preventing automated attempts from being made.

We’ll use a simple request throttling mechanism, along with an artificial delay to slow down the number of attempts an attacker can make:

private static ConcurrentDictionary<string, bool> RunningRequests = new ConcurrentDictionary<string, bool>();

Next, we’ll try to add the user’s IP Address to the list of running requests. Because it’s a Concurrent Dictionary, if they’re already running a request, then the TryAdd will fail, and we’ll just wait 3 seconds before trying again

while (!RunningRequests.TryAdd(HttpContext.Connection.RemoteIpAddress.ToString(), true))
    await Task.Delay(3000, ct);

Next, we’ll wait 3 seconds regardless of the previous one, which covers the case of the user making the first request

await Task.Delay(3000, ct);

We’ll want to do the above in a try/catch block, and then remove the user’s IP address from the list in the finally block. Otherwise, the user could be stuck not being able to make more requests.

finally
{
    RunningRequests.Remove(HttpContext.Connection.RemoteIpAddress.ToString(), out var _);
}

Then finally we’ll continue with our business logic.

Full code

try
{
    using (var client = _clientFactory.CreateClient("captcha"))
    {
        var response = await client.PostAsync("recaptcha/api/siteverify", new FormUrlEncodedContent(new Dictionary<string, string> { { "secret", _captchaSettings.Value.Secret }, { "response", model.Token }, { "remoteip", Request.HttpContext.Connection.RemoteIpAddress?.ToString() } }), ct);
        response.EnsureSuccessStatusCode();

        var captchaResponse = await response.Content.ReadAsAsync<CaptchaResponse>(ct);
        if (!captchaResponse.Success)
        {
            ModelState.AddModelError(nameof(model.Email), "Invalid captcha");
            _logger.LogWarning($"Invalid captcha doing business logic - {model.Email} - {captchaResponse.Hostname}");

            return ValidationProblem(ModelState);
        }
    }

    while (!RunningRequests.TryAdd(HttpContext.Connection.RemoteIpAddress.ToString(), true))
        await Task.Delay(3000, ct);

    await Task.Delay(3000, ct);

    //business logic
}
catch (Exception ex)
{
    _logger.LogError(ex, "Error doing the business logic");
    throw;
}
finally
{
    RunningRequests.Remove(HttpContext.Connection.RemoteIpAddress.ToString(), out var _);
}

return Json(results);

Monitoring and visibility into what’s going on are key. Check out Observability using Elasticsearch and Neo4j for some ideas