Most serious .NET compromises do not come from the runtime itself but from what teams configure around it: leaked or copied machine keys, unsafe deserialisers, secrets in appsettings files, permissive CORS and long-lived NuGet publishing keys. The platform has moved a long way: ASP.NET Core replaced the <machineKey> model with the Data Protection API, .NET 9 removed the working BinaryFormatter, NuGet now audits dependencies on restore, and nuget.org supports OIDC-based Trusted Publishing. This guide covers the settings and patterns that matter in ASP.NET Core and legacy ASP.NET, how to handle secrets and machine identities, and how to secure the NuGet supply chain in both directions. It is for .NET developers, AppSec engineers and platform teams.
Key takeaways
- Check your runtime support date now: .NET 8 (LTS) and .NET 9 (STS) both leave support on 10 November 2026; .NET 10 (LTS) is supported to 14 November 2028.
- Key material is an identity: ASP.NET machine keys and Data Protection key rings sign cookies and ViewState, so a stolen or copied key lets an attacker forge trusted state and, for ViewState, run code.
- Deny by default in authorisation: set a fallback policy that requires an authenticated user and mark public endpoints with
[AllowAnonymous], rather than relying on every new controller remembering[Authorize]. - Replace connection-string passwords with managed identities: in Azure, a managed identity with
ManagedIdentityCredentialremoves the stored secret entirely; user-secrets are for development only and are not encrypted. - Lock down NuGet both ways: consume with NuGet Audit, package source mapping and lock files, and publish with Trusted Publishing, because nuget.org API keys created before 17 August 2026 expire on 1 November 2026.
Supported versions and patching
Running an unsupported runtime means no security fixes for the runtime, ASP.NET Core or the base class libraries. Microsoft publishes the support policy on the .NET website; long-term support (LTS) releases get three years of patches and standard-term support (STS) releases get two, with end-of-support dates aligned to Patch Tuesday.
| Version | Type | Released | End of support | Action as of October 2026 |
|---|---|---|---|---|
| .NET 10 | LTS | 11 November 2025 | 14 November 2028 | Target for all new and migrated workloads |
| .NET 9 | STS | 12 November 2024 | 10 November 2026 | Migrate to .NET 10 immediately |
| .NET 8 | LTS | 14 November 2023 | 10 November 2026 | Migrate to .NET 10 immediately |
| .NET 7, 6 and earlier | STS / LTS | 2022 and earlier | Already ended (.NET 6 on 12 November 2024) | Treat as an unpatched asset; isolate and replace |
- Patch the runtime, not just packages: self-contained deployments and container images embed the runtime, so they must be rebuilt to pick up a fix. Framework-dependent apps take the fix from the host's installed runtime.
- Track container base images: pin a supported tag such as a 10.0 runtime image and rebuild on each monthly servicing release.
Authentication and authorisation in ASP.NET Core
ASP.NET Core supplies strong building blocks, but its defaults assume you will opt endpoints in to protection. The most reliable pattern is to invert that.
- Fallback policy: Microsoft's documentation describes the fallback policy as the preferred way to require all users to be authenticated, because it protects newly added Razor Pages and controllers automatically. The policy applies whenever an endpoint has no authorisation metadata.
- Explicit public endpoints: use
[AllowAnonymous](or.AllowAnonymous()on minimal API endpoints) for login, health and landing pages, and review that list in code review. - Resource-based checks: attributes cannot see the object being accessed. Use
IAuthorizationService.AuthorizeAsync(User, resource, requirement)in the handler for per-record rules, which is where broken object-level authorisation (CWE-639, OWASP API1) usually creeps in. - Middleware order:
UseRouting, thenUseCors, thenUseAuthenticationandUseAuthorization, then endpoint mapping. Placing static files or custom middleware before authorisation can expose content unintentionally.
builder.Services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
For service-to-service APIs, validate JWT audience, issuer and lifetime explicitly and prefer short-lived tokens from your identity provider over shared API keys. See the NHI Authentication Guide.
Data Protection API and machine keys
The ASP.NET Core Data Protection API replaces the <machineKey> element from ASP.NET 4.x. It protects authentication cookies, antiforgery tokens, TempData and anything else your code round-trips through the browser. By default keys have a 90-day lifetime, new keys are generated automatically, and retired keys stay in the ring so older payloads can still be read. Payloads use AES-256-CBC with HMACSHA256, derived from a 512-bit master key.
| Hosting environment | Default key location | Encrypted at rest by default | What to do |
|---|---|---|---|
| Azure App Service | %HOME%\ASP.NET\DataProtection-Keys, one ring per slot | No | Use an external ring (Blob Storage plus Key Vault) so slot swaps do not log users out, and protect keys with a Key Vault key |
| IIS with user profile | %LOCALAPPDATA%\ASP.NET\DataProtection-Keys | Yes, DPAPI | Keep setProfileEnvironment true; share the ring across a farm |
| IIS without profile | HKLM registry, ACL'd to the worker process | Yes, DPAPI | Acceptable for a single server; farms need shared storage |
| Containers and Kubernetes | None matched: keys held in memory only | Not applicable | Persist to a volume or external provider, otherwise every restart invalidates cookies |
Custom repository (PersistKeysTo*) | Where you point it | No, automatic encryption is disabled | Add ProtectKeysWith* (certificate or Key Vault key) |
- Set an application name: every instance in a farm must share the ring and call
SetApplicationNamewith the same value; different apps should not share a ring. - Do not delete keys casually: Microsoft warns that deleting a key makes data it protected permanently unreadable. Revoke a suspected-compromised key instead, which forces a new active key.
- Not for long-term storage: the API is designed for transient protected payloads. For data that must stay encrypted for years, use envelope encryption with a key management service. See the Cryptographic Key Management Guide.
Legacy ASP.NET machine keys and ViewState
In ASP.NET 4.x Web Forms, the ValidationKey signs ViewState and the DecryptionKey encrypts it. Anyone holding those values can craft a ViewState payload the server will deserialise, which is remote code execution on IIS. In February 2025 Microsoft Threat Intelligence reported more than 3,000 publicly disclosed machine keys, copied from documentation and code repositories, and an attacker using one to load the Godzilla post-exploitation framework. See the ASP.NET machine key attacks write-up.
- Generate, never copy: create keys per application or farm with IIS Manager or Microsoft's script, or remove a fixed
<machineKey>element so keys are auto-generated on single servers. - Encrypt configuration sections: Microsoft recommends encrypting
machineKeyandconnectionStringsinweb.configduring deployment. - Upgrade to .NET Framework 4.8 for Antimalware Scan Interface (AMSI) support, and enable the attack surface reduction rule that blocks web shell creation on servers.
- Rotation is not remediation: rotating keys does not remove an implant already installed. If a public or stolen key was in use, investigate the host and consider rebuilding it.
Deserialisation and other dangerous APIs
Microsoft's own guidance says calling BinaryFormatter.Deserialize on a payload is roughly equivalent to running that payload as an executable, and that no SerializationBinder configuration fully mitigates it. From .NET 9 the in-box BinaryFormatter throws on use and the compatibility switches have been removed. The same unrestricted polymorphic risk applies to SoapFormatter, LosFormatter, NetDataContractSerializer and ObjectStateFormatter, which is why ViewState with a known key leads to code execution. See the Insecure Deserialisation Guide.
| API or pattern | Risk | Safer alternative |
|---|---|---|
BinaryFormatter, SoapFormatter, NetDataContractSerializer | Type-controlled deserialisation, RCE (CWE-502) | System.Text.Json into concrete types; DataContractSerializer or XmlSerializer with known types |
LosFormatter, ObjectStateFormatter (ViewState) | RCE when machine key is known | Unique keys, MAC validation on, migrate off Web Forms |
Json.NET with TypeNameHandling other than None | Attacker-chosen types from $type | Keep TypeNameHandling.None, or a strict allow-list binder |
String-built SQL with FromSqlRaw or SqlCommand | SQL injection (CWE-89) | Parameters, FromSql with interpolated parameters, LINQ |
Html.Raw, MarkupString on user data | Cross-site scripting (CWE-79) | Default Razor encoding; sanitise if HTML is genuinely needed |
Process.Start with user input | OS command injection (CWE-78) | Use ProcessStartInfo.ArgumentList, never a shell, and an allow-list |
Enable the built-in .NET analysers (<AnalysisMode>All</AnalysisMode> or at least the security rules) and fail the build on new security warnings. Many of the patterns above have dedicated CA rules. See the CWE Top 25 Guide.
Browser-facing protections: antiforgery, CORS and errors
Antiforgery
- Razor Pages are protected automatically; MVC needs
AddControllersWithViews(notAddControllersalone) and a globalAutoValidateAntiforgeryTokenAttribute, which skips GET, HEAD, OPTIONS and TRACE. - Minimal APIs: call
AddAntiforgery()andUseAntiforgery()after authentication and authorisation. The middleware validates only POST, PUT and PATCH; callIAntiforgery.ValidateRequestAsyncfor DELETE handlers that use cookies. - Watch method override:
HttpMethodOverrideMiddlewarein form-field mode placed before antiforgery can turn a POST into a DELETE that skips validation.
CORS
Microsoft's documentation is explicit that CORS relaxes the same-origin policy and is not a security feature. Configure the narrowest named policy with exact origins (no trailing slash). AllowAnyOrigin with AllowCredentials is rejected by the CORS service, and the common workaround SetIsOriginAllowed(_ => true) with AllowCredentials is equally insecure: it lets any site make credentialed requests and read responses. Be careful with SetIsOriginAllowedToAllowWildcardSubdomains where any subdomain can be taken over.
Errors, debugging and tracing
- ASP.NET Core: the developer exception page should only run when
app.Environment.IsDevelopment(); production usesUseExceptionHandlerand problem details without stack traces. Never setASPNETCORE_ENVIRONMENT=Developmenton a production host. - Legacy ASP.NET: in
web.configset<compilation debug="false">,<trace enabled="false">(otherwisetrace.axdcan expose request data and session values), and<customErrors mode="RemoteOnly">orOn. Use<deployment retail="true">inmachine.configon production servers to force these off. - HTTPS and HSTS: use
UseHttpsRedirectionandUseHstsfor browser apps; for APIs, reject plain HTTP rather than redirecting, because redirects break CORS preflight.
Secrets and machine identities
.NET configuration layers make it easy to put a secret anywhere: appsettings.json, environment variables, command-line arguments, user-secrets or a vault. Choose by environment, not by convenience.
| Mechanism | Encrypted | Use for | Notes |
|---|---|---|---|
appsettings.json | No | Non-secret settings only | Checked into source control; scan with secret detection |
Secret Manager (dotnet user-secrets) | No | Local development | Stored in the user profile, outside the repo; Microsoft states it is not a trusted store |
| Environment variables | No | Container and platform injection | Plain text, visible to process dumps and diagnostics; use __ as the separator |
| Azure Key Vault configuration provider | Yes | Production secrets that must exist | Secrets are cached for the app lifetime unless ReloadInterval is set; expired secrets still load by default |
| Managed identity (no secret) | Not applicable | Azure SQL, Storage, Service Bus, Key Vault and other Entra-integrated services | Removes the password from the connection string altogether |
Where identity fits
A connection string with User ID and Password is a long-lived non-human credential that ends up in config files, deployment logs and memory dumps. In Azure, a managed identity gives the app an Entra ID service principal whose credentials are never exposed to you; the app requests tokens through Azure.Identity. Microsoft's Key Vault provider guidance recommends DefaultAzureCredential for development convenience and ManagedIdentityCredential in production, which avoids the credential chain silently picking up a developer or environment credential.
- System-assigned versus user-assigned: system-assigned identities are deleted with the resource and cannot be shared; user-assigned identities live independently and suit several instances or frequently recycled resources. Microsoft recommends user-assigned for its own services. Either way, assign least-privilege RBAC roles and review them like any other privileged account. See the Azure RBAC and Service Principals Guide.
- Outside Azure: use workload identity federation (for example from Kubernetes or GitHub Actions) or a certificate credential, not a client secret. See the Workload Identity Federation Guide.
- Machine keys and Data Protection keys belong in the same inventory: give each an owner, a storage location and a rotation rule. See the Secrets Management Guide.
// SQL with a managed identity: no password in the connection string
"Server=tcp:contoso.database.windows.net;Database=orders;Authentication=Active Directory Managed Identity;Encrypt=True"
NuGet supply chain: consuming packages
| Control | Setting | Threat addressed |
|---|---|---|
| NuGet Audit | NuGetAudit (on by default since the .NET 8 SDK and NuGet 6.8), NuGetAuditMode (default all for net10.0 and later, direct otherwise), NuGetAuditLevel | Known-vulnerable direct and transitive packages, using the GitHub Advisory Database for nuget.org |
| Fail on severity | WarningsAsErrors with NU1903;NU1904 (high, critical) in a CI audit pipeline | Vulnerable packages reaching release builds |
| Package source mapping | <packageSourceMapping> in nuget.config with <clear /> on sources, prefix patterns such as Contoso.* | Dependency confusion between internal and public feeds |
| Central Package Management | Directory.Packages.props, ManagePackageVersionsCentrally, CentralPackageTransitivePinningEnabled | Version drift; lets you pin a patched transitive package |
| Lock files | RestorePackagesWithLockFile and dotnet restore --locked-mode in CI | Unreviewed dependency changes at build time |
| Signature validation | signatureValidationMode=require with trustedSigners (author or repository, optional owners) | Packages from untrusted signers; tampering fails with NU3008 |
- Source mapping caveat: packages already in the global packages folder bypass mapping, so use a per-repository packages folder in CI. Commands such as
dotnet list package --vulnerablestill query every configured source.
See the Malicious Package and Dependency Confusion Guide and the Software Supply Chain Security Guide.
NuGet supply chain: publishing packages
If your organisation publishes to nuget.org, the publishing key is the most valuable credential in the project: whoever holds it can ship code to every consumer. Microsoft announced Trusted Publishing for nuget.org on 22 September 2025. A GitHub Actions or GitLab CI/CD job requests an OIDC token, nuget.org validates it against a policy (repository owner, repository, workflow file and optional environment) and returns a temporary API key valid for one hour. Each OIDC token yields exactly one key.
- Deadline: on 3 August 2026 Microsoft announced that new nuget.org API keys are limited to 30 days from 17 August 2026, and that keys created before that date expire on 1 November 2026. Any pipeline still using an old key will stop publishing then.
- Bind to an environment: require a protected GitHub environment such as
releasein the policy, so only reviewed release jobs can obtain a key. - Least privilege in the workflow: grant
id-token: writeonly to the publishing job, not the whole workflow. - Resurrection protection: new policies are temporarily active for 7 days until a first publish records the repository and owner IDs, which stops a deleted repository being recreated under the same name to inherit the policy.
- Sign what you ship: author-sign packages with a code signing certificate so consumers can pin your fingerprint. See the Code Signing Security Guide and the CI/CD Pipeline Identity Security Guide.
jobs:
publish:
environment: release
permissions:
id-token: write
steps:
- uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}
- run: dotnet nuget push out/*.nupkg --api-key ${{ steps.login.outputs.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
Rolling out a .NET security baseline
- Inventory: list every application with its target framework, hosting model, and whether it is ASP.NET Core or .NET Framework. Flag anything on .NET 8 or 9 for migration before 10 November 2026.
- Find exposed keys and secrets: search repositories and
web.configfiles for<machineKey,Password=andAccountKey=; check machine keys against Microsoft's published hashes of disclosed keys. - Enable build-time checks in warning mode: NuGet Audit with
NuGetAuditMode=all, .NET security analysers and secret scanning. Run for two sprints to measure the backlog. - Apply platform defaults in a shared template: fallback authorisation policy, antiforgery filter, strict CORS policy, production exception handling, external Data Protection ring.
- Move to managed identities: pilot one service per team, removing the password from its connection string, then retire the old secret after a week with no fallback use.
- Switch the gates to blocking: fail CI on NU1903/NU1904 and on new high-severity analyser findings; adopt lock files and source mapping.
- Migrate publishing to Trusted Publishing and delete the old API keys.
- Rollback: keep each change in configuration (MSBuild properties,
nuget.config, policy registration) so it can be reverted per repository; for Data Protection moves, import the old key ring into the new store first so existing cookies stay valid.
Detection and measurement
- IIS and app logs: alert on
w3wp.exespawningcmd.exeor PowerShell, new.aspxfiles in web roots, and ViewState MAC validation failures (event 1316 in the Application log on .NET Framework) in bursts. - Defender signals: Microsoft Defender for Endpoint raises "Publicly disclosed ASP.NET machine key" and "IIS worker process loaded suspicious .NET assembly" alerts.
- Identity logs: managed identity sign-ins appear in Entra ID sign-in logs; alert on a managed identity used from an unexpected resource or calling an unusual service.
| Metric | Target |
|---|---|
| Applications on a supported .NET runtime | 100% after 10 November 2026 |
Builds with NuGet Audit in all mode and blocking on high/critical | 100% of release pipelines |
| Open high or critical NuGet advisories older than 30 days | Zero, or a recorded suppression |
| Azure workloads authenticating with a managed identity rather than a stored secret | Rising each quarter |
Fixed <machineKey> values found in source control | Zero |
| nuget.org packages published via Trusted Publishing | 100% before 1 November 2026 |
Framework mapping
| Area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | ISO/IEC 27001:2022 Annex A | MITRE ATT&CK |
|---|---|---|---|---|
| Secure development and analysers | SA-11, SA-15 | PR.PS-06 | A.8.25, A.8.28 | T1190 Exploit Public-Facing Application |
| Machine keys and Data Protection | SC-12, SC-28 | PR.PS-06 | A.8.24 | T1505.003 Web Shell |
| Secrets and managed identities | IA-5 | PR.PS-06 | A.8.24 | T1552.001 Credentials In Files |
| NuGet consumption and publishing | SR-3, SR-4 | ID.RA-01 | A.8.25 | T1195.001, T1195.002 |
| Runtime patching and debug settings | SI-2, CM-7 | ID.RA-01 | A.8.28 | T1190 |
In CIS Controls v8.1 this work sits mainly in Control 16 (Application Software Security). See the Secure Coding Guide.
Common failure patterns
- Copied machine keys: a
<machineKey>block pasted from a forum or sample ships to production, giving anyone who reads the sample the means to forge ViewState. This is the pattern behind the ASP.NET machine key attacks. - Patching without rotating: in the ToolShell SharePoint exploitation of July 2025, a web shell read the server's ValidationKey and DecryptionKey, so attackers could keep signing malicious ViewState after the CVE-2025-53770 fix until keys were rotated and IIS restarted. See the SharePoint and OneDrive Security Guide.
- Credentialed wildcard CORS:
SetIsOriginAllowed(_ => true)plusAllowCredentialsadded to "fix" a front-end error, exposing every authenticated API response to any website. - Ephemeral key rings in containers: Data Protection keys held in memory, so every redeploy logs users out; teams then "fix" it by baking a static key file into the image, which leaks with the image.
- Secrets in configuration history: a connection string with a password committed to
appsettings.Production.json, removed later but still in git history and old build artefacts. - Long-lived publishing keys in CI variables: a 365-day nuget.org key with push rights to all packages, shared across repositories, which is exactly what Trusted Publishing removes.
Practitioner checklist
- Migrate every .NET 8 and .NET 9 application to .NET 10 before 10 November 2026 and rebuild container images monthly.
- Set a fallback authorisation policy requiring authenticated users and review every
[AllowAnonymous]. - Persist Data Protection keys to shared, encrypted storage with an explicit application name.
- Remove fixed or copied
<machineKey>values, generate unique keys, and encrypt the section inweb.config. - Eliminate BinaryFormatter and related formatters; deserialise only into known types with System.Text.Json.
- Replace connection-string passwords with managed identities and use
ManagedIdentityCredentialin production. - Enable NuGet Audit in
allmode, package source mapping, lock files and blocking on NU1903/NU1904. - Move nuget.org publishing to Trusted Publishing before old API keys expire on 1 November 2026.
- Configure CORS with explicit origins only and never combine permissive origins with credentials.
- Monitor for
w3wp.exechild processes and new web files, and rotate keys after any web shell finding.
Standards and references
- Microsoft: .NET and .NET Core Support Policy
- Microsoft Learn: Data Protection Key Management and Lifetime in ASP.NET Core
- Microsoft Learn: Deserialization Risks in Use of BinaryFormatter and Related Types
- Microsoft Learn: Azure Key Vault Configuration Provider in ASP.NET Core
- Microsoft Learn: Trusted Publishing on nuget.org
- Microsoft Learn: Auditing Package Dependencies for Security Vulnerabilities
- Microsoft Threat Intelligence: Code Injection Attacks Using Publicly Disclosed ASP.NET Machine Keys
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: Secure Coding Guide · Software Supply Chain Security Guide · Secrets Management Guide · Insecure Deserialisation Guide · Java and Spring Security Guide