Broken Access Control in Aspnet with Hmac Signatures
Broken Access Control in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when authorization checks are missing or bypassed, allowing attackers to access resources or perform actions they should not be permitted to. In ASP.NET applications that rely on HMAC signatures for request authentication, a subtle implementation gap can turn a integrity mechanism into an authorization bypass.
HMAC (Hash-based Message Authentication Code) is typically used to ensure request integrity and authenticity: the client signs a subset of the request (method, path, selected headers, and body) using a shared secret, and the server recomputes the signature and compares it. If the comparison is done incorrectly—such as using a vulnerable string comparison, failing to include the full request scope, or not enforcing scope on authorization boundaries—an attacker can reuse a valid signature for a different resource or method.
Consider an ASP.NET Core API where the HMAC signature is validated but the endpoint enforces no additional policy or scope check. A signature obtained for reading /api/users/me might also be accepted for /api/users/me/billing or for performing a state-changing POST /api/users/me/billing/activate. This is a BOLA/IDOR pattern enabled by weak authorization enforcement around a seemingly secure HMAC check. The signature proves the request was generated by the holder of the secret, but it does not prove the caller is allowed to act on the target resource.
Another common pattern is including only the path and method in the signed string while omitting the resource identifier or tenant context. In a multi-tenant application, an attacker who can obtain one valid request (e.g., via a logging leak or client-side code) can reuse its HMAC to access another tenant’s data if the server fails to bind the signature to the tenant or principal. This maps directly to OWASP API Top 10 2023: Broken Object Level Authorization and maps to frameworks such as PCI-DSS and SOC2 controls around segregation of duties and access control.
Additionally, if the server accepts multiple signature versions or keys without clear scope separation, an attacker might downgrade or swap contexts. For example, if one endpoint validates the HMAC and then calls an internal service using the same headers without re-authorizing, this can lead to privilege escalation or SSRF-like lateral movement within the application. The key takeaway is that HMAC ensures integrity and origin, but does not replace explicit, per-request authorization checks tied to the subject and the resource.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
Remediation focuses on binding the HMAC validation to authorization decisions and ensuring the signed scope covers all contextual elements that define permissible access. Below are concrete patterns for ASP.NET Core that couple signature verification with resource-level checks.
1. Include resource identifiers and tenant context in the signed string
When building the string to sign, always incorporate the resource ID and tenant or user context. This prevents signature reuse across resources or tenants.
// Example: building the string to sign
public static string BuildStringToSign(HttpRequest request, string userId, string resourceId)
{
var timestamp = request.Headers["X-Timestamp"].ToString();
var nonce = request.Headers["X-Nonce"].ToString();
var method = request.Method;
var path = request.Path.Value; // e.g., /api/users/123/billing
var body = new StreamReader(request.Body).ReadToEnd();
// Important: reset the body stream if you need to read it again later
request.Body = new MemoryStream(Encoding.UTF8.GetBytes(body));
var parts = new[] { method, path, timestamp, nonce, userId, resourceId, body };
return string.Join("|", parts);
}
2. Validate HMAC and enforce ownership/authorization in the endpoint
After verifying the signature, explicitly check that the current principal matches the user implied by the signed user ID and that the resource belongs to that user.
// In a controller or minimal API handler
[HttpGet("/api/users/{userId}/billing/{resourceId}")]
public async Task<IActionResult> GetBilling(
string userId,
string resourceId,
[FromHeader] string timestamp,
[FromHeader] string nonce,
[FromHeader] string signature)
{
// 1) Ensure the user in the route matches the user in the signature context
var principal = HttpContext.User;
if (principal.FindFirst(ClaimTypes.NameIdentifier)?.Value != userId)
{
return Forbid();
}
// 2) Confirm the resource belongs to the user (example with a service)
var resource = await _billingService.GetResourceAsync(userId, resourceId);
if (resource == null)
{
return NotFound();
}
// 3) Recompute and compare HMAC
var computed = HmacUtils.ComputeSha256(BuildStringToSign(Request, userId, resourceId), _sharedSecret);
var provided = Convert.FromBase64String(signature);
if (!CryptographicOperations.FixedLengthEquals(computed, provided))
{
return Unauthorized();
}
// Proceed only if all checks pass
return Ok(resource);
}
3. Use constant-time comparisons and avoid key reuse across contexts
Always use cryptographic fixed-length comparison to prevent timing attacks. Do not reuse the same HMAC key for different purposes (e.g., signing and encryption). If you support multiple signature versions, encode the version into the signed string and validate it before processing.
// Constant-time comparison example
byte[] computed = HmacUtils.ComputeSha256(data, secret);
byte[] provided = Convert.FromBase64String(providedSignature);
bool valid = CryptographicOperations.FixedLengthEquals(computed, provided);
if (!valid)
{
return Unauthorized();
}
// Versioned signature: include version in the string to sign
var version = "v2";
var stringToSign = $"{version}|{method}|{path}|{timestamp}|{nonce}|{userId}|{resourceId}|{body}";
4. Middleware-level scoping for APIs with internal calls
If your pipeline makes internal HTTP calls on behalf of the user, re-validate the user context and do not blindly forward the original signature. Instead, establish a secure service identity and perform a fresh authorization check at each hop.
| Check | Purpose | Example in ASP.NET |
|---|---|---|
| Signature validity | Ensure request integrity and origin | HMAC-SHA256 with Shared Secret |
| User-context binding | Prevent subject tampering | Claims-based NameIdentifier match |
| Resource ownership | Prevent IDOR/BOLA | Lookup resource and verify UserId |
| Scope coverage | Cover method, path, tenant, resource | Include all in the signed string |