CVE Writeup
CVE-2026-40372 | ASP.NET Core: HMAC Bypass, Cookie Forgery and Critical Privilege Escalation
Technical analysis of CVE-2026-40372 (CVSS 9.1) in Microsoft.AspNetCore.DataProtection 10.0.0 to 10.0.6: broken HMAC validation, authenticated cookie forgery and mandatory key ring rotation after the 10.0.7 out-of-band patch.
On April 21st 2026 Microsoft shipped an out-of-band advisory for ASP.NET Core. CVE-2026-40372 carries a CVSS of 9.1, CWE-347 (Improper Verification of Cryptographic Signature) and a network attack vector with no credentials and no user interaction required. The affected component is Microsoft.AspNetCore.DataProtection, the same subsystem that signs auth cookies, antiforgery tokens, confirmation and password reset links across every ASP.NET Core app.
If you run .NET backends in production, especially on Linux behind Kubernetes, App Service for Linux or container VPS, this is a patch that cannot wait for the monthly cycle. And the patch alone is not enough: anyone exposed during the vulnerable window must rotate the key ring.
Context
Microsoft.AspNetCore.DataProtection is the crypto subsystem ASP.NET Core uses whenever a signature or authenticated encryption is needed. The .AspNetCore.Identity.Application cookie, antiforgery tokens, password reset links, OAuth state, protected distributed caches: all of them flow through it. It is enabled by default in Web templates and in Identity apps, not something developers wire manually.
Advisory timeline:
| Date | Event |
|---|---|
| 2026-04-21 | Microsoft publishes advisory and out-of-band release 10.0.7 |
| 2026-04-21 | Coordinated disclosure on dotnet/announcements#395 |
| 2026-04-22 | Second wave of vendor coverage (Qualys, SecurityAffairs, Hacker News) |
| 2026-04-22 | No public PoC, in-the-wild exploitation rated "less likely" by Microsoft |
The bug was introduced during .NET 10 development and affected every preview and RTM up to and including 10.0.6. The 8.0 and 9.0 servicing branches are not affected, the regression was never backported. Credit: anonymous researcher via MSRC, fix landed by Bri Brothers.
Technical analysis
What DataProtection does and where it breaks
DataProtection applies authenticated encryption in two phases: AES-CBC for the ciphertext, then an HMAC over the whole payload (header, key id, IV, ciphertext). On verification the receiver recomputes the HMAC with the same key and compares byte by byte against the value carried in the payload. A mismatch rejects the payload before any decryption happens.
On NuGet package version 10.0.6 this comparison is broken. The official advisory phrases it:
Improper verification of cryptographic signature in ASP.NET Core allows an unauthorized attacker to elevate privileges over a network.
In practice the ManagedAuthenticatedEncryptor implementation (the managed code path used on Linux, macOS and generally outside the Windows CNG route) "sometimes ignores the correct hash". An attacker who crafts a payload with an all-zero HMAC, or one that triggers the mishandled condition, gets it accepted as valid. Microsoft compares the bug class to MS10-070, the 2010 ASP.NET padding oracle that back then allowed viewstate key recovery and impersonation.
When you are actually vulnerable
The advisory splits affected configurations into two buckets:
Primary (most common): app on net10.0, Microsoft.AspNetCore.DataProtection 10.0.6 referenced from NuGet (directly or transitively via .StackExchangeRedis, .EntityFrameworkCore, .AzureKeyVault, .AzureStorage, .Redis), and the NuGet binary loaded at runtime, and the application running on Linux, macOS or any non-Windows OS.
Secondary: apps or libraries consuming the net462 or netstandard2.0 target framework asset of package 10.0.0 to 10.0.6. The Windows exception does not apply here because the managed code path is used in every case.
Not vulnerable:
- Apps on Windows with DataProtection in normal usage
- Framework-dependent apps on
net10.0where the installed ASP.NET Core shared framework is at or above the PackageReference (typical App Service scenario) - Any app on
Microsoft.AspNetCore.DataProtection8.0.x or 9.0.x, regardless of OS
The combo that hits hard is the modern deployment template: Linux container, net10.0 app, DataProtection pulled in transitively (Redis sink for distributed key ring, Azure Key Vault for encryption, EF Core for persistence). In a Kubernetes cluster all pods share the same key ring, so compromise is uniform.
Spotting your exposure in 30 seconds
Inside the project working directory:
# 1. Transitive DataProtection dependencies
dotnet list package --include-transitive \
| grep -i "DataProtection"
# 2. Target framework
grep -r "TargetFramework" *.csproj | head
# 3. Container runtime OS
docker inspect <image> | jq '.[0].Os'
# or inside the Dockerfile
grep -iE "FROM.*(alpine|linux|debian|ubuntu)" Dockerfile
If the output shows Microsoft.AspNetCore.DataProtection 10.0.0 up to 10.0.6, TargetFramework net10.0 and a Linux base image, the machine is in the primary configuration. Go straight to the remediation plan.
Proof of concept
Microsoft did not publish a PoC and no exploit code is publicly available at the time of writing. The mechanics as described are still enough to know what to look for.
An attacker who knows the DataProtection token layout produces a payload shaped like this:
[ magic header 09 F0 C9 F0 ]
[ key id (16 byte GUID) ]
[ random IV ]
[ arbitrary ciphertext ]
[ HMAC = 00 00 00 ... 00 ] <-- all zeros, or a value that trips
the mishandled condition
The token is submitted as a .AspNetCore.Identity.Application cookie or as a form antiforgery token. The vulnerable code path accepts the payload, decrypts the ciphertext with the current key ring key and hands an authenticated ClaimsPrincipal to the Authorization middleware. From there the request walks the app as the forged user.
If the ciphertext carries a known admin UserId (harvested via /api/users, /admin/users.json or a backup dump), the outcome is administrative access. If the attacker does not know valid IDs, the reverse flow works too: hit endpoints that return a protected token (e.g. password reset), capture the response, extract the current HMAC key via padding-oracle, then mint legitimate tokens.
The sneakiest effect is not immediate access. Any legitimately signed token the app issues to an attacker during the vulnerable window, say an API key, a refresh token, an onboarding magic link, survives the patch. The fix corrects validation, but a token the app generated correctly stays cryptographically valid until its natural expiry or manual revocation.
Real-world impact
Operational scenarios we have mapped against our Italian assessment portfolio:
- Italian SaaS with .NET 10 backend on App Service for Linux: forgeable session cookies for any tenant, full customer data access without triggering MFA because the Identity cookie is post-MFA.
- .NET 10 API gateway with DataProtection-protected OAuth state: the attacker mints arbitrary signed state, bypasses the CSRF check of the OAuth flow and completes fraudulent consent.
- Background worker on a Linux container signing transactional magic links: valid links targeting accounts that never signed up, email bombing with authentic links.
- Enterprise admin portal .NET deployed on Kubernetes: a single cookie forged from a
UserIdleaked via GitHub Actions logs grants production admin access.
CVSS 9.1 is credible. It is not RCE (high integrity, zero availability impact) but the blast radius is the core of user session. Customers that integrate Identity with Azure AD B2C, Auth0 or Clerk still stay exposed on the DataProtection side for internal APIs using tokens minted by the same ASP.NET process.
Remediation
Step 1, patch NuGet (mandatory)
<!-- csproj -->
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.DataProtection"
Version="10.0.7" />
</ItemGroup>
dotnet restore
dotnet build -c Release
dotnet publish -c Release -o out
# redeploy
After restore, verify resolution really lands on 10.0.7:
dotnet list package --include-transitive | grep DataProtection
Step 2, key ring rotation (mandatory if exposed)
Straight from the official advisory:
// Run once, with access to the same production key ring
var services = new ServiceCollection()
.AddDataProtection()
// ... existing repository and protection configuration
.Services
.BuildServiceProvider();
var keyManager = services.GetRequiredService<IKeyManager>();
keyManager.RevokeAllKeys(
revocationDate: DateTimeOffset.UtcNow,
reason: "CVE-2026-40372: DataProtection 10.0.6 validation bypass");
RevokeAllKeys marks every key as revoked and spawns a new one at the next protect operation. All users get logged out, all antiforgery tokens are reissued. If your key ring is persisted on Redis, Azure Blob or EF Core, the revocation propagates to the other cluster nodes automatically.
For a less disruptive rotation, when you know exactly which keys were active during the vulnerable window, use RevokeKey(Guid keyId, string reason) to revoke the single key.
Step 3, audit long-lived artifacts
Patch and rotation do not invalidate tokens the app issued during the window. Those must be revoked at application level:
- API keys and refresh tokens persisted in the DB, issued by protected endpoints: regenerate and notify
- Non-expired password reset or email confirmation magic links: invalidate in table
- OAuth refresh tokens toward external clients: revoke upstream
- JWTs tracked by a
jtirevocation list: full sweep
Step 4, recommended
- Audit plaintext inside protected payloads. If you stored DB secrets, third-party API keys inside
IDataProtector.Protect()output, treat them as potentially decrypted and rotate them at their source service. - Review web server logs for anomalous volume against endpoints that consume protected payloads. A padding-oracle attack needs many requests per recovered byte. Sustained traffic with varying cookies or query parameters against a single authenticated endpoint during the vulnerable window is a strong signal. Sample Kusto/Splunk query:
Requests
| where Url matches regex @"(/Account/|/Identity/|csrf)"
| where TimeGenerated between (datetime(2026-03-01) .. datetime(2026-04-22))
| summarize RequestCount=count(), UniqueCookies=dcount(Cookie)
by ClientIP, bin(TimeGenerated, 1h)
| where RequestCount > 5000 and UniqueCookies > 500
Notes from the field, SPECTROSEC
In a March assessment against an Italian B2B SaaS platform, we recovered, for entirely different reasons, a chunk of the DataProtection configuration: the key ring was persisted on Azure Blob with a non-expiring SAS, and the keys were materialized in container memory. That was a medium severity finding, we pulled the container out of scope and the keys landed on our laptop.
On the same platform, if they had been running Microsoft.AspNetCore.DataProtection 10.0.6 (today, two weeks ago), the CVE-2026-40372 vector would have made key ring exfiltration irrelevant: any outside user, starting from a UserId harvested via /api/users/me of a trial account, would have forged a valid admin cookie. Zero database interaction, zero monitoring anomalies, one well-crafted cookie.
This is why, even when the patch lands within 24 hours of the advisory, the job does not stop at dotnet restore. Auditing tokens issued during the window and rotating the key ring are part of remediation, not a bonus for the paranoid.
If you run an ASP.NET Core 10 backend in production and have not started rotation yet, do it before closing this tab. If you need an independent review of the rotation process or a re-test once patched, drop us a line.
SPECTROSEC Team | professional pentest engagements, write to info@spectrosec.com https://spectrosec.com