Credential Stuffing in Aspnet with Hmac Signatures
Credential Stuffing in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Credential stuffing relies on automated requests using stolen username and password pairs. In ASP.NET applications that use HMAC signatures to authenticate requests, a common misconfiguration can weaken the protection that HMAC is intended to provide. HMAC is designed to ensure integrity and authenticity of a request by signing a combination of headers, body, and a secret key. If the server validates the signature but does not sufficiently bind the authentication state to the signed context, an attacker can reuse captured signed requests in a credential stuffing campaign.
Consider an ASP.NET Core endpoint that accepts a JSON payload and an HMAC-SHA256 signature passed in a custom header. If the server verifies the signature and then relies on a separate session cookie or token that is not included in the HMAC computation, an attacker can replay a previously signed request with different credential pairs. The signature remains valid because the payload and headers used to generate it have not changed, but the server may treat the request as authenticated once the user context is established via the session cookie. This mismatch allows attackers to automate login attempts using valid HMAC-signed requests while systematically trying different username and password combinations.
A concrete example is an endpoint that signs the request body but does not include a nonce or a timestamp. Without a nonce or timestamp, the same signed request can be replayed indefinitely. In a credential stuffing scenario, attackers use botnets to rotate through proxies and user agents, submitting the same signed payload with different credentials. If rate limiting is weak or absent, and if the application does not tie the HMAC validation to a per-request context, the attack can proceed undetected. The application may log successful authentication events while attributing them to the legitimate signer, masking abuse. This is particularly dangerous when the HMAC key is static and the signing logic does not incorporate per-request variability, enabling scalable, automated abuse.
Additionally, if the server uses HMAC to sign responses but does not enforce strict validation of the request context before performing sensitive operations, attackers can exploit this gap. For instance, an endpoint that changes a user’s email or password may rely on HMAC for integrity but fail to verify that the authenticated identity matches the subject of the request. In such cases, credential stuffing becomes a viable path to privilege escalation or account takeover, especially when attackers already possess a list of known breached credentials.
To detect this class of issues, scanning tools evaluate whether HMAC usage is coupled with replay protection and whether authentication decisions depend on data included in the signature. They also check whether the server validates the scope of what is signed and whether per-request randomness or binding is enforced. Without these controls, HMAC can give a false sense of security while credential stuffing attacks proceed against the application’s login surfaces.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
Remediation focuses on ensuring that HMAC validation is tightly coupled with authentication context and that replay attacks are prevented. The following code examples demonstrate a secure pattern in ASP.NET Core that includes a timestamp, a nonce, and the authenticated user identity in the signed payload.
Example 1: Generating and validating HMAC with nonce and timestamp
using System;
using System.Security.Cryptography;
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Http;
public static class HmacHelper
{
private const int NonceLength = 16;
private static readonly TimeSpan ClockSkew = TimeSpan.FromMinutes(5);
public static string ComputeHash(string message, string secret)
{
using var hmac = new HMACSHA256(Convert.FromBase64String(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(hash);
}
public static string BuildSignedPayload(HttpContext context, string body, string nonce, string timestamp)
{
var userId = context.User.Identity?.Name ?? string.Empty;
var scope = $"{context.Request.Method}:{context.Request.Path}:{userId}:{nonce}:{timestamp}:{body}";
return scope;
}
public static (string nonce, string timestamp) GenerateNonceAndTimestamp()
{
var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(NonceLength));
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
return (nonce, timestamp);
}
public static bool ValidateRequest(HttpContext context, string receivedSignature, string body, string secret)
{
var timestamp = context.Request.Headers["X-Timestamp"];
var nonce = context.Request.Headers["X-Nonce"];
if (string.IsNullOrEmpty(timestamp) || string.IsNullOrEmpty(nonce))
{
return false;
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestTimestamp = long.Parse(timestamp);
if (Math.Abs(now - requestTimestamp) > ClockSkew.TotalSeconds)
{
return false;
}
var expectedScope = BuildSignedPayload(context, body, nonce, timestamp);
var expectedSignature = ComputeHash(expectedScope, secret);
return CryptographicOperations.FixedTimeEquals(
Convert.FromBase64String(expectedSignature),
Convert.FromBase64String(receivedSignature));
}
}
In this example, the signed scope includes the HTTP method, request path, authenticated user name, a nonce, a timestamp, and the request body. By incorporating the user identity and a one-time nonce, the signature becomes unique per request and per user, which prevents replay across different authentication contexts. The timestamp allows the server to reject requests with stale timestamps, mitigating replay windows.
Example 2: Middleware to enforce per-request HMAC validation
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
public class HmacValidationMiddleware
{
private readonly RequestDelegate _next;
private const string Secret = "BASE64_ENCODED_SHARED_SECRET";
public HmacValidationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Method == "POST" || context.Request.Method == "PUT" || context.Request.Method == "PATCH")
{
using var reader = new StreamReader(context.Request.Body);
var body = await reader.ReadToEndAsync();
context.Request.Body.Position = 0;
var signature = context.Request.Headers["X-Hmac-Signature"];
if (!HmacHelper.ValidateRequest(context, signature, body, Secret))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Invalid signature");
return;
}
}
await _next(context);
}
}
This middleware validates the HMAC for state-changing HTTP methods before the request reaches the controller. It reads the raw body, extracts the signature and metadata headers, and ensures they match before proceeding. By validating early, the middleware prevents unauthorized actions even if downstream logic mistakenly trusts headers or cookies alone.
Additional measures include binding HMAC validation to anti-CSRF tokens for browser-originated requests, rotating secrets periodically, and logging failed validation events for anomaly detection. These steps reduce the effectiveness of credential stuffing when attackers attempt to reuse previously captured signed requests.