Clickjacking — também chamado de UI redressing — é um ataque em que a vítima acha que está clicando em uma coisa, mas está clicando em outra. O atacante carrega a aplicação legítima dentro de um iframe invisível, posiciona esse iframe exatamente sobre um elemento atraente da sua própria página (um botão “Ganhe um prêmio”, por exemplo) e captura o clique. Para o navegador, o clique é totalmente legítimo: ele acontece dentro da sessão autenticada da vítima, com os cookies dela, na origem real da aplicação.
O resultado é que o usuário executa uma ação sensível — trocar e-mail de recuperação, autorizar um pagamento, dar permissão a um app OAuth, excluir a conta — sem nunca ter a intenção de fazê-lo.
Como funciona, na prática
O ataque depende de três ingredientes:
- A aplicação-alvo permite ser carregada em um iframe (não envia cabeçalhos que proíbam isso).
- A vítima tem uma sessão ativa na aplicação-alvo.
- O atacante consegue atrair a vítima para uma página que ele controla.
A página maliciosa monta o iframe da vítima por cima da sua isca e o torna transparente:
<style>
/* O iframe da vítima fica por cima, invisível, captando o clique real */
iframe {
position: absolute;
top: 0;
left: 0;
width: 800px;
height: 600px;
opacity: 0.0; /* totalmente transparente */
z-index: 2;
}
/* A isca fica embaixo, visível, alinhada ao botão real do alvo */
#lure {
position: absolute;
top: 410px; /* posição do botão "Confirmar" dentro do iframe */
left: 300px;
z-index: 1;
}
</style>
<button id="lure">🎁 Clique para resgatar seu prêmio</button>
<iframe src="https://app.exemplo.com/configuracoes/excluir-conta"></iframe>
O usuário vê o botão de prêmio, clica nele, mas o clique atravessa para o botão “Confirmar exclusão” do iframe transparente. Durante o desenvolvimento do ataque, o invasor inverte a lógica (opacity: 0.5) para alinhar a isca com precisão de pixel e só depois zera a opacidade.
Variações que vão além do clique simples
- Likejacking: sequestro de cliques em botões de “curtir”/“seguir” de redes sociais para inflar engajamento.
- Cursorjacking: o atacante esconde o cursor real e desenha um cursor falso deslocado, fazendo a vítima clicar em um ponto diferente do que ela enxerga.
- Drag-and-drop / fill jacking: combinando iframes e eventos de arrastar, é possível induzir a vítima a preencher e arrastar dados sensíveis para campos controlados pelo atacante.
- Double clickjacking: técnica mais recente que abusa da janela de tempo entre dois cliques para burlar proteções que dependem de interação do usuário (como telas de consentimento OAuth).
Por que “frame busting” em JavaScript não basta
Durante anos, a defesa padrão foi o frame busting: um script que detecta estar dentro de um frame e quebra para fora.
// Frame busting clássico — frágil, NÃO confie nisso como única defesa
if (top !== self) {
top.location = self.location;
}
Esse padrão falha de várias formas:
- O atributo
sandboxdo iframe pode bloquear a navegação do top (sandbox="allow-forms allow-scripts"semallow-top-navigation), neutralizando otop.location. - Navegadores antigos e handlers de
onbeforeunloadpodem cancelar o redirecionamento. - Há race conditions explorables entre o carregamento do script e o clique.
Frame busting é, na melhor das hipóteses, uma camada extra — nunca a defesa principal.
A defesa correta: cabeçalhos de resposta
A proteção sólida contra clickjacking é dizer ao navegador, no servidor, quem pode (ou não) embutir suas páginas.
1. CSP frame-ancestors (recomendado, moderno)
A diretiva frame-ancestors da Content Security Policy é o padrão atual e o mais flexível. Para proibir totalmente o embutimento:
Content-Security-Policy: frame-ancestors 'none';
Para permitir apenas a própria origem (e nada de terceiros):
Content-Security-Policy: frame-ancestors 'self';
Para permitir um parceiro específico:
Content-Security-Policy: frame-ancestors 'self' https://parceiro-confiavel.com;
frame-ancestors suporta múltiplas origens, curinga de esquema e porta, e é respeitado por todos os navegadores modernos. É a única opção que cobre cenários de allowlist com mais de um domínio. Para conferir como o seu site responde hoje, cole os headers no Analisador de Security Headers e veja a nota e o fix por stack.
2. X-Frame-Options (legado, ainda útil)
Cabeçalho mais antigo, com apenas dois valores práticos:
X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
O valor
ALLOW-FROM urifoi descontinuado e é ignorado pela maioria dos navegadores. Se você precisa de allowlist, useframe-ancestors.
Mantenha X-Frame-Options por compatibilidade com user agents antigos, mas trate frame-ancestors como a fonte da verdade. Quando os dois discordam, o CSP prevalece nos navegadores modernos.
3. Cookies SameSite em ações sensíveis
Mesmo com o embutimento bloqueado, defina cookies de sessão como SameSite=Lax (ou Strict para áreas críticas). Isso garante que, em um contexto de terceiros, o cookie não acompanhe requisições que mudam estado — uma camada que reforça também a proteção contra CSRF.
Set-Cookie: session=...; HttpOnly; Secure; SameSite=Lax
4. Confirmação explícita para ações irreversíveis
Para operações de altíssimo impacto (excluir conta, transferir valores), exija uma confirmação que não possa ser “clicada por engano”: reautenticação, digitação de uma palavra, ou um desafio que o overlay invisível não consiga reproduzir.
Como testamos clickjacking em um pentest
No nosso processo de teste, a verificação segue uma linha objetiva:
- Mapear endpoints que mudam estado com um único clique (formulários POST, toggles, fluxos de consentimento).
- Inspecionar os cabeçalhos de resposta procurando
Content-Security-Policy: frame-ancestorse/ouX-Frame-Options. A ausência em qualquer rota sensível já é um achado. - Construir uma PoC real: embutir a página em um iframe controlado e confirmar que ela renderiza e é operável.
- Avaliar o impacto combinando com a função sensível — clickjacking que só “curte” é baixo risco; clickjacking sobre “autorizar app OAuth” é alto.
A falha mais comum que encontramos não é a ausência total de proteção, e sim a proteção inconsistente: a home e o login enviam os cabeçalhos, mas o painel administrativo ou um endpoint de API embutível, não.
Checklist de mitigação
-
Content-Security-Policy: frame-ancestors 'none'(ou allowlist explícita) em todas as respostas, inclusive APIs e páginas autenticadas. -
X-Frame-Options: DENY/SAMEORIGINcomo reforço para navegadores legados. - Cookies de sessão com
HttpOnly; Secure; SameSite=Lax(ouStrict). - Reautenticação/confirmação forte em ações irreversíveis.
- Cabeçalhos aplicados de forma centralizada (middleware/edge), não rota a rota.
- Frame busting apenas como camada extra, nunca como única defesa.
Clickjacking é barato de explorar e barato de corrigir — o que o torna um achado quase imperdoável quando aparece em um relatório. Dois cabeçalhos de resposta, aplicados de forma consistente, eliminam a classe inteira do problema.