HIGH race conditionaspnethmac signatures

Race Condition in Aspnet with Hmac Signatures

Race Condition in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability

A race condition in ASP.NET when HMAC signatures are used for request authentication typically occurs between the time a signature is generated on the client and the time the server validates it. If the server relies on a changing value—such as a nonce, timestamp, or one-time key—to validate the HMAC, an attacker can exploit timing differences or replace the changing value after signature generation to cause the server to accept a tampered request.

Consider an endpoint that expects an X-Timestamp, a nonce, and an HMAC covering the payload plus that timestamp. The client computes the signature, sends the request, and an attacker intercepts or replays it. If the server-side validation logic first checks the timestamp for staleness and then recomputes the HMAC using the received timestamp, an attacker can repeatedly send the same request with slightly updated timestamps within the acceptable window. Because the checks are not performed atomically with respect to a server-side ledger of already-seen nonces or timestamps, the server may accept a request that should have been rejected due to replay.

In ASP.NET, this is often seen when anti-replay protections are implemented manually (for example, by storing recent timestamps or nonces in memory or in a cache) without proper synchronization or atomicity. If two requests arrive concurrently—one legitimate and one forged—but the validation logic reads and updates the seen-nonce cache in multiple steps, an attacker may manipulate timing to slip a forged request through between the read and the write. The vulnerability is not in HMAC itself—HMAC is cryptographically sound—but in how the application coordinates the mutable state used during validation.

Another scenario involves the use of a rolling key or a key derived from a time-based value where the server allows a small skew window. If the server does not ensure that a given key or nonce is processed only once within that window, an attacker who captures a valid request can forward it within the skew window while the server is still processing or recording that same key/nonce pair. Because the HMAC verifies integrity and authenticity, but does not automatically prevent reuse, the race resides in the application’s state management and not in the cryptographic primitive.

To detect this pattern, middleBrick’s checks include API Security tests for BOLA/IDOR, Input Validation, and unsafe consumption patterns that can expose timing or state-handling issues. The scanner does not assume an internal implementation, but it flags endpoints where replay or timing-related risks are suggested by the observable behavior, such as missing nonce enforcement or inconsistent timestamp validation.

Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes

Remediation focuses on making validation atomic, ensuring each signed token or nonce is used at most once within the allowed time window, and avoiding mutable shared state that can lead to race conditions.

  • Use a monotonic server-side store with atomic checks: store nonces or timestamps in a structure that guarantees atomic add-and-check operations to prevent reuse between concurrent requests.
  • Validate timestamp and nonce inside a single synchronized block or by using a thread-safe cache with add-if-absent semantics to eliminate the read–modify–write window.
  • Prefer short, server-side clock skew windows and bind the nonce or timestamp tightly to the principal and the request context so that reuse across users or sessions is impossible.

Example: secure HMAC validation in ASP.NET Core using a concurrent cache with AddOrUpdate semantics to ensure a nonce is processed only once. The server computes the expected HMAC and validates it within a synchronized check, returning 401 if the nonce has already been seen or the timestamp is outside the allowed window.

using System;
using System.Collections.Concurrent;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;

public class HmacValidationMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ConcurrentDictionary _seenNonces = new ConcurrentDictionary();
    private readonly TimeSpan _clockSkew = TimeSpan.FromMinutes(5);
    private readonly byte[] _serverKey;

    public HmacValidationMiddleware(RequestDelegate next, byte[] serverKey)
    {
        _next = next;
        _serverKey = serverKey ?? throw new ArgumentNullException(nameof(serverKey));
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Headers.TryGetValue("X-Api-Key", out var apiKeyHeader)
            || !context.Request.Headers.TryGetValue("X-Timestamp", out var timestampHeader)
            || !context.Request.Headers.TryGetValue("X-Nonce", out var nonceHeader)
            || !context.Request.Headers.TryGetValue("X-Signature", out var signatureHeader))
        {
            context.Response.StatusCode = 400;
            return;
        }

        if (!long.TryParse(timestampHeader, out var timestampTicks))
        {
            context.Response.StatusCode = 400;
            return;
        }

        var requestTime = new DateTime(timestampTicks, DateTimeKind.Utc);
        var now = DateTime.UtcNow;
        if (Math.Abs((now - requestTime).TotalMinutes) > _clockSkew.TotalMinutes)
        {
            context.Response.StatusCode = 401;
            return;
        }

        // Atomic check-and-add to prevent replay within server-side window
        if (!_seenNonces.TryAdd(nonceHeader, 0))
        {
            context.Response.StatusCode = 401;
            return;
        }

        var computedSignature = ComputeHmac(apiKeyHeader, timestampHeader, nonceHeader, await new StreamReader(context.Request.Body).ReadToEndAsync());
        if (!computedSignature.Equals(signatureHeader, StringComparison.Ordinal))
        {
            context.Response.StatusCode = 401;
            return;
        }

        await _next(context);
    }

    private string ComputeHmac(string apiKey, string timestamp, string nonce, string body)
    {
        using var hmac = new HMACSHA256(Convert.FromBase64String(_serverKey));
        var data = $"{apiKey}:{timestamp}:{nonce}:{body}";
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
        return Convert.ToBase64String(hash);
    }
}

In this pattern, _seenNonces.TryAdd is atomic, which prevents two concurrent requests with the same nonce from both passing validation. The timestamp check is performed before the nonce add to avoid unnecessary state updates for clearly stale requests. For production, back the nonce store with a distributed cache if you run multiple server instances, ensuring atomicity across the cluster.

Additionally, prefer standard schemes like Digest or Bearer with HMAC-based signatures where the server can verify integrity without mutable server-side state—for example, using a hash-based message authentication code that includes a server-side key and a client-supplied nonce/timestamp, validated in one step. MiddleBrick’s scans verify that such nonce/timestamp handling is present and that replay protections are observable in the API behavior.

Frequently Asked Questions

Why does a race condition appear only when HMAC validation is split across multiple steps in ASP.NET?
Because non-atomic read–modify–write operations on shared state (e.g., a list of seen nonces) allow an attacker to interleave requests so that a forged request passes a check after the legitimate request has updated—but not yet recorded—the state.
Can using HTTPS alone prevent replay or race condition attacks involving HMAC signatures?
No. HTTPS protects confidentiality and integrity in transit, but replay and race conditions are application-level logic issues. Without atomic, idempotent validation and anti-replay state management, an attacker can reuse or manipulate requests even when they are encrypted.