CVE Writeup
CVE-2026-40372 | ASP.NET Core: HMAC bypass, cookie forgery e privilege escalation critica
Analisi tecnica della CVE-2026-40372 (CVSS 9.1) in Microsoft.AspNetCore.DataProtection 10.0.0, 10.0.6: validazione HMAC difettosa, forgia di cookie autenticati e rotazione obbligatoria del key ring dopo la patch 10.0.7.
Il 21 aprile 2026 Microsoft ha pubblicato un advisory out-of-band per ASP.NET Core. La CVE-2026-40372 porta CVSS 9.1, CWE-347 (Improper Verification of Cryptographic Signature) e un vettore di rete senza credenziali, senza interazione utente. Il componente colpito è Microsoft.AspNetCore.DataProtection, lo stesso che firma i cookie di autenticazione, gli antiforgery token, i link di conferma e password reset di qualunque app ASP.NET Core.
Per chi gira backend .NET in produzione, in particolare su Linux dietro Kubernetes, App Service for Linux, container su VPS, questa è una patch che non può aspettare il ciclo mensile. E la patch da sola non basta: chi è stato esposto durante la finestra vulnerabile deve ruotare il key ring.
Contesto
Microsoft.AspNetCore.DataProtection è il sistema crittografico che ASP.NET Core usa ovunque serva una firma o una cifratura autenticata. Cookie .AspNetCore.Identity.Application, token antiforgery, link di password reset, OAuth state, cache distribuite protette: tutto passa da qui. È abilitato by default nei template Web e nelle app Identity, non è qualcosa che gli sviluppatori integrano manualmente.
La timeline dell'advisory:
| Data | Evento |
|---|---|
| 2026-04-21 | Microsoft pubblica l'advisory e la release out-of-band 10.0.7 |
| 2026-04-21 | Disclosure coordinata sull'issue dotnet/announcements#395 |
| 2026-04-22 | Seconda ondata di comunicati vendor (Qualys, SecurityAffairs, Hacker News) |
| 2026-04-22 | Ad oggi nessun PoC pubblico, exploitation in-the-wild giudicata da Microsoft "less likely" |
Il bug è stato introdotto durante lo sviluppo di .NET 10 e ha colpito tutte le preview e RTM fino alla 10.0.6 inclusa. Le branch 8.0 e 9.0 non sono affette, la regressione non è stata portata all'indietro. Reporter: ricercatore anonimo via MSRC, fix landed da Bri Brothers.
Analisi tecnica
Il ruolo del DataProtection e dove si rompe
Il DataProtection applica uno schema di authenticated encryption in due fasi: AES-CBC per il ciphertext, poi un HMAC su tutto il payload (header, key id, IV, ciphertext). In verifica, chi riceve il token ricalcola l'HMAC con la stessa chiave e lo confronta byte per byte con quello trasportato nel payload. Se differiscono, il payload è rifiutato prima ancora di essere decifrato.
Nella versione 10.0.6 del pacchetto NuGet, questo confronto è difettoso. L'advisory ufficiale lo riassume così:
Improper verification of cryptographic signature in ASP.NET Core allows an unauthorized attacker to elevate privileges over a network.
In pratica l'implementazione del ManagedAuthenticatedEncryptor (il code path managed usato su Linux, macOS e in generale fuori dal path CNG di Windows) "sometimes ignores the correct hash". Un attaccante che costruisce un payload con HMAC a tutti zero, o con un HMAC che colpisce la condizione non gestita, lo fa accettare come valido. Microsoft parla di una classe di bug paragonabile a MS10-070, il padding oracle ASP.NET del 2010 che all'epoca consentiva il recupero del viewstate key e l'impersonificazione.
Quando sei effettivamente vulnerabile
L'advisory distingue due configurazioni colpite:
Primary (più comune): app su net10.0, con Microsoft.AspNetCore.DataProtection 10.0.6 referenziato da NuGet (direttamente o via transient come .StackExchangeRedis, .EntityFrameworkCore, .AzureKeyVault, .AzureStorage, .Redis), e la copia NuGet del binario caricata a runtime, e l'app girata su Linux, macOS o qualunque SO non Windows.
Secondary: app o librerie che consumano il target framework net462 o netstandard2.0 del pacchetto 10.0.0, 10.0.6. Qui l'eccezione Windows non si applica perché il code path managed è usato in ogni caso.
Non vulnerabile:
- App su Windows con DataProtection in uso normale
- App framework-dependent su
net10.0con shared framework ASP.NET Core installato pari o successivo alla PackageReference (tipico scenario App Service) - Qualsiasi app su
Microsoft.AspNetCore.DataProtection8.0.x o 9.0.x, indipendentemente dal SO
La combinazione che colpisce duro è quindi la tipica deploy moderna: container Linux, app net10.0, DataProtection referenziato via transient (Redis sink per key ring distribuita, Azure Key Vault per encryption, EF Core per persistenza). In un cluster Kubernetes tutti i pod condividono la stessa key ring, quindi la compromissione è uniforme.
Identificare l'esposizione in 30 secondi
Dentro la working directory del progetto:
# 1. Dipendenze transitive a DataProtection
dotnet list package --include-transitive \
| grep -i "DataProtection"
# 2. Target framework
grep -r "TargetFramework" *.csproj | head
# 3. SO del container runtime
docker inspect <image> | jq '.[0].Os'
# oppure dentro Dockerfile
grep -iE "FROM.*(alpine|linux|debian|ubuntu)" Dockerfile
Se l'output mostra Microsoft.AspNetCore.DataProtection 10.0.0 fino a 10.0.6, TargetFramework net10.0 e base image Linux, la macchina è nella primary configuration. Passi direttamente al remediation plan.
Proof of concept
Microsoft non ha pubblicato un PoC e al momento non è disponibile exploit code dimostrativo. La meccanica descritta è però sufficiente per capire cosa cercare.
Un attaccante che conosce il formato del token DataProtection produce un payload così strutturato:
[ magic header 09 F0 C9 F0 ]
[ key id (16 byte GUID) ]
[ IV random ]
[ ciphertext arbitrario ]
[ HMAC = 00 00 00 ... 00 ] <-- tutti zero o valore che colpisce
la condizione gestita male
Il token così costruito viene presentato come cookie .AspNetCore.Identity.Application oppure come antiforgery token nel form. La versione vulnerabile accetta il payload, decifra il ciphertext con la chiave corrente del key ring e passa un ClaimsPrincipal autenticato al middleware Authorization. Da quel momento la richiesta naviga l'app come l'utente forgiato.
Se il ciphertext contiene un UserId di un admin conosciuto (trovato via /api/users, /admin/users.json o dump di backup), il risultato è accesso amministrativo. Se l'attaccante non conosce gli ID, può provare il flusso inverso: richiede endpoint che restituiscono un token protetto (es. password reset), cattura la response, estrae la chiave HMAC corrente via padding-oracle, emette token legittimi.
L'effetto più subdolo non è l'accesso immediato. È che qualunque token legittimamente firmato emesso dall'app a un attaccante durante la finestra vulnerabile, per esempio una API key, un refresh token, un magic link di onboarding, sopravvive alla patch. Il fix corregge la validazione, ma un token che l'app ha generato correttamente resta crittograficamente valido fino a scadenza o revoca manuale.
Impatto reale
Gli scenari operativi che abbiamo mappato sul nostro parco assessment italiano:
- SaaS italiano con backend .NET 10 su App Service for Linux: cookie di sessione forgiabili per qualsiasi tenant, accesso completo ai dati cliente senza far suonare MFA perché il cookie Identity è post-MFA.
- API gateway .NET 10 con OAuth state protetto via DataProtection: l'attaccante emette state firmati arbitrari, bypassa il CSRF check del flusso OAuth, completa il consent fraudolento.
- Worker background su container Linux che firma magic link di invio: link validi per account di destinazione mai iscritti, email bombing con link autentici.
- Portale amministrativo enterprise .NET deployato su Kubernetes: un unico cookie forgiato a partire da un leak di UserId su GitHub Actions o log dà accesso admin in produzione.
Il CVSS 9.1 è credibile. Non è un RCE (integrity alta, availability nulla) ma il perimetro colpito è il cuore della sessione utente. I clienti che integrano Identity con Azure AD B2C, Auth0, Clerk rimangono esposti sul lato DataProtection per le API interne che usano token emessi dallo stesso processo ASP.NET.
Remediation
Step 1, patch NuGet (obbligatorio)
<!-- 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
Controllare che dopo il restore la risoluzione sia davvero 10.0.7:
dotnet list package --include-transitive | grep DataProtection
Step 2, rotazione key ring (obbligatoria se esposti)
Dall'advisory ufficiale:
// Da eseguire una volta, con accesso allo stesso key ring di produzione
var services = new ServiceCollection()
.AddDataProtection()
// ... stessa configurazione di repository e protection
.Services
.BuildServiceProvider();
var keyManager = services.GetRequiredService<IKeyManager>();
keyManager.RevokeAllKeys(
revocationDate: DateTimeOffset.UtcNow,
reason: "CVE-2026-40372: DataProtection 10.0.6 validation bypass");
RevokeAllKeys segna tutte le chiavi come revocate e ne genera una nuova alla prossima operazione di protect. Tutti gli utenti sono disconnessi, tutti gli antiforgery token sono riemessi. Se il tuo key ring è persistito su Redis, Azure Blob o EF Core, la revoca si propaga agli altri nodi del cluster automaticamente.
Per una rotazione meno traumatica, se sai esattamente quali chiavi erano attive durante la finestra vulnerabile, usa RevokeKey(Guid keyId, string reason) sulla singola chiave.
Step 3, audit artifact a lunga vita
La patch e la rotazione non invalidano i token che l'app ha emesso durante la finestra. Vanno revocati al livello applicativo:
- API key e refresh token persistiti in DB emessi da endpoint protetti: rigenerare e notificare
- Magic link di password reset o email confirmation non ancora scaduti: invalidare in tabella
- OAuth refresh token verso client esterni: revoca lato provider
- JWT con
jtitrattenuti in lista revoche: check completo
Step 4, consigliati
- Audit plaintext dentro payload protetti. Se hai salvato secret DB, API key esterne dentro
IDataProtector.Protect(), trattali come potenzialmente decifrati e ruotali al servizio di origine. - Review log del web server per volumi anomali su endpoint che accettano payload protetti. Un attacco di tipo padding oracle richiede molti, molti request per byte recuperato. Traffico sostenuto con cookie o query parameter che variano verso un singolo endpoint autenticato durante la finestra vulnerabile è un indicatore forte. Query Kusto/Splunk esempio:
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
Note dal campo SPECTROSEC
In un assessment di marzo a una piattaforma SaaS italiana B2B abbiamo recuperato, per motivi del tutto diversi, una porzione della configurazione DataProtection: il key ring era persistito su Azure Blob con SAS che non scadeva, e le chiavi erano materializzate in memoria del container. Era un finding di severity media, spostavamo il container fuori perimetro e le chiavi arrivavano al nostro laptop.
Su quella stessa piattaforma, se avessero usato Microsoft.AspNetCore.DataProtection 10.0.6 (oggi, due settimane fa), il vettore CVE-2026-40372 avrebbe reso inutile la necessità di esfiltrare il key ring: qualunque utente esterno, partendo da un UserId ricavato via /api/users/me di un account trial, avrebbe forgiato un cookie admin valido. Zero interazione con il DB, zero anomalie nel monitoring, un singolo cookie ben costruito.
È la ragione per cui, anche quando la patch arriva entro 24 ore dall'advisory, il lavoro non si ferma al dotnet restore. L'audit dei token emessi durante la finestra e la rotazione del key ring sono parte integrante della remediation, non un extra opzionale per i paranoici.
Se gestisci un backend ASP.NET Core 10 in produzione e non hai ancora iniziato la rotazione, fallo prima di chiudere questa tab. Se vuoi una review indipendente del processo di rotazione o un re-test a patch applicata, scrivici.
Team SPECTROSEC | pentest professionali, scrivimi a info@spectrosec.com https://spectrosec.com