HIGH cross site request forgeryaspnethmac signatures

Cross Site Request Forgery in Aspnet with Hmac Signatures

Cross Site Request Forgery in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Cross-Site Request Forgery (CSRF) in ASP.NET arises when an authenticated endpoint relies only on cookies for session identity without a cryptographic proof that the request intent originated from the legitimate client. HMAC signatures can mitigate this when implemented as a request-side binding between client and server, but if the implementation is incomplete, it can create or expose a CSRF surface.

Consider an ASP.NET Core API that uses anti-CSRF tokens in a custom header, such as X-Request-Signature, where the client computes an HMAC over selected request properties (HTTP method, path, selected headers, and a request body digest) using a shared secret. If the server validates the HMAC but does not enforce same-site cookie policies or does not bind the HMAC verification to the authenticated session (for example, by tying the signature to a per-session nonce or timestamp), an attacker can forge requests from a victim who is already authenticated via cookie-based session identifiers.

In a typical vulnerable pattern, the client sends an authenticated request with both the session cookie and the HMAC header. An attacker can construct a malicious page that invokes a state-changing endpoint (e.g., POST /api/account/transfer) with forged parameters. If the server validates the HMAC on the forged request and the shared secret is static or predictable, and if anti-forgery tokens or per-request nonces are absent, the server may treat the request as legitimate because the HMAC appears valid. This is especially risky when the HMAC covers only the body and not the full request context, enabling replay or parameter tampering if the attacker can guess or obtain a valid signature for a known request template.

Another exposure occurs when the HMAC is computed over a subset of request components. For instance, omitting the request path or a critical header from the signed payload can allow an attacker to alter the target URL or inject headers that change behavior, while still producing a valid HMAC if the server only checks the signature in isolation. In ASP.NET, if the developer uses a custom middleware that validates the HMAC but does not integrate with the framework’s anti-forgery mechanisms, or if the signature validation is applied only to certain HTTP methods, the unguarded methods remain vulnerable to CSRF.

LLM/AI Security relevance appears when endpoints accept user-influenced inputs that affect how the HMAC is computed or verified. For example, if an attacker can influence the set of signed headers or the body format through prompt injection techniques against an AI-assisted API gateway, they may reduce the effective scope of the HMAC and increase the likelihood of a successful CSRF. Therefore, the scope of what is included in the HMAC must be fixed and verified consistently for every request.

Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes

Remediation centers on ensuring the HMAC covers all aspects of the request that an attacker can influence, binding the signature to the authenticated session, and using constant-time comparison. Below are concrete ASP.NET code examples that demonstrate a robust approach.

First, define a service that computes and validates the HMAC using a server-side secret. The example uses HMAC-SHA256 and includes the HTTP method, request path, selected headers, and a hash of the body to prevent tampering.

using System.Security.Cryptography;
using System.Text;

public static class HmacHelper
{
    private static readonly byte[] Secret = Encoding.UTF8.GetBytes("REPLACE_WITH_STRONG_KEY_FROM_CONFIGURATION");

    public static string ComputeHash(HttpRequest request, string body)
    {
        using var hmac = new HMACSHA256(Secret);
        var path = request.Path.Value ?? string.Empty;
        var method = request.Method;
        // Include a timestamp or nonce in production to prevent replay
        var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
        var headersToSign = string.Join("|", request.Headers.Where(h => h.Key.Equals("X-Request-ID", StringComparison.OrdinalIgnoreCase)).Select(h => h.Value));
        var bodyHash = SHA256.HashData(Encoding.UTF8.GetBytes(body ?? string.Empty));
        var data = $"{method}:{path}:{timestamp}:{headersToSign}:{Convert.ToBase64String(bodyHash)}";
        var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
        return Convert.ToBase64String(hash);
    }

    public static bool VerifyHash(HttpRequest request, string body, string receivedHash, out string error)
    {
        error = string.Empty;
        var expected = ComputeHash(request, body);
        // Use CryptographicOperations.FixedTimeEquals to avoid timing attacks
        var received = Convert.FromBase64String(receivedHash);
        var expectedBytes = Encoding.UTF8.GetBytes(expected);
        if (received.Length != expectedBytes.Length || !CryptographicOperations.FixedTimeEquals(received, expectedBytes))
        {
            error = "Invalid signature";
            return false;
        }
        return true;
    }
}

In the controller, read the body with buffering enabled so it can be inspected for the signature and then replayed for model binding. Configure the pipeline to allow the middleware to inspect the request body without interfering with model binding.

using Microsoft.AspNetCore.Mvc;
using System.IO;
using System.Text;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class TransferController : ControllerBase
{
    [HttpPost]
    public async Task Transfer()
    {
        // Enable buffering so the body can be read multiple times
        Request.EnableBuffering();
        var body = new StreamReader(Request.Body, Encoding.UTF8, leaveOpen: true).ReadToEnd();
        Request.Body.Position = 0; // Reset for model binding or downstream middleware

        if (!Request.Headers.TryGetValue("X-Request-Signature", out var signature))
        {
            return Unauthorized("Missing signature");
        }

        if (!HmacHelper.VerifyHash(Request, body, signature, out var error))
        {
            return BadRequest(error);
        }

        // At this point, the request identity and integrity are verified.
        // Proceed with anti-forgery token validation if using cookies.
        // Example: ValidateAntiForgeryToken() for MVC, or custom checks for APIs.

        return Ok(new { Message = "Request validated" });
    }
}

To bind the HMAC to the authenticated session, include a per-session nonce or the session identifier (e.g., the session cookie value) in the signed payload. This ensures that a signature generated for one session cannot be reused by an attacker who can only control unauthenticated requests.

In the client, ensure the HMAC is computed consistently with the server. For example, a JavaScript client can use the Web Crypto API to generate the signature and include it in the X-Request-Signature header. The client must include the same headers and body formatting as the server to produce a matching HMAC.

Finally, apply defense-in-depth by enabling anti-forgery tokens for cookie-based authentication in ASP.NET and enforcing SameSite cookie attributes. This layered approach ensures that even if the HMAC scope is misconfigured, the request is still protected by the framework’s built-in anti-CSRF measures.

Frequently Asked Questions

Can HMAC headers alone prevent CSRF in ASP.NET?
HMAC headers reduce CSRF risk only if the signature covers all attacker-influenced parts of the request and is bound to the authenticated session. Without session binding and strict header/body inclusion, cookies alone can still enable CSRF.
What should be included in the HMAC payload to avoid replay and parameter tampering?
Include the HTTP method, request path, a timestamp or nonce, selected headers, and a hash of the request body. This prevents replay and ensures tampering with any signed component invalidates the signature.