Nosql Injection in Aspnet with Hmac Signatures
Nosql Injection in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
When an ASP.NET API uses HMAC signatures to authenticate requests but still deserializes untrusted query or header values into a NoSQL query, the HMAC check can give a false sense of integrity. HMAC signatures verify that parameters have not been altered in transit, but they do not validate the structure or safety of parameter content. If the server uses those parameters directly in a NoSQL query (for example, passing user input into a MongoDB filter or a DynamoDB condition), an attacker can inject operators even when the HMAC is valid.
Consider an endpoint that accepts filter and signature. The client computes an HMAC over selected parameters and sends it in a header. The server recomputes the HMAC using a shared secret and, if it matches, proceeds to build a NoSQL query like Builders<BsonDocument>.Filter.Where(x => x["tenant"] == tenantId && x["status"] == status). If the server embeds untrusted input directly into the filter expression, an attacker can supply values such as {"$ne": ""} for status, causing the condition to match unintended documents. Because the HMAC was computed over the tampered parameter, the server accepts it as authentic, and the NoSQL injection is executed in the context of the application’s permissions.
This pattern is especially risky when the HMAC covers only a subset of request data, or when the server reuses keys across multiple endpoints. Injection payloads can leverage NoSQL-specific operators ($ne, $regex, $where, $or) to bypass authorization rules, enumerate data, or extract sensitive fields. In ASP.NET, if the deserialization logic uses dynamic dictionaries or loosely typed models, the injection surface expands further. For example, a dictionary-based model populated from request headers can be merged into a NoSQL filter without validation, enabling nested injection via compound keys. Because the HMAC does not constrain the semantic meaning of values, the server must treat all deserialized input as untrusted and apply strict allow-lists and type checks before it reaches the database layer.
Another vector arises when HMAC-signed parameters are used to construct JSON path expressions or dynamic queries in document stores. An attacker might supply a field name like email.$ne or embed operators inside JSON strings that are later parsed and merged into a query. Even with correct signature validation, the server may fail to differentiate between data and structure, leading to injection through seemingly benign parameters. In ASP.NET applications that integrate with services such as Azure Cosmos DB or MongoDB, this often maps to the OWASP API Top 10 category '2023-A1: Broken Object Level Authorization' and can expose data across tenants or escalate privilege when administrative flags are injected.
To assess this risk, scans examine whether endpoints that validate HMAC signatures also process untrusted input into NoSQL queries without rigorous schema enforcement. They check for missing allow-lists, improper use of dynamic filters, and the presence of NoSQL operators in user-controlled fields. The detection logic correlates HMAC usage patterns with database query construction to highlight cases where authenticity does not imply safety.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
Remediation focuses on never letting untrusted data influence query structure, even after HMAC validation. Use strict allow-lists, typed models, and parameterization instead of string-based or dynamic query assembly. Below are concrete examples for ASP.NET Core that demonstrate safe patterns.
1) Validate and bind to a strongly typed model, then build queries from fixed fields:
public class QueryRequest {
public string TenantId { get; set; }
public string Status { get; set; }
// Whitelisted fields only
}
[HttpPost]
public IActionResult Search([FromBody] QueryRequest req)
{
if (req == null || string.IsNullOrEmpty(req.TenantId) || string.IsNullOrEmpty(req.Status))
return BadRequest("Missing required parameters");
// Use allow-listed values; avoid direct concatenation
var filter = Builders<BsonDocument>.Filter.And(
Builders<BsonDocument>.Filter.Eq("tenant", req.TenantId),
Builders<BsonDocument>.Filter.Eq("status", req.Status)
);
var results = collection.Find(filter).ToList();
return Ok(results);
}
2) When headers are needed, explicitly select and sanitize them before using them in queries:
[HttpGet]
public IActionResult GetItems([FromHeader(Name = "X-Tenant")] string tenantHeader)
{
if (!IsValidTenant(tenantHeader)) // strict regex or allow-list check
return Unauthorized();
var filter = Builders<MyEntity>.Filter.Eq(e => e.Tenant, tenantHeader);
var items = await collection.Find(filter).ToListAsync();
return Ok(items);
}
private bool IsValidTenant(string value)
{
// Allow-list pattern, length limits, and type checks
return !string.IsNullOrWhiteSpace(value) &&
System.Text.RegularExpressions.Regex.IsMatch(value, "^[a-zA-Z0-9-]{1,64}$");
}
3) If you must accept dynamic query fragments for advanced scenarios, parse them into a safe structure and reject any operators:
public class SafeFilter
{
public string Field { get; set; }
public string Value { get; set; }
}
[HttpPost]
public IActionResult Filter([FromBody] SafeFilter filter)
{
if (filter == null || string.IsNullOrEmpty(filter.Field) || string.IsNullOrEmpty(filter.Value))
return BadRequest();
// Explicitly forbid operator-like input
if (filter.Value.Contains("$") || filter.Value.Contains(".") || filter.Value.Contains("[")
return UnprocessableEntity("Invalid filter value");
var safeFilter = Builders<MyDoc>.Filter.Eq(filter.Field, filter.Value);
var results = collection.Find(safeFilter).ToList();
return Ok(results);
}
4) For HMAC verification, ensure the signature is computed over a canonical, minimal set of parameters and that replay or nonce checks are applied to prevent tampering at the semantic level:
public static bool VerifyRequestSignature(HttpRequest req, string secret)
{
var sorted = new SortedDictionary<string, string>();
foreach (var key in req.Query.Keys.Where(k => k.StartsWith("param_")))
{
sorted[key] = req.Query[key];
}
// Include a timestamp or nonce to prevent replay
var data = string.Join("&", sorted.Select(kv => $"{kv.Key}={kv.Value}"));
var computed = Convert.ToBase64String(new HMACSHA256(Encoding.UTF8.GetBytes(secret)).ComputeHash(Encoding.UTF8.GetBytes(data)));
return computed == req.Headers["X-Signature"];
}
These patterns ensure that HMAC signatures protect integrity without inadvertently enabling injection. The key is to treat HMAC as transport integrity, not query safety, and to enforce strict schema validation before any NoSQL interaction.