Unicode Normalization in Aspnet with Hmac Signatures
Unicode Normalization in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
In ASP.NET applications, HMAC signatures are commonly used to ensure the integrity of HTTP requests by signing a canonical representation of the data being transmitted. Unicode normalization becomes critical when the data being signed includes text that can be represented in multiple equivalent Unicode forms. For example, the character "é" can be encoded as a single code point U+00E9 or as a combination of "e" and a combining acute accent U+0065 U+0301. If the signer and verifier do not normalize the input to the same Unicode form, the byte sequences used to compute the HMAC will differ, causing signature validation to fail even when the semantically identical content is being compared.
ASP.NET Core model binding and parameter parsing can introduce subtle normalization gaps. When an API endpoint accepts user-controlled string inputs—such as a username, resource identifier, or token—and uses those strings directly to compute an HMAC without normalization, an attacker can supply visually identical but differently encoded characters to bypass signature validation or trigger inconsistent behavior. This is especially relevant when the signature covers query parameters, header values, or JSON payload fields that are later compared against a stored canonical signature.
Consider an endpoint that expects an HMAC in a custom header and computes the signature over selected request properties. If the implementation concatenates string values directly (e.g., using string interpolation or StringBuilder) without applying a consistent normalization form like FormC (Canonical Composition), two requests that are semantically identical but differ only in Unicode composition will produce different HMAC outputs. The server will reject one of them, which can lead to authentication bypass in certain edge cases or, more commonly, to denial of service through signature mismatch errors that may be mishandled.
The interaction with ASP.NET’s normalization settings is nuanced. By default, ASP.NET does not enforce Unicode normalization on incoming request data. If the application layer does not explicitly normalize inputs before signing, the HMAC computation will reflect the raw bytes received over the wire. This creates a vulnerability where an attacker can probe normalization-sensitive endpoints with equivalent but differently encoded payloads to observe timing differences, error messages, or behavior changes that leak information about the signing logic.
Real-world examples include endpoints that sign identifiers containing non-ASCII characters, such as email addresses or API keys that permit Unicode characters. Without normalization, an attacker could register an account with a decomposed form of a character and then supply the precomposed form in requests, potentially evading signature checks if the comparison logic is inconsistent. The issue is compounded when the signature also covers metadata like timestamps or nonces that are concatenated without normalization, enabling subtle replay or integrity bypass techniques.
To detect this class of issue during scanning, middleBrick performs black-box tests that submit semantically identical requests with varied Unicode representations and compares the resulting HMAC validation outcomes. It checks whether the application consistently applies normalization before signing and whether error handling diverges based on encoding differences. Findings from such checks are included in the scan report with severity ratings and remediation guidance, helping teams identify normalization-related weaknesses in HMAC-based schemes.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
To mitigate Unicode normalization issues in ASP.NET when using HMAC signatures, enforce a consistent normalization form before computing or verifying the signature. The recommended approach is to normalize all string inputs to FormC (Canonical Composition) using .NET's String.Normalize method with NormalizationForm.FormC. This ensures that semantically identical text always results in the same byte sequence for HMAC computation.
Below is a complete example of a middleware component that normalizes selected request components before computing an HMAC signature. The example assumes a custom header X-Data-Signature that covers a subset of request data and demonstrates how to compute and validate the signature safely.
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Http;
public class HmacNormalizationMiddleware
{
private readonly RequestDelegate _next;
private readonly byte[] _secretKey;
public HmacNormalizationMiddleware(RequestDelegate next, byte[] secretKey)
{
_next = next;
_secretKey = secretKey;
}
public async Task InvokeAsync(HttpContext context)
{
// Normalize inputs before building the signing string
string userId = (context.Request.Query["userId"]).ToString().Normalize(NormalizationForm.FormC);
string action = context.Request.Query["action"].ToString().Normalize(NormalizationForm.FormC);
string timestamp = context.Request.Query["timestamp"].ToString().Normalize(NormalizationForm.FormC);
string dataToSign = $"{userId}|{action}|{timestamp}";
string computedSignature = ComputeHmac(dataToSign, _secretKey);
string providedSignature = context.Request.Headers["X-Data-Signature"].ToString();
if (!CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(computedSignature),
Encoding.UTF8.GetBytes(providedSignature)))
{
context.Response.StatusCode = 401;
await context.Response.WriteAsync("Invalid signature");
return;
}
await _next(context);
}
private static string ComputeHmac(string data, byte[] key)
{
using var hmac = new HMACSHA256(key);
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
return Convert.ToBase64String(hash);
}
}
In this example, each input extracted from the query string is normalized to FormC before concatenation. The use of CryptographicOperations.FixedTimeEquals prevents timing attacks during signature comparison. For JSON payloads, normalize string values within the object before serialization, or normalize the serialized JSON string if the signature covers the raw bytes.
When using ASP.NET Core data protection APIs, ensure that any user-controlled strings included in the protected payload are normalized before invoking Protect or Unprotect. Similarly, when signatures are computed over HTTP headers or path segments, apply normalization at the earliest point where the raw string is available in your middleware or controller logic.
The CLI tool middlebrick scan <url> can be used to verify that your endpoints consistently handle Unicode representations. The scan report highlights inconsistencies in signature validation across encoded variants and provides prioritized findings with remediation steps. For teams using automated pipelines, the GitHub Action can be added to CI/CD to fail builds if normalization-sensitive HMAC checks are detected during integration tests.