On April 21, 2026, Microsoft published an out-of-band advisory for CVE-2026-40372, an elevation-of-privilege vulnerability in the Microsoft.AspNetCore.DataProtection NuGet package. The flaw sits in the cryptographic core of every ASP.NET Core application that relies on Data Protection: the same component that signs authentication cookies, antiforgery tokens, OAuth state parameters, and any payload an app passes to IDataProtector.Protect. Microsoft assigned a CVSS score of 9.1 and rated the issue Critical. The patched version is 10.0.7.
This article is a two-layered breakdown. The first part is for non-technical readers who need to decide quickly whether their organization has anything to do, and what exactly. The second part dissects the bug at the level of what is actually broken inside the encryptor, how a remote attacker can turn it into forged session cookies, and why patching alone does not fully close the door.
Part I. Executive breakdown
What happened
A regression introduced during the .NET 10 development cycle made it into shipped code. Specifically, the managed authenticated encryptor inside Microsoft.AspNetCore.DataProtection started computing its HMAC validation tag over the wrong portion of the payload, and in some cases skipped validation entirely. The end result: protected payloads that should have been rejected as tampered or forged were accepted as legitimate.
The Data Protection API is what ASP.NET Core uses to sign and encrypt sensitive data on behalf of the application. Authentication cookies, antiforgery tokens, password reset links, OAuth state, anything a developer hands to IDataProtector ends up flowing through this code path. When the integrity check is broken, the chain of trust between the user's browser and the application server is broken with it.
In practice, an attacker who knew about the bug could mount a padding oracle attack against a vulnerable app: by sending many crafted requests against an endpoint that consumes a protected payload, they could iteratively reconstruct the plaintext of an existing cookie or forge a new one that the server would accept. The capability of this bug is comparable to MS10-070, the legacy ASP.NET padding oracle vulnerability that turned every IIS site running affected versions into a target for the same class of attack.
Who is affected
| Component | Status |
|---|---|
Microsoft.AspNetCore.DataProtection 10.0.0 through 10.0.6 (NuGet) |
Vulnerable on Linux, macOS, and other non-Windows runtimes. |
Microsoft.AspNetCore.DataProtection 10.0.7 |
Patched. |
| ASP.NET Core 8.0.x and 9.0.x | Not affected. The defective code path is a 10.0 regression and was never backported. |
Applications running on Windows (net10.0) |
Not affected. The Windows runtime uses a separate CNG-backed encryptor that does not include the broken code. |
Applications targeting net462 or netstandard2.0 and consuming the vulnerable package |
Affected on any operating system, including Windows, because they share the managed encryptor code path. |
There is one important nuance. If your application targets net10.0 and runs framework-dependent against an installed shared framework whose version is greater than or equal to your PackageReference version, you are running the shared framework's copy and not the NuGet copy. The NuGet binary is shadowed, and you are not affected. If the shared framework version is older than the package reference, the NuGet binary wins and you are affected. This is the kind of detail that is easy to miss in a hurry.
Why this matters beyond .NET
Padding oracle bugs are not new. The pattern was well understood before MS10-070, and it was well understood after. What CVE-2026-40372 illustrates is how easily a regression in cryptographic validation slips through review when the surface change looks small. The vulnerable HMAC routine compiles, passes the existing unit tests, and behaves correctly on the happy path. Forged payloads with all-zero HMAC bytes still validate, because the validation never actually happens (or happens against the wrong bytes). Nothing observable from the outside breaks until an adversary specifically tries to break it.
The broader lesson: any framework that sits on top of cryptographic primitives needs explicit, independently maintained tests for negative cases. "Does the verifier reject a payload with a deliberately wrong tag?" is a test that should exist for every code path, on every supported target framework, and it should be run as part of every release.
Recommended actions
- Identify whether you are affected. Run
dotnet nuget why Microsoft.AspNetCore.DataProtectionagainst every solution in your portfolio. The command outputs every direct and transitive reference to the package, which is exactly what you need to scope the blast radius. Do not forget storage providers like.AzureKeyVault,.AzureStorage,.StackExchangeRedis,.EntityFrameworkCore, and.Redis, all of which pull the affected dependency. - Upgrade affected applications to 10.0.7 or later and redeploy them. The corrected validation routine rejects forged payloads carrying the all-zero HMAC bytes that the bug enabled.
- Rotate the Data Protection key ring if the application served internet-exposed endpoints during the vulnerable window. Tokens an attacker tricked the application into issuing during that window are legitimately signed, and they survive the patch. They do not survive key rotation. Microsoft documents
IKeyManager.RevokeAllKeysfor full ring rotation, andRevokeKey(Guid keyId, string reason)for surgical cases where you can pin the affected window to specific keys. - Audit long-lived application-layer artifacts issued during the window. API keys, refresh tokens, password reset links, and email confirmation tokens that were minted while the bug was live can survive even key rotation, because the application persisted them outside the Data Protection layer. They have to be rotated at the application layer.
- Audit plaintext stored inside protected payloads. If your code uses
IDataProtector.Protectto wrap database connection strings, third-party API keys, or other long-lived secrets, treat those secrets as potentially disclosed and rotate them at their source. - Review web server logs for sustained, anomalous request volume against endpoints that consume protected payloads. A practical padding oracle attack typically generates orders of magnitude more requests than legitimate traffic, varying the cookie or query parameter on each request. That signature is hard to hide.
Part II. Technical breakdown
What ASP.NET Core Data Protection actually does
The Data Protection API is the modern replacement for the legacy <machineKey> system in ASP.NET. When an application calls IDataProtector.Protect(plaintext), the framework:
- Picks the active key from the Data Protection key ring (a managed set of keys with creation dates, expiration dates, and revocation flags).
- Derives a sub-key for confidentiality and a sub-key for integrity using a key derivation function bound to the active key plus a per-payload context string.
- Encrypts the plaintext using AES in CBC mode with the confidentiality sub-key, against a freshly generated random IV.
- Computes an HMAC (typically HMAC-SHA-256) over the IV concatenated with the ciphertext, using the integrity sub-key.
- Emits the protected payload as:
[key id] || [IV] || [ciphertext] || [HMAC tag].
Unprotect runs the chain in reverse:
- Reads the key id, looks up the key, derives the same sub-keys.
- Recomputes the HMAC over the IV and ciphertext.
- Compares the recomputed tag against the tag carried in the payload, in constant time, before doing anything else.
- Only if the tag matches does it remove the PKCS#7 padding and return the plaintext.
That ordering is the entire point. The MAC is the gate. If it validates, the ciphertext is trusted and decryption proceeds. If it fails, the call throws and the application returns a generic error. An attacker attempting to flip ciphertext bytes to manipulate plaintext after decryption never reaches the decryption step, because their tampered tag does not match the recomputed tag.
The actual bug
The defect lives in CalculateAndValidateMac inside the managed authenticated encryptor that ships with the affected package versions on non-Windows runtimes (and on the net462 / netstandard2.0 assets, which are managed on every OS, including Windows).
Two classes of behavior were observed:
- MAC computed over the wrong portion of the payload. The HMAC was being calculated against a span that did not include all of the ciphertext (or, in some scenarios, included extra unrelated bytes). The recomputed tag differed from the legitimate tag for any real payload, but the comparison was performed in a way that did not reliably surface that mismatch.
- Validation skipped entirely under specific conditions. In certain code paths the comparison degenerated into a check against an effectively constant value (zeros, in practice), so any payload accompanied by an all-zero tag would validate.
The fix in 10.0.7 explicitly recomputes the tag over the correct range, performs a constant-time comparison against the carried tag, and rejects payloads whose carried tag is not exactly equal to the recomputed value.
Why the all-zero HMAC matters for forensics
If an attacker forged payloads during the vulnerable window, those payloads carry an all-zero HMAC tag, because that was the only practical way to satisfy the broken validator. A targeted log review on a captured payload trace can flag this. In practice, you are unlikely to have full payload captures sitting around, but if you have an upstream WAF or reverse proxy logging cookie values, this is one of the few ways to retroactively identify successful exploitation.
Padding oracle, in one paragraph
The "padding oracle" name comes from the side channel a vulnerable verifier creates. When the MAC check is broken or absent, decryption proceeds against attacker-controlled ciphertext. PKCS#7 padding validation runs after decryption. If the padding is valid, the application returns one error (or behaves one way); if it is invalid, it returns a different error (or behaves a different way). That binary distinction lets an attacker, working one byte at a time, recover the plaintext of any captured ciphertext, or construct a new ciphertext that decrypts to a chosen plaintext. Roughly 256 requests are needed per byte of plaintext, which is why heavy, sustained, repetitive traffic against a single endpoint is the visible signature of this attack.
Reproducing the conditions
A simplified view of what happens during exploitation, using cookie authentication as the running example:
[1] Attacker captures a legitimate cookie from any user session
(the cookie itself does not need to be theirs):
Cookie: .AspNetCore.Cookies=CfDJ8...[ciphertext]...[tag]
[2] Attacker iterates over the last byte of the ciphertext block,
sending the modified cookie to any endpoint that calls Unprotect:
Cookie: .AspNetCore.Cookies=CfDJ8...[ciphertext']...[tag]
On the patched validator: every modified cookie is rejected
at the MAC check, before decryption, with the same generic
error. No oracle exists.
On the vulnerable validator: the MAC check passes (or is
bypassed), decryption proceeds, and PKCS#7 padding validation
surfaces a distinguishable response when the padding is valid.
[3] By repeating step 2 across all positions, the attacker recovers
the plaintext of the captured cookie, or constructs a new
ciphertext that decrypts to the identity of a chosen user.
[4] Attacker replaces their session cookie with the forged value
and is authenticated as the chosen user.
The vulnerability is straightforward to trigger, but worth stating plainly: this is not a purely theoretical attack. A motivated attacker with a few hours of API access can plausibly recover or forge a session cookie on a vulnerable endpoint.
From padding oracle to persistent compromise
Forging an authentication cookie is the loud part of the attack. The quiet, more dangerous part is what happens after the attacker authenticates with a forged cookie:
- The application sees a request with what looks like a valid session cookie. It does what it was designed to do: authenticate the request, attach the user identity, and proceed.
- The attacker performs a normal session-refresh action, hits a long-lived token issuance endpoint, or triggers a password reset for the impersonated account. The application issues a legitimately signed new cookie, refresh token, API key, or email link, using a key that is currently in the Data Protection ring and is fully valid.
- The attacker now holds a token that does not depend on the bug. It is real, it is correctly signed, and it is going to keep working until either it expires or the application revokes the underlying key.
This is why patching alone is insufficient. The 10.0.7 update fixes the validator but does nothing to the tokens already minted. The only ways to invalidate them are:
- Rotate the Data Protection key ring (
RevokeAllKeysor surgicalRevokeKey). - Rotate any application-managed identity artifacts that were issued during the window and persist outside Data Protection (database-stored API keys, refresh tokens, password reset entries, and so on).
Why Windows-on-net10.0 is not affected
ASP.NET Core 10 ships with two parallel implementations of the authenticated encryptor:
- On Windows running
net10.0, the framework uses a CNG-backed implementation that delegates to the native cryptographic stack viaBCryptGenerateSymmetricKey,BCryptEncrypt, and friends. The defective managed code is not in the call path. - On Linux, macOS, FreeBSD, and other non-Windows runtimes, the framework falls back to the managed implementation, which contains the bug. The same managed implementation is used on Windows when the target is
net462ornetstandard2.0, because the CNG-only assets only ship fornet10.0.
This is also why the Microsoft advisory takes the trouble to spell out the asset resolution rules. Whether the vulnerable code is loaded depends on the precise combination of target framework, runtime asset selected, and shared framework version installed on the deployment target. In a heterogeneous environment running mixed Windows and Linux containers, the same package reference can produce a vulnerable binary on one host and a safe binary on the next.
A note on the two CVSS scores
A small but worth-noting detail: the CVE record on MSRC and the downstream feeds (NVD, OpenCVE, Red Hat) lists CVSS 9.1 with the vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N, treating the attack as fully unauthenticated. The Microsoft dotnet/announcements advisory itself lists CVSS 8.1 with PR:L, treating the attack as requiring some level of privilege. The truth depends on the application: an internet-exposed endpoint that consumes a protected payload from any unauthenticated visitor falls under the 9.1 vector, while an endpoint that requires an authenticated session before processing protected data falls under the 8.1 vector. For risk modeling, assume the 9.1 vector unless your specific deployment demonstrably forecloses it.
Timeline
| Date | Event |
|---|---|
| 2026-04-11 | CVE-2026-40372 reserved by Microsoft. |
| 2026-04-21 | Microsoft publishes the advisory and ships Microsoft.AspNetCore.DataProtection 10.0.7 to NuGet. Out-of-band release. |
| 2026-04-22 | First independent write-ups appear (Duende, eSecurity Planet, The Hacker News, Help Net Security). |
There is no public exploit at the time of writing, and Microsoft has not reported active exploitation in the wild. That does not mean exploitation has not happened. Padding oracle attacks against a fully bypassed MAC are well within reach for any moderately resourced attacker who has read the advisory carefully.
What we should learn from CVE-2026-40372
A few patterns from this CVE are worth carrying back to other codebases:
- Negative tests for cryptographic validation are not optional. Every verifier should have explicit unit tests that hand it a wrong tag and assert that it rejects the payload, and those tests should run on every supported target framework and runtime asset, not just the developer's preferred one.
- Asset and target framework matrices need crypto-aware coverage. "It works on Windows" and "the package compiles" are not equivalent to "the cryptographic guarantees hold across all supported assets". When a package ships separate assets for
net10.0,net462, andnetstandard2.0, the security-relevant code in each asset deserves the same scrutiny. - Patching is the start of remediation, not the end. Any vulnerability that allows the issuance of legitimately signed long-lived artifacts forces a separate remediation phase: token rotation, key rotation, and audit of stored capabilities. Build that phase into your incident response runbook before you need it.
- SBOMs pay for themselves the day a CVE drops. The Microsoft advisory explicitly recommends
dotnet nuget whyto scope exposure. That works for one solution at a time. For an organization with hundreds of services, a maintained SBOM (CycloneDX or SPDX) per repository turns "are we affected?" from a multi-day investigation into a single query. - Trust-boundary code should be reviewed against its history, not just its diff. A clean diff that touches
CalculateAndValidateMacshould automatically trigger a heightened review: not "does the change look right?" but "does the code still satisfy every property the previous version satisfied?". For cryptographic code, regression-by-refactor is the most common path to bugs of this severity.
How Pragma Core addresses this class of problem
Pragma Core is built for exactly the situation organizations found themselves in on April 21, 2026: an advisory drops, a single transitive dependency in an obscure corner of the codebase turns out to be the load-bearing one, and security teams have hours, not days, to scope the blast radius.
Continuous third-party package tracking with CVE correlation
The dependency tracker in Pragma Core watches every connected repository for the package versions, transitive paths, and target frameworks involved. When CVE-2026-40372 lands in the catalog, every repository pulling Microsoft.AspNetCore.DataProtection 10.0.0 through 10.0.6 (directly or via a storage provider) is flagged automatically, with the full transitive path made visible. There is no manual triage step between "Microsoft published the advisory" and "we know which of our services are affected".
Full SBOM per repository, exportable as CycloneDX
For every connected repository, Pragma Core produces a complete inventory of libraries, versions, licenses, and package URLs. The SBOM is exportable as CycloneDX JSON. In the context of a CVE like this one, that inventory turns the question "what installations do we have, and which version is each running?" from a multi-team scramble into a single export.
AI security research agents focused on attack chain reconstruction
The autonomous agents in Pragma Core do not stop at "we found a vulnerable package". They reason over the actual call paths: which endpoints consume IDataProtector.Unprotect, which routes are anonymous versus authenticated, which protected payloads carry capabilities (refresh tokens, password reset state, OAuth state) that would survive a key rotation. That reasoning is the difference between "you have a CVE" and "here is the prioritized list of services where the CVE is actually reachable, and these three are the ones an attacker would target first".
Interactive call graphs with vulnerability overlay
For organizations that want to verify the agent's conclusions themselves, the call graph view shows how IDataProtector flows through the codebase, which controllers expose Unprotect-consuming endpoints, and which background services persist tokens issued from those endpoints. The vulnerability overlay highlights the affected functions on top of the live graph, which is the fastest way to move from "we have a finding" to "we have a fix plan".
Human-led AppSec investigations
The expert-driven research module in Pragma Core is built for exactly the investigations that follow a CVE like this one. An AppSec operator drives the deeper analysis (which protected payloads are long-lived, which endpoints would not have logged a padding oracle attack) supported by the agents and platform context already in your workspace. Same subscription, same repository coverage, no separate engagement to spin up.
Native repository integration
The platform connects to GitHub, GitLab, and Azure DevOps in minutes. Continuous scanning picks up dependency changes the moment they land in a pull request, which means the next time Microsoft.AspNetCore.DataProtection ships a security release, the upgrade landing in your csproj is already on Pragma Core's radar.
Closing thoughts
CVE-2026-40372 is, on the surface, a single function with a regression. Underneath, it is a textbook case of why cryptographic code is special: the bug compiles, passes the happy-path tests, and ships into a critical security boundary that millions of applications depend on. The integrity check that gates every authentication cookie in ASP.NET Core was effectively removed for two months on the most common production runtime (Linux), and there was no observable failure mode for any developer running the framework normally.
Patching is the easy part. The hard parts are scoping the blast radius across a real-world portfolio, deciding which key rings need to be rotated, and auditing every long-lived artifact that an attacker might have caused the application to issue during the window. Organizations that had visibility into their dependency graph and their authentication endpoints handled this in hours. Organizations that did not are still handling it.
If your team wants to move from "we react to CVEs" to "we know our exposure before the advisory drops", Pragma Core was built for that step. Reach us directly at pragma-core.com for a demo or to discuss how the platform fits into your security pipeline.
Sources
- Microsoft, Microsoft Security Advisory CVE-2026-40372 – ASP.NET Core Elevation of Privilege (dotnet/announcements issue #395), April 21, 2026.
- Microsoft Security Response Center, CVE-2026-40372 Security Update Guide.
- Duende Software, Update Guidance for CVE-2026-40372 - ASP.NET Data Protection, April 22, 2026.
- eSecurity Planet, CVE-2026-40372: Microsoft Patches ASP.NET Core Privilege Escalation Vulnerability, April 22, 2026.
- The Hacker News, Microsoft Patches Critical ASP.NET Core CVE-2026-40372 Privilege Escalation Bug, April 22, 2026.
- OpenCVE, CVE-2026-40372 record.
- GitLab Advisory Database, Microsoft Security Advisory CVE-2026-40372 – ASP.NET Core Elevation of Privilege.