| name | dotnet-security-owasp |
| description | Hardens .NET apps per OWASP Top 10 -- injection, auth, XSS, deprecated security APIs. |
| metadata | {"short-description":".NET skill guidance for security tasks"} |
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
- Injection, broken access control, XSS, SSRF prevention patterns
- 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
builder.Services.AddAuthorizationBuilder()
.SetFallbackPolicy(new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build());
var app = builder.Build();
app.MapControllers();
public sealed class DocumentAuthorizationHandler
: AuthorizationHandler<EditRequirement, Document>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
EditRequirement requirement,
Document resource)
{
if (resource.OwnerId == context.User.FindFirstValue(ClaimTypes.NameIdentifier))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
app.MapPut("/documents/{id}", async (
int id,
DocumentDto dto,
IAuthorizationService authService,
ClaimsPrincipal user,
AppDbContext db) =>
{
var document = await db.Documents.FindAsync(id);
if (document is null) 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
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
Code Navigation (Serena MCP)
Primary approach: Use Serena symbol operations for efficient code navigation:
- Find definitions:
serena_find_symbol instead of text search
- Understand structure:
serena_get_symbols_overview for file organization
- Track references:
serena_find_referencing_symbols for impact analysis
- Precise edits:
serena_replace_symbol_body for clean modifications
When to use Serena vs traditional tools:
- Use Serena: Navigation, refactoring, dependency analysis, precise edits
- Use Read/Grep: Reading full files, pattern matching, simple text operations
- Fallback: If Serena unavailable, traditional tools work fine
Example workflow:
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"