Hardens .NET apps per OWASP Top 10 -- injection, auth, XSS, deprecated security APIs.
dotnet-security-owasp
OWASP Top 10 (2021) security guidance for .NET applications. Each category includes the vulnerability description,
.NET-specific risk, mitigation code examples, and common pitfalls. This skill is the canonical owner of deprecated
security pattern warnings (CAS, APTCA, .NET Remoting, DCOM, BinaryFormatter).
Scope
OWASP Top 10 (2021) vulnerability categories with .NET-specific mitigations
Deprecated security API warnings (CAS, APTCA, BinaryFormatter, .NET Remoting)
Security header configuration and CORS hardening
Rate limiting and anti-forgery middleware patterns
NuGet package audit and dependency vulnerability scanning
Out of scope
Authentication/authorization implementation -- see [skill:dotnet-api-security]
Blazor auth UI -- see [skill:dotnet-blazor-auth]
Cryptographic algorithm selection -- see [skill:dotnet-cryptography]
Configuration binding and Options pattern -- see [skill:dotnet-csharp-configuration]
Secrets storage and management -- see [skill:dotnet-secrets-management]
Cross-references: [skill:dotnet-secrets-management] for secrets handling, [skill:dotnet-cryptography] for cryptographic
best practices, [skill:dotnet-csharp-coding-standards] for secure coding conventions.
A01: Broken Access Control
Vulnerability: Users act outside their intended permissions -- accessing other users' data, elevating privileges, or
bypassing access checks.
Risk in .NET: Missing [Authorize] attributes on controllers/endpoints, insecure direct object references (IDOR)
where user IDs are taken from route parameters without ownership validation, and CORS misconfiguration allowing
unintended origins.
Mitigation
// 1. Apply authorization globally, then opt out explicitly
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy(new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build());
var app = builder.Build();
app.MapControllers(); // All endpoints require auth by default// 2. Resource-based authorization to prevent IDORpublicsealedclassDocumentAuthorizationHandler
: AuthorizationHandler<EditRequirement, Document>
{
protectedoverride Task HandleRequirementAsync(
AuthorizationHandlerContext context,
EditRequirement requirement,
Document resource)
{
if (resource.OwnerId == context.User.FindFirstValue(ClaimTypes.NameIdentifier))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// In the endpoint:
app.MapPut("/documents/{id}", async (
int id,
DocumentDto dto,
IAuthorizationService authService,
ClaimsPrincipal user,
AppDbContext db) =>
{
var document = await db.Documents.FindAsync(id);
if (document isnull) return Results.NotFound();
var authResult = await authService.AuthorizeAsync(user, document, "Edit");
if (!authResult.Succeeded) return Results.Forbid();
document.Title = dto.Title;
await db.SaveChangesAsync();
return Results.NoContent();
});
```text
```csharp
// 3. Restrict CORS to known origins
builder.Services.AddCors(options =>
{
options.AddPolicy(, policy =>
{
policy.WithOrigins()
.WithMethods(, )
.WithHeaders(, );
});
});
```text
**Gotcha:** `AllowAnyOrigin()` combined `AllowCredentials()` rejected at runtime ASP.NET Core, but `SetIsOriginAllowed(_ => )` `AllowCredentials()` silently allows all origins -- never use pattern.
---
**Vulnerability:** Sensitive data exposed due to weak missing encryption -- plaintext storage, deprecated algorithms, improper key management.
**Risk .NET:** Using MD5/SHA1 hashing passwords, storing connection strings plaintext passwords `appsettings.json`, transmitting sensitive data over HTTP, `DES`/`RC2` encryption.
```csharp
builder.Services.AddHttpsRedirection(options =>
{
options.HttpsPort = ;
});
app = builder.Build();
app.UseHsts();
app.UseHttpsRedirection();
```json
```csharp
{
IDataProtector _protector =
provider.CreateProtector();
=> _protector.Protect(plaintext);
=> _protector.Unprotect(ciphertext);
}
```text
See [skill:dotnet-cryptography] = db.Orders
.Where(o => o.CustomerId == customerId)
.ToListAsync();
results = db.Orders
.FromSqlInterpolated()
.ToListAsync();
```text
```csharp
app.MapGet(, ( name) =>
Results.Content(,
));
=>
toolName ;
```bash
**Gotcha:** `FromSqlRaw` concatenation bypasses parameterization. Always use `FromSqlInterpolated` pass `SqlParameter` objects to `FromSqlRaw`.
---
**Vulnerability:** Flaws design patterns that cannot be implementation alone -- missing rate limiting, lack of defense depth, unrestricted resource consumption.
**Risk .NET:** APIs without rate limiting, unbounded uploads, missing anti-forgery tokens state-changing operations.
```csharp
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(, limiterOptions =>
{
limiterOptions.PermitLimit = ;
limiterOptions.Window = TimeSpan.FromMinutes();
limiterOptions.QueueLimit = ;
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
app = builder.Build();
app.UseRateLimiter();
app.MapGet(, () => Results.Ok())
.RequireRateLimiting();
```text
```csharp
builder.Services.AddAntiforgery();
app = builder.Build();
app.UseAntiforgery();
app.MapPost(, ([FromForm] productId, AppDbContext db) =>
{
order = Order { ProductId = productId };
db.Orders.Add(order);
db.SaveChangesAsync();
Results.Created(, order);
});
app.MapPost(, (CreateOrderDto dto, AppDbContext db) =>
{
order = Order { ProductId = dto.ProductId };
db.Orders.Add(order);
db.SaveChangesAsync();
Results.Created(, order);
}).RequireAntiforgery();
```text
**Gotcha:** `UseRateLimiter()` must be called after `UseRouting()` before `MapControllers()`/`MapGet()` to apply correctly.
---
**Vulnerability:** Insecure configurations, incomplete configurations, open cloud storage, unnecessary features enabled, verbose error messages.
**Risk .NET:** ;
app = builder.Build();
(app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
{
app.UseExceptionHandler();
app.UseHsts();
}
app.Use( (context, next) =>
{
context.Response.Headers.Append(, );
context.Response.Headers.Append(, );
context.Response.Headers.Append(, );
context.Response.Headers.Append(
,
);
next();
});
```text
```csharp
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = * * ;
options.Limits.MaxRequestHeadersTotalSize = * ;
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds();
});
```text
**Gotcha:** `UseDeveloperExceptionPage()` leaks source code paths stack traces. Ensure it gated behind `IsDevelopment()` never enabled production staging.
---
**Vulnerability:** Using components known vulnerabilities, unsupported frameworks, unpatched dependencies.
**Risk .NET:** Running -of-support .NET versions, NuGet packages known CVEs, transitive dependency vulnerabilities audited.
```xml
<!-- Enable NuGet audit Directory.Build.props csproj -->
<PropertyGroup>
<NuGetAudit></NuGetAudit>
<NuGetAuditLevel>low</NuGetAuditLevel>
<NuGetAuditMode>all</NuGetAuditMode> <!-- Audit direct + transitive -->
</PropertyGroup>
```text
```bash
dotnet list package --vulnerable --include-transitive
dotnet outdated
dotnet --info
```text
**Gotcha:** `NuGetAuditMode` defaults to `direct` -- transitive vulnerabilities are hidden unless you `all`. Always use `all` CI to deep dependency issues.
---
**Vulnerability:** Weak authentication mechanisms, credential stuffing, session fixation, missing multi-factor authentication.
**Risk .NET:** Default Identity password policies that are too weak, session cookies without `Secure`/`SameSite` attributes, missing account lockout configuration.
```csharp
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
{
options.Password.RequireDigit = ;
options.Password.RequiredLength = ;
options.Password.RequireNonAlphanumeric = ;
options.Password.RequireUppercase = ;
options.Password.RequireLowercase = ;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes();
options.Lockout.MaxFailedAccessAttempts = ;
options.Lockout.AllowedForNewUsers = ;
options.User.RequireUniqueEmail = ;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
```text
```csharp
builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = ;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
options.ExpireTimeSpan = TimeSpan.FromHours();
options.SlidingExpiration = ;
});
```text
**Gotcha:** `CookieSecurePolicy.SameAsRequest` allows cookies over HTTP development, which fine. But production behind a reverse proxy terminating TLS, the app sees HTTP -- so cookies are sent insecurely. Always use `CookieSecurePolicy.Always` production configure forwarded headers.
---
**Vulnerability:** Code infrastructure that does protect against integrity violations -- unsigned packages, insecure CI/CD pipelines, deserialization of untrusted data.
**Risk .NET:** Using `BinaryFormatter` = = />
< key= = />
</packageSources>
<packageSourceMapping>
<packageSource key=>
<package pattern= />
</packageSource>
<packageSource key=>
<package pattern= />
</packageSource>
</packageSourceMapping>
</configuration>
```text
```csharp
data = JsonSerializer.Deserialize<OrderDto>(jsonString);
bytes = MessagePackSerializer.Serialize(order);
restored = MessagePackSerializer.Deserialize<Order>(bytes);
```text
**Gotcha:** Package source mapping uses most-specific-pattern-wins: `MyCompany.*` beats the `*` wildcard. Always define specific patterns packages to prevent dependency confusion attacks.
---
**Vulnerability:** Insufficient logging of security-relevant events, lack of monitoring breaches, inability to detect respond to active attacks.
**Risk .NET:** Not logging authentication failures, missing audit trails sensitive operations,
{
{
userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? ;
path = context.Request.Path.Value;
(logger.BeginScope( Dictionary<, ?>
{
[] = userId,
[] = path,
[] = context.Connection.RemoteIpAddress?.ToString()
}))
{
next(context);
(context.Response.StatusCode == StatusCodes.Status401Unauthorized)
{
logger.LogWarning(, path);
}
(context.Response.StatusCode == StatusCodes.Status403Forbidden)
{
logger.LogWarning(, path);
}
}
}
}
```text
```csharp
builder.Logging.AddFilter(, LogLevel.Warning);
builder.Services.AddHttpLogging(options =>
{
options.LoggingFields = HttpLoggingFields.RequestPath
| HttpLoggingFields.RequestMethod
| HttpLoggingFields.ResponseStatusCode
| HttpLoggingFields.Duration;
});
```text
**Gotcha:** Structured logging `{Placeholder}` syntax safe,
{
HashSet<> AllowedHosts = (StringComparer.OrdinalIgnoreCase)
{
,
};
{
(!Uri.TryCreate(url, UriKind.Absolute, uri))
;
(uri.Scheme != Uri.UriSchemeHttps)
;
(IPAddress.TryParse(uri.Host, ip))
{
(IsPrivateOrReserved(ip))
;
}
AllowedHosts.Contains(uri.Host);
}
{
[] bytes = ip.GetAddressBytes();
bytes[]
{
=> ,
=> ,
bytes[] == => ,
bytes[] >= && bytes[] <= => ,
bytes[] == => ,
_ =>
};
}
}
app.MapPost(, (FetchRequest request, IHttpClientFactory factory) =>
{
(!UrlValidator.IsAllowed(request.Url))
Results.BadRequest();
client = factory.CreateClient();
response = client.GetStringAsync(request.Url);
Results.Ok(response);
});
```text
```csharp
builder.Services.AddHttpClient(, client =>
{
client.BaseAddress = Uri();
})
.ConfigurePrimaryHttpMessageHandler(() => SocketsHttpHandler
{
AllowAutoRedirect =
});
```text
**Gotcha:** DNS rebinding can bypass IP allowlists -- an attacker
"Strict"
"https://app.example.com"
"GET"
"POST"
"Content-Type"
"Authorization"
with
is
by
true
with
this
## A02: Cryptographic Failures
or
or
in
for
with
in
or
using
for
### Mitigation
// Enforce HTTPS and HSTS
443
var
// Strict-Transport-Security header
// Never store secrets in appsettings.json -- use user secrets or env vars
// See [skill:dotnet-secrets-management] for proper secrets handling
// Use Data Protection API for symmetric encryption of application data
for algorithm selection (AES-GCM, RSA, ECDSA) and key derivation.
---
## A03: Injection
**Vulnerability:** Untrusted data sent to an interpreter as part of a command or query -- SQL injection, command injection, LDAP injection, and cross-site scripting (XSS).
**Risk in .NET:** String concatenation in SQL queries, `Process.Start` with unsanitized input, rendering user input as raw HTML in Razor pages.
### Mitigation
```csharp
// SQL injection prevention: always use parameterized queries// EF Core is parameterized by default via LINQvar orders
await
// When raw SQL is needed, use parameterized interpolation
var
await
$"SELECT * FROM Orders WHERE Status = {status}"
// NEVER concatenate user input into SQL:
// var bad = db.Orders.FromSqlRaw("SELECT * FROM Orders WHERE Status = '" + status + "'");
// XSS prevention: Razor encodes output by default.
// Use @Html.Raw() ONLY for trusted, pre-sanitized HTML.
// In Minimal APIs, return typed results -- not raw strings:
logging sensitive data (passwords, tokens) in plaintext.
### Mitigation
```csharp
// Log security events with structured loggingpublicsealedclassAuditMiddleware(RequestDelegate next, ILogger<AuditMiddleware> logger)
publicasync Task InvokeAsync(HttpContext context)
var
"anonymous"
var
using
new
string
object
"UserId"
"RequestPath"
"RemoteIp"
await
// Log failed authentication attempts
if
"Authentication failed for {Path}"
// Log authorization failures
if
"Authorization denied for {Path}"
// NEVER log sensitive data -- redact credentials and PII
// Configure log filtering to exclude sensitive paths
"Microsoft.AspNetCore.Authentication"
// Use IHttpLoggingInterceptor (.NET 8+) to redact request/response headers
// Explicitly exclude request/response bodies and auth headers
with
is
but stringinterpolation (`$"User {userId}"`) in log calls bypasses structured logging and may leak PII into log sinks that donot support redaction.
---
## A10: Server-Side Request Forgery (SSRF)
**Vulnerability:** Application fetches a remote resource based on user-supplied URL without validation, allowing attackers to reach internal services or metadata endpoints.
**Risk in .NET:** `HttpClient` calls with user-provided URLs, URL redirect following to internal networks, accessing cloud metadata endpoints (169.254.169.254).
### Mitigation
```csharp
// Validate and restrict outbound URLspublicstaticclass UrlValidator
// Configure HttpClient to disable automatic redirect following
"external"
new
"https://api.example.com"
new
false
// Prevent redirect-based SSRF
's domain resolves to a public IP during validation but to an internal IP during the actual request. Pin DNS resolution or re-validate after connection.
---
## Deprecated Security Patterns
This skill is the **canonical owner** of deprecated security pattern warnings. Other skills should cross-reference here rather than duplicating these warnings.
### Code Access Security (CAS)
CAS is **not supported** in .NET Core/.NET 5+. Code that references `System.Security.Permissions`, `SecurityPermission`, or `[SecurityCritical]`/`[SecuritySafeCritical]` attributes for CAS purposes must be removed or replaced with OS-level security boundaries (containers, process isolation).
### AllowPartiallyTrustedCallers (APTCA)
The `[AllowPartiallyTrustedCallers]` attribute has **no effect** in .NET Core/.NET 5+. The partial-trust model is gone. Remove APTCA attributes during migration. Use standard authorization and input validation instead.
### .NET Remoting
.NET Remoting is **not available** in .NET Core/.NET 5+. It was inherently insecure due to unrestricted deserialization of remote objects. Replace with:
- gRPC for cross-process/cross-machine RPC (see [skill:dotnet-cryptography] for transport security)
- Named pipes for same-machine IPC
- HTTP APIs for service-to-service communication
### DCOM
Distributed COM (DCOM) is **Windows-only and not supported** in .NET Core/.NET 5+. Replace with gRPC, REST APIs, or message queues for distributed communication.
### BinaryFormatter
`BinaryFormatter` is **obsolete as error** (SYSLIB0011) in .NET 8 and **removed** in .NET 9+. It enables arbitrary code execution through deserialization attacks. Replace with:
- `System.Text.Json` for JSON serialization
- MessagePack or Protocol Buffers for binary formats
- `XmlSerializer` with strict type allowlists for XML scenarios
Do **not** set `System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization` to `true` as a workaround.
---
## Agent Gotchas
1. **Do not use `[AllowAnonymous]` without explicit justification** -- it overrides the global fallback policy. Mark each anonymous endpoint with a comment explaining why.
2. **Do not disable HTTPS redirection for convenience** -- use `dotnet dev-certs https --trust` for local development instead.
3. **Do not log raw request bodies** -- they may contain credentials, tokens, or PII. Use `HttpLoggingFields` to select safe fields.
4. **Do not rely solely on client-side validation** -- always validate on the server. Razor form validation is for UX, not security.
5. **Do not use `FromSqlRaw` with string interpolation** -- use `FromSqlInterpolated` which auto-parameterizes.
6. **Do not store secrets in `appsettings.json`** -- use user secrets for development and environment variables or managed identity for production. See [skill:dotnet-secrets-management].
7. **Do not generate security-sensitive code using deprecated patterns** -- CAS, APTCA, .NET Remoting, DCOM, and BinaryFormatter are all unsupported in modern .NET. See the Deprecated Security Patterns section above.
---
## Prerequisites
- .NET 8.0+ (LTS baseline)
- ASP.NET Core 8.0+ for security middleware, anti-forgery, and rate limiting
- Microsoft.AspNetCore.Identity for authentication/identity (if using A07 patterns)
---
## References
- [OWASP Top 10 (2021)](https://owasp.org/www-project-top-ten/)
- [ASP.NET Core Security](https://learn.microsoft.com/en-us/aspnet/core/security/?view=aspnetcore-10.0)
- [Secure Coding Guidelines for .NET](https://learn.microsoft.com/en-us/dotnet/standard/security/secure-coding-guidelines)
- [Security in .NET](https://learn.microsoft.com/en-us/dotnet/standard/security/)
- [ASP.NET Core Data Protection](https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/introduction?view=aspnetcore-10.0)
- [Rate Limiting Middleware](https://learn.microsoft.com/en-us/aspnet/core/performance/rate-limit?view=aspnetcore-10.0)
- [NuGet Package Source Mapping](https://learn.microsoft.com/en-us/nuget/consume-packages/package-source-mapping)
- [BinaryFormatter Migration Guide](https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-migration-guide/)