HIGH replay attackaspnethmac signatures

Replay Attack in Aspnet with Hmac Signatures

Replay Attack in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A replay attack occurs when an attacker intercepts a valid request and retransmits it to reproduce the intended effect. In ASP.NET applications that use HMAC signatures for request integrity, a common vulnerability arises when the server validates the signature but does not adequately prevent replays. HMAC ensures that the payload and selected headers have not been altered, yet if the server does not track or reject previously seen requests, an attacker can capture a signed request (for example, a payment initiation or a state-changing POST) and resend it to the same endpoint.

The risk is particularly pronounced when the HMAC is computed over a static subset of the request—such as HTTP method, path, and body—without including a nonce or a timestamp. Without a nonce, two identical requests produce the same HMAC, making replay indistinguishable from the original legitimate call. Without a timestamp or short-lived window, there is no built-in expiration, allowing captured requests to remain valid for extended periods. Even when a timestamp is included, if the server does not enforce a tight tolerance and does not maintain a short-term cache of recently seen nonces, an attacker can slightly adjust the timestamp within the acceptable window and successfully replay the request.

In ASP.NET, this often maps to the OWASP API Top 10 category '2023-A1: Broken Object Level Authorization' and '2023-A7: Rate Limiting & DoS' when replay is used to escalate privileges or exhaust operations. For example, an attacker could replay a signed request that transfers funds or modifies a user’s email. Because HMAC confirms integrity but not freshness or uniqueness, the server may incorrectly assume that a valid signature implies a new, authorized action. This highlights why HMAC must be paired with replay controls—such as a server-side nonce store with TTL or a strict timestamp window—to ensure that each signed request is processed at most once within an acceptable time bound.

Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes

To mitigate replay attacks in ASP.NET when using HMAC signatures, you must ensure that each request includes a mechanism for uniqueness and freshness, and that the server tracks and rejects replays. Below are concrete, secure patterns and code examples.

  • Include a nonce and timestamp in the HMAC scope

The HMAC should cover the HTTP method, the request path, the body, a nonce, and an expiration timestamp. This binds uniqueness and freshness to the signature. Require the client to send the nonce and timestamp in headers (e.g., X-Request-Nonce and X-Timestamp), and include them when computing and verifying the HMAC.

// Example: computing HMAC on the server (C#)
using System.Security.Cryptography;
using System.Text;

public static string ComputeHmac(string httpMethod, string path, string body, string nonce, string timestamp, string secret)
{
    var data = $"{httpMethod}|{path}|{body}|{nonce}|{timestamp}";
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
    return Convert.ToBase64String(hash);
}
  • Validate freshness and reject expired requests

Enforce a tight tolerance window (e.g., 2 minutes) for the timestamp. Reject requests with timestamps outside this window to prevent reuse of captured requests across time.

// Example: timestamp validation (C#)
public static bool IsTimestampValid(string timestamp, int toleranceSeconds = 120)
{
    if (!long.TryParse(timestamp, out var ts))
        return false;
    var requestTime = DateTimeOffset.FromUnixTimeSeconds(ts).UtcDateTime;
    var now = DateTime.UtcNow;
    var diff = now - requestTime;
    return Math.Abs(diff.TotalSeconds) <= toleranceSeconds;
}
  • Use a server-side nonce cache to ensure one-time use

Maintain a short-lived cache (e.g., in-memory or distributed cache with TTL slightly longer than the timestamp tolerance) keyed by the nonce. Before processing a request, check whether the nonce has been seen; if so, reject the request as a potential replay.

// Example: nonce replay protection (C#)
using System.Collections.Concurrent;

public class NonceCache
{
    private readonly ConcurrentDictionary _seenNonces = new();
    private readonly TimeSpan _nonceTtl;

    public NonceCache(TimeSpan nonceTtl)
    {
        _nonceTtl = nonceTtl;
    }

    public bool TryAddNonce(string nonce)
    {
        // TryAdd ensures idempotent addition; returns false if key already exists
        return _seenNonces.TryAdd(nonce, 0);
    }

    // Call this periodically via a timer or background service to evict old entries
    public void CleanupExpired(IEnumerable expiredKeys) 
    {
        foreach (var key in expiredKeys) 
        {
            _seenNonces.TryRemove(key, out _);
        }
    }
}
  • Full verification example in middleware

Integrate validation and nonce checks into ASP.NET middleware to protect endpoints consistently. This example shows a simplified pipeline step that verifies HMAC, timestamp, and nonce before allowing the request to proceed.

// Example: middleware snippet (C#)
public class HmacValidationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly string _secret;
    private readonly NonceCache _nonceCache;

    public HmacValidationMiddleware(RequestDelegate next, string secret, NonceCache nonceCache)
    {
        _next = next;
        _secret = secret;
        _nonceCache = nonceCache;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue("X-Request-Nonce", out var nonceValues) ||
            !context.Request.Headers.TryGetValue("X-Timestamp", out var timestampValues))
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("Missing nonce or timestamp");
            return;
        }

        var nonce = nonceValues.FirstOrDefault();
        var timestamp = timestampValues.FirstOrDefault();
        if (string.IsNullOrEmpty(nonce) || string.IsNullOrEmpty(timestamp))
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("Invalid nonce or timestamp");
            return;
        }

        if (!IsTimestampValid(timestamp, toleranceSeconds: 120))
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsync("Request expired");
            return;
        }

        if (!_nonceCache.TryAddNonce(nonce))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Replay detected");
            return;
        }

        // Compute expected HMAC using the request body and headers included in the signature
        var body = await new StreamReader(context.Request.Body).ReadToEndAsync();
        context.Request.Body.Position = 0; // reset for downstream middleware

        var computed = ComputeHcontext.Request.Method, context.Request.Path.Value ?? "", body, nonce, timestamp, _secret);
        var provided = context.Request.Headers["X-Request-Signature"].FirstOrDefault();
        if (string.IsNullOrEmpty(provided) || !CryptographicOperations.FixedTimeEquals(Convert.FromBase64String(provided), Convert.FromBase64String(computed)))
        {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Invalid signature");
            return;
        }

        await _next(context);
    }
}

Frequently Asked Questions

Why does including a nonce and timestamp in the HMAC scope prevent replay attacks in ASP.NET?
Including a nonce (unique per request) and a timestamp (indicating when the request was created) in the HMAC scope ensures that two identical requests produce different signatures. The server can reject requests with duplicate nonces and can enforce a tight timestamp window, making captured requests invalid if reused.
What is the purpose of server-side nonce caching in Hmac replay protection for ASP.NET APIs?
Server-side nonce caching records recently used nonces for a duration slightly longer than the timestamp tolerance. Before processing a request, the server checks the cache; if the nonce is already present, the request is rejected as a replay. This prevents an attacker from resending a valid, signed request even if the timestamp is within the allowed window.