Overposting in ASP.NET Core APIs: Prevention Guide

Overposting - also called mass assignment - is one of those vulnerabilities that catches teams off guard because the attack surface hides inside perfectly normal-looking controller code. In production, I've seen it expose fields like IsAdmin, CreditBalance, and SubscriptionTier to untrusted client input because nobody questioned whether model binding was being too generous. The patterns covered here go deeper on Patreon - with annotated, production-ready source code showing how enterprise teams layer these defences inside a real API project.
The core problem is simple: ASP.NET Core's model binder maps every matching JSON property from an HTTP request body onto your domain object - including properties the client was never supposed to touch. If you're binding directly to EF Core entities, that mapping can silently update database columns an attacker has no business writing to.
Most tutorials on this topic were written around 2017 and cover MVC form binding. This guide focuses on the JSON body binding in contemporary ASP.NET Core Web API and .NET 10 patterns - the variants that actually cause problems in modern API backends. Getting security right in a real API means thinking about authorization at the property level, not just the endpoint level - Chapter 8 of the Zero to Production course covers resource-based and property-level authorization inside a full production codebase, with source code you can run immediately.
What Is an Overposting Attack?
An overposting attack happens when a client sends extra properties in a request body that were not intended to be user-controlled, and the API binds and persists them anyway.
Consider a simple user update endpoint that accepts a JSON body mapped directly to a User entity:
// Vulnerable pattern - binding directly to an EF Core entity
[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(int id, User user)
{
// attacker-controlled body: { "name": "Alice", "isAdmin": true }
_dbContext.Users.Update(user);
await _dbContext.SaveChangesAsync();
return Ok();
}
A malicious client that knows (or guesses) your User class has an IsAdmin property simply includes it in the request. The model binder happily maps it. Your application trusts it. The database stores it.
This type of attack was behind the GitHub 2012 mass assignment exploit and remains on the OWASP API Security Top 10 as API3:2023 - Broken Object Property Level Authorization.
Why ASP.NET Core Is Particularly Exposed
ASP.NET Core's [ApiController] attribute and System.Text.Json deserialization are designed for developer convenience - they bind everything by default. Unlike the old [Bind] attribute approach for MVC form data which required explicit allow-listing, JSON body binding in Web APIs has no automatic protection. Whatever properties exist on your input model, the binder will attempt to populate them from the request body.
In production, I've seen three conditions that consistently combine to create this vulnerability:
Direct entity binding - passing
DbSetentity types as action parameters or return values without a DTO boundaryThin controller layers - no service layer intercepting or re-mapping input
Implicit trust - assuming authenticated requests are authorized to set any field they send
Authentication does not protect you here. An authenticated and authorized user can still overpost a field they were not supposed to change.
The Right Fix: Input DTOs with Explicit Property Mapping
The most robust and maintainable defence is a dedicated input DTO (Data Transfer Object) that models only what the client is allowed to send:
// Only the properties a client is permitted to update
public record UpdateUserRequest(string Name, string Email);
Your controller accepts the DTO, not the entity:
[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(int id, UpdateUserRequest request)
{
var user = await _dbContext.Users.FindAsync(id);
if (user is null) return NotFound();
// Explicit, intentional mapping - no surprise properties
user.Name = request.Name;
user.Email = request.Email;
await _dbContext.SaveChangesAsync();
return NoContent();
}
The DTO acts as a contract. Properties like IsAdmin, CreditBalance, or Role literally do not exist on the input type, so they cannot be bound regardless of what the client sends.
This is the pattern we ship at Coding Droplets for every write endpoint. It also gives you a natural place to add FluentValidation rules without polluting the domain entity.
Using [BindNever] as a Secondary Layer
For cases where an entity must be exposed directly (legacy code, scaffolded endpoints), [BindNever] from Microsoft.AspNetCore.Mvc marks a property as off-limits for model binding:
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
[BindNever]
public bool IsAdmin { get; set; }
[BindNever]
public decimal CreditBalance { get; set; }
}
[BindNever] works for both form data and JSON body binding. It is useful as a quick fix in existing codebases but should not replace a proper DTO boundary - it requires discipline to apply to every sensitive property and is easy to forget on newly added columns.
[JsonIgnore] for Read-Only Response Properties
If a property should only appear in responses (never written from input), decorate it with [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] or use separate request/response DTOs:
// Request DTO: what the client can write
public record CreateOrderRequest(int ProductId, int Quantity);
// Response DTO: what the client can read back
public record CreateOrderResponse(int OrderId, decimal Total, DateTimeOffset CreatedAt);
Separating read and write representations eliminates the entire class of ambiguity about which properties flow in which direction.
Defence-in-Depth: What to Layer On Top
A DTO boundary closes the primary attack surface, but these additional controls reinforce it:
Authorization checks on sensitive field updates - even with DTOs, check that the caller is authorized to change the specific fields they are sending. A regular user should not be able to patch their own
SubscriptionTier.EF Core owned entities and shadow properties - fields that should never be set by application code at all can be modelled as shadow properties, keeping them completely invisible to the application layer.
FluentValidation on input DTOs - validate ranges, allowed values, and cross-field constraints. An input DTO without validation is still half-defended.
Audit logging on sensitive writes - log who changed what values and when. This turns a silent exploit into a traceable event.
Integration tests with hostile payloads - write tests that send extra properties in request bodies and assert they are ignored. This codifies the expected behaviour and prevents regression.
How to Find Existing Vulnerabilities in Your Codebase
A quick audit:
Search for
[HttpPost]and[HttpPut]action parameters - any that accept EF Core entity types are immediately suspectSearch for
[HttpPatch]endpoints that useJsonPatchDocument<YourEntity>- these are particularly risky without property allow-listingReview scaffolded CRUD controllers - default scaffolding in Visual Studio and
dotnet-aspnet-codegeneratorstill generates entity-bound actions in many project templatesCheck AutoMapper profiles - if you're mapping
CreateRequest -> EntitywithCreateMap<CreateRequest, Entity>()and no explicitForMemberexclusions, AutoMapper will happily map any identically-named property
The quickest way to rule out a whole category of risk is to enforce at the team level: no entity type as an action parameter, ever. One code review rule, consistently applied.
Internal Links
If you work with EF Core entities and want to understand how the model binder interacts with change tracking, the patterns here pair naturally with EF Core query and update patterns covered in our EF Core performance guide.
For setting up authorization policies that restrict what authenticated users can modify, see our ASP.NET Core authorization patterns post.
FAQ
What is overposting in ASP.NET Core?
Overposting (also called mass assignment) is when a client sends HTTP request properties that were not intended to be user-controlled, and the API binds and persists them because no explicit allow-list is in place. It allows attackers to set privileged fields like IsAdmin or CreditBalance by simply including them in a JSON request body.
How does [BindNever] work in ASP.NET Core?
[BindNever] is an attribute from Microsoft.AspNetCore.Mvc that instructs the model binder to skip a property entirely during binding, regardless of what the client sends. It applies to both form data and JSON deserialization. It is a useful quick fix but does not replace a proper DTO boundary.
Should I use DTOs or [BindNever] to prevent overposting?
DTOs are the stronger and more maintainable choice. They create an explicit contract for what the client can send, independent of your domain entities. [BindNever] is a useful safety net for existing codebases but requires manual discipline on every sensitive property - something that is easy to overlook when new columns are added.
Is overposting protected automatically by [ApiController]?
No. [ApiController] simplifies model binding and validation, but it does not whitelist or blacklist any properties. It binds all matching JSON properties by default, which is precisely the behaviour that makes overposting possible without an explicit countermeasure.
Does JSON body binding in ASP.NET Core 10 have built-in overposting protection?
Not by default. System.Text.Json deserializes all properties that match the target type, and ASP.NET Core does not add automatic allow-listing over that. You must apply it yourself via input DTOs, [BindNever], or both.
What OWASP category covers overposting?
Overposting maps to API3:2023 - Broken Object Property Level Authorization in the OWASP API Security Top 10. It represents unauthorized modification of object properties that the caller is not authorized to change.
How can I test my API for overposting vulnerabilities?
Write integration tests using WebApplicationFactory that send requests with extra properties - fields that should not be accepted. Assert that those properties are not reflected in the stored state. In addition, audit action parameters in controllers for direct entity binding and replace them with dedicated input DTOs.
About the Author
I'm Celin Daniel, Co-founder of Coding Droplets. I've been building .NET and ASP.NET Core systems in production for 13+ years - APIs, distributed backends, enterprise platforms. Everything I write here comes from real shipping experience: patterns that held up, trade-offs that bit us, and lessons learned the hard way.
GitHub: codingdroplets
YouTube: Coding Droplets
Website: codingdroplets.com



