Rate Limiting Bypass in Aspnet with Hmac Signatures
Rate Limiting Bypass in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
In ASP.NET APIs that use HMAC signatures for request authentication, rate limiting can be inadvertently bypassed when the signature is computed over a subset of the request that does not include a nonce or a per-request unique value. Without a unique element per request, identical requests produce identical signatures, enabling an attacker to reuse a signed payload to circumvent rate limits that rely only on endpoint paths or IP-based counters.
The vulnerability occurs because the rate limiter and the HMAC verifier operate on different dimensions of the request. The rate limiter may track requests by URL + IP, while the HMAC validation ensures integrity and authenticity of a known set of headers and the body. If the signed set excludes a timestamp or nonce, an attacker can repeatedly send the exact same signed request within the rate limit window, and the API will accept each copy as valid. This is especially effective when the API tolerates replay windows or does not enforce one-time-use semantics for the signed components.
Consider an endpoint that accepts financial commands. The client computes the HMAC over HTTP method, path, selected headers, and the JSON body, then sends the signature in a custom header. If the body does not contain a client-generated nonce or a strictly increasing timestamp, an attacker can capture a valid request/response pair and replay the exact same HTTP message. The server validates the signature successfully and processes the command, while the rate limiter counts only one request if it is configured to inspect the signature context or if the limit is applied after authentication rather than before. This mismatch between authentication and rate-limiting scope allows abuse such as transaction duplication or brute-force attempts masked as legitimate traffic.
Insecure design patterns exacerbate the issue. For example, using a static or session-level key without per-request randomness, including only stable headers in the signature, or failing to bind the signature to a short time window can turn HMAC-based authentication into a vector for bypassing rate controls. Attack patterns include signature replay, where captured traffic is repeated within the allowed time window, and algorithmic abuse, where the client probes acceptable skew to synchronize clocks and avoid rejection.
To detect this class of issue, scanners evaluate whether the signed request scope includes a unique or monotonic element, whether the server enforces replay protection, and whether rate limiting is applied consistently across authenticated and unauthenticated paths. Findings highlight missing nonce usage, weak time synchronization, and inconsistent limit application as risk factors that can degrade the security grade assigned to the endpoint.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
Remediation centers on ensuring that each signed request carries a unique, verifiable value and that rate limiting considers the full authenticated context. In ASP.NET, this means including a nonce or a strictly increasing timestamp in the data covered by the HMAC, validating freshness on the server, and applying rate limits before or in conjunction with authentication checks.
Below is a complete, syntactically correct example of a server-side HMAC validation in ASP.NET Core that incorporates a nonce and timestamp to prevent replay and support accurate rate limiting. The client must generate a nonce and include it in the JSON body and in the signature base string.
// Server-side HMAC validation in ASP.NET Core middleware or action filter
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
public class HmacValidationFilter : IAsyncActionFilter
{
private readonly string _apiSecret;
private const int ClockSkewSeconds = 30;
public HmacValidationFilter(IOptions<HmacOptions> options)
{
_apiSecret = options.Value.ApiSecret;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
if (!context.HttpContext.Request.Headers.TryGetValue("X-API-Signature", out var signatureHeader))
{
context.Result = new UnauthorizedResult();
return;
}
if (!context.HttpContext.Request.Headers.TryGetValue("X-API-Nonce", out var nonceHeader) ||
!context.HttpContext.Request.Headers.TryGetValue("X-API-Timestamp", out var timestampHeader))
{
context.Result = new UnauthorizedResult();
return;
}
var nonce = nonceHeader.FirstOrDefault();
var timestampString = timestampHeader.FirstOrDefault();
if (string.IsNullOrWhiteSpace(nonce) || string.IsNullOrWhiteSpace(timestampString))
{
context.Result = new UnauthorizedResult();
return;
}
if (!long.TryParse(timestampString, out var timestamp))
{
context.Result = new UnauthorizedResult();
return;
}
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (Math.Abs(now - timestamp) > ClockSkewSeconds)
{
context.Result = new UnauthorizedResult();
return;
}
// Read the request body as a string for signing; ensure it can be read again if needed
context.HttpContext.Request.EnableBuffering();
using var reader = new StreamReader(context.HttpContext.Request.Body, Encoding.UTF8, leaveOpen: true);
var body = await reader.ReadToEndAsync();
context.HttpContext.Request.Body.Position = 0;
var payload = $"{context.HttpContext.Request.Method}:{context.HttpContext.Request.Path}:{nonce}:{timestamp}:{body}";
var computedSignature = ComputeHmacSha256(payload, _apiSecret);
if (!computedSignature.Equals(signatureHeader.FirstOrDefault(), StringComparison.OrdinalIgnoreCase))
{
context.Result = new UnauthorizedResult();
return;
}
// Optionally track nonce/timestamp to prevent reuse (e.g., in a distributed cache)
await next();
}
private static string ComputeHmacSha256(string message, string secret)
{
using var key = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
using var hm = key;
var hash = hm.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(hash);
}
}
public class HmacOptions
{
public string ApiSecret { get; set; }
}
Client-side signing example that includes a nonce and timestamp ensures each request is unique. This makes replayed signatures detectable and allows the server to enforce per-request freshness checks, which in turn lets rate limiters use the authenticated identity plus nonce or timestamp to scope limits accurately.
// Client-side signing in C#
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
public static class HmacClient
{
public static string ComputeSignature(string method, string path, string nonce, long timestamp, object body, string secret)
{
var bodyJson = JsonSerializer.Serialize(body);
var payload = $"{method}:{path}:{nonce}:{timestamp}:{bodyJson}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return Convert.ToBase64String(hash);
}
}
// Usage
var nonce = Guid.NewGuid().ToString();
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var body = new { accountId = "123", amount = 100 };
var signature = HmacClient.ComputeSignature("POST", "/api/transfer", nonce, timestamp, body, "my-secret");
// Send request with headers:
// X-API-Signature: {signature}
// X-API-Nonce: {nonce}
// X-API-Timestamp: {timestamp}
// Content-Type: application/json
// Body: {"accountId":"123","amount":100}
To bind rate limiting to the authenticated context, configure the rate limiter to consider the combination of user identity (or API key), nonce, or timestamp. In ASP.NET Core, the policy can inspect the validated claims or a cache of recently used nonces to reject duplicates within a sliding window. This ensures that even if an attacker bypasses simple IP-based counters, each unique signed request is still subject to enforceable limits.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |