Mass Assignment in Aspnet with Hmac Signatures
Mass Assignment in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Mass assignment in ASP.NET occurs when an action method binds incoming request data (form values, query strings, JSON) directly to a model or entity without explicit filtering. When HMAC signatures are used to authenticate requests, developers may assume that because a request is authenticated, the bound model is safe. This assumption is incorrect: HMACs verify integrity and origin of the payload, but they do not limit which properties can be set by the client.
In ASP.NET Core, model binding merges data from multiple sources (route, query, body) into the target object. If the action uses FromBody with JSON and the model contains public setters for sensitive fields (e.g., IsAdmin, Role, Balance), an authenticated request with a valid HMAC can still set these values. For example, a client computes an HMAC over a JSON payload that includes {\"Id\":1,\"IsAdmin\":true}; the server validates the signature, binds the JSON to a UserUpdateDto, and unknowingly applies the privilege escalation.
This combination creates a vulnerability where authentication via HMAC gives a false sense of security. The signature ensures the payload has not been tampered with in transit, but if the server trusts the client to decide which fields to set, an attacker can modify authorized fields simply by including them in the JSON. The risk is especially pronounced in update endpoints that merge partial updates (PATCH) or use DTOs that mirror domain models closely. Without explicit allow-lists or bind exclusions, mass assignment can lead to privilege escalation, data leakage, or unauthorized state changes despite valid HMAC verification.
Real-world analogies include the OWASP API Top 10 A01:2023 broken object level authorization, where IDOR often pairs with over-permissive binding. In such cases, HMAC does not mitigate the underlying binding issue; it only authenticates an otherwise malicious payload.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
Remediation focuses on explicit model binding control and strict filtering of what the client can set. Do not rely on HMAC to enforce data boundaries.
1. Use [ApiController] with explicit [Bind] or DTOs with private setters
Define input models with private setters and populate only allowed properties in the action. This prevents mass assignment regardless of HMAC validation.
// Request DTO with explicit, safe properties
public class UserUpdateDto
{
public int Id { get; set; }
public string Name { get; set; }
// Do not include IsAdmin, Role, or Balance here
}
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] UserUpdateDto dto)
{
// Map to domain entity manually; do not bind directly to entity
_userService.UpdateUser(id, dto.Name);
return NoContent();
}
}
2. Use [FromBody] with custom input formatters or exclude sensitive properties via [JsonIgnore] and [BindNever]
Ensure JSON deserialization ignores fields that should never be set by the client.
public class UserUpdateDto
{
public int Id { get; set; }
public string Name { get; set; }
[JsonIgnore]
public bool IsAdmin { get; set; }
[BindNever] // Ensures model binder skips this property
public string Role { get; set; }
}
3. Validate HMAC before binding and enforce allow-lists
In scenarios where you accept raw JSON and compute HMAC manually, validate the signature before model binding and only map known-safe fields.
[HttpPost("orders")]
public IActionResult CreateOrder()
{
// Read raw body for HMAC validation
using var reader = new StreamReader(Request.Body);
string rawBody = reader.ReadToEnd();
// Assume HMAC is provided in a header
if (!Request.Headers.TryGetValue("X-API-Signature", out var signature))
return Unauthorized();
if (!HmacValidator.IsValid(rawBody, signature, secretKey))
return Unauthorized();
// Now deserialize safely with explicit DTO
var payload = JsonSerializer.Deserialize<OrderCreateDto>(rawBody);
if (payload == null || !ModelState.IsValid)
return BadRequest();
// Proceed with business logic using only allowed fields
_orders.Create(payload.ProductId, payload.Quantity);
return CreatedAtAction(nameof(GetOrder), new { id = payload.ProductId }, null);
}
4. Prefer PATCH with explicit property mappings
For partial updates, avoid binding entire objects. Use a patch document model that enumerates allowed operations.
public class PatchOperation
{
public string Op { get; set; }
public string Path { get; set; }
public object Value { get; set; }
}
[HttpPatch("{id}")]
public IActionResult Patch(int id, [FromBody] List<PatchOperation> ops)
{
// Apply only whitelisted paths, e.g., /name, /email
var allowedPaths = new HashSet<string> { "/name", "/email" };
foreach (var op in ops)
{
if (!allowedPaths.Contains(op.Path))
return BadRequest("Operation not allowed");
// Apply change
}
return NoContent();
}
5. Framework-level protections
Configure model binding to ignore unknown properties and disallow binding from form data to sensitive models.
// In Program.cs or Startup.cs
builder.Services.AddControllers(options =>
{
options.ModelMetadataDetailsProviders.Add(
new ExcludeBindingMetadataProvider<SensitiveModel>());
});
// Custom provider to block binding for specific types
public class ExcludeBindingMetadataProvider<T> : IModelMetadataProvider
{
public IReadOnlyDictionary<object, object> AdditionalValues => null;
public IReadOnlyList<ApiParameterDescriptor>> ParameterDescriptors => null;
public IReadOnlyList<BindTreeNode> BindTreeNodes => null;
public IReadOnlyList<PropertyInfo> BindingProperties => null;
public IReadOnlyCollection<string> AllowedChildTypes => null;
public bool IsBindingAllowed => false;
public bool IsCollectionType => false;
public bool IsComplexType => false;
public bool IsEnumerableType => false;
public bool IsEnumerableStringOrByteArray => false;
public bool IsEnumType => false;
public bool IsFlagEnum => false;
public bool IsGenericType => false;
public bool IsKeyProperty => false;
public bool IsNonNullableValueType => false;
public bool IsNullableType => false;
public bool IsPrimaryKey => false;
public bool IsReadOnly => false;
public bool IsRequired => false;
public bool IsRowVersionProperty => false;
public bool IsRuntimeType => false;
public bool IsSequenceType => false;
public bool IsSimpleNullableType => false;
public bool IsSimpleType => false;
public bool IsString Type => false;
public bool IsTupleType => false;
public bool IsUserSettable Property => false;
public bool SuppressBinding => true;
}
By combining explicit DTOs, property-level attributes, and strict allow-lists, you ensure that HMAC-authenticated endpoints remain protected against mass assignment, even when the payload is cryptographically verified.
Related CWEs: propertyAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-915 | Mass Assignment | HIGH |