Clickjacking: prevenção com X-Frame-Options e CSP

1. O que é Clickjacking e por que devs devem se importar?

Clickjacking, também conhecido como "UI redressing", é uma técnica de ataque onde um invasor sobrepõe uma página web legítima dentro de um iframe transparente, enganando o usuário a clicar em elementos invisíveis. O usuário acredita estar interagindo com a página visível, mas na verdade está clicando em botões, links ou formulários da página alvo oculta.

Um exemplo clássico é o "likejacking": o invasor cria uma página atraente (como um quiz viral) e, por trás dela, posiciona o botão "Curtir" do Facebook. Quando o usuário clica em qualquer lugar da página visível, está na verdade curtindo uma página ou publicando conteúdo sem consentimento.

Para desenvolvedores, as consequências são graves:
- Responsabilidade legal por ações não autorizadas realizadas em nome dos usuários
- Perda de confiança quando dados sensíveis são comprometidos
- Violação de regulamentações como LGPD e GDPR
- Danos à reputação da aplicação e da empresa

2. Anatomia de um ataque Clickjacking

O ataque utiliza três elementos principais: um iframe apontando para a página alvo, posicionamento absoluto para sobreposição, e transparência total. Eis um exemplo simplificado:

<!DOCTYPE html>
<html>
<head>
  <title>Quiz Interativo</title>
  <style>
    iframe {
      position: absolute;
      top: 0;
      left: 0;
      width: 500px;
      height: 500px;
      opacity: 0;
      z-index: 10;
    }
    .botao-falso {
      position: absolute;
      top: 200px;
      left: 150px;
      width: 200px;
      height: 50px;
      background: #007bff;
      color: white;
      text-align: center;
      line-height: 50px;
      z-index: 1;
    }
  </style>
</head>
<body>
  <div class="botao-falso">Clique aqui para ganhar!</div>
  <iframe src="https://banco-exemplo.com/transferir"></iframe>
</body>
</html>

O invasor alinha o botão falso exatamente sobre o botão "Confirmar Transferência" da página do banco. O usuário vê apenas o botão azul convidativo, mas seu clique é capturado pelo iframe transparente, executando a ação maliciosa.

3. Primeira linha de defesa: X-Frame-Options

O cabeçalho HTTP X-Frame-Options foi a primeira defesa padronizada contra clickjacking. Ele informa ao navegador se a página pode ser exibida dentro de um iframe.

Valores disponíveis:
- DENY: a página nunca pode ser exibida em iframe
- SAMEORIGIN: permitido apenas na mesma origem
- ALLOW-FROM uri: permitido apenas para uma URI específica (obsoleto e não suportado em navegadores modernos)

Implementação em servidores comuns:

# Apache - arquivo .htaccess ou httpd.conf
Header always set X-Frame-Options "SAMEORIGIN"

# Nginx - arquivo de configuração
add_header X-Frame-Options "SAMEORIGIN" always;

# Express.js (Node.js)
app.use((req, res, next) => {
  res.setHeader('X-Frame-Options', 'DENY');
  next();
});

# Django - settings.py
MIDDLEWARE = [
  'django.middleware.clickjacking.XFrameOptionsMiddleware',
  # ...
]
X_FRAME_OPTIONS = 'DENY'

Limitações importantes:
- ALLOW-FROM é obsoleto e ignorado pelo Chrome e Firefox
- Não permite listar múltiplas origens confiáveis
- Suporte descontinuado em navegadores modernos que priorizam CSP

4. Defesa moderna e flexível: Content-Security-Policy (CSP)

A diretiva frame-ancestors do CSP substitui e expande as capacidades do X-Frame-Options. Ela define quais origens podem embutir a página em iframes, frames ou objetos.

Sintaxe básica:

# Bloquear todos os iframes
Content-Security-Policy: frame-ancestors 'none'

# Permitir apenas mesma origem
Content-Security-Policy: frame-ancestors 'self'

# Permitir origens específicas
Content-Security-Policy: frame-ancestors https://meudominio.com https://parceiro.com

# Permitir qualquer origem (não recomendado)
Content-Security-Policy: frame-ancestors *

Comparação com X-Frame-Options:

Característica X-Frame-Options CSP frame-ancestors
Múltiplas origens Não Sim
Suporte moderno Parcial Completo
Granularidade Baixa Alta
Relatórios de violação Não Sim (com report-uri)

Exemplo de cabeçalho completo para um cenário típico:

Content-Security-Policy: 
  default-src 'self';
  frame-ancestors 'self' https://api.parceiro.com;
  report-uri /csp-violation;

5. Implementação prática no código do desenvolvedor

Node.js com Express usando Helmet.js

const helmet = require('helmet');
const express = require('express');
const app = express();

// Helmet configura X-Frame-Options e CSP automaticamente
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      frameAncestors: ["'self'", "https://trusted-app.com"],
    },
  },
}));

Python com Flask usando django-csp

# settings.py
INSTALLED_APPS = [
    'csp',
    # ...
]

MIDDLEWARE = [
    'csp.middleware.CSPMiddleware',
    # ...
]

CSP_DEFAULT_SRC = ("'self'",)
CSP_FRAME_ANCESTORS = ("'self'", "https://widgets.externos.com")

Java com Spring Security

// SecurityConfig.java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .headers()
                .frameOptions().sameOrigin()
                .and()
            .headers()
                .contentSecurityPolicy("frame-ancestors 'self' https://app.externo.com");
    }
}

Testando a proteção

# Teste com curl
curl -I https://meusite.com/pagina-segura

# Resposta esperada:
# HTTP/2 200
# content-security-policy: frame-ancestors 'self'
# x-frame-options: SAMEORIGIN

No navegador, abra o DevTools (F12), vá para a aba Network e verifique os cabeçalhos de resposta. Para testar se a proteção funciona, crie uma página HTML local com um iframe apontando para seu site e veja se o carregamento é bloqueado.

6. Casos especiais e armadilhas comuns

Clickjacking em SPAs e iframes legítimos

Single Page Applications frequentemente precisam de iframes para widgets de terceiros (YouTube, Stripe, Google Maps). Nesses casos, configure frame-ancestors com as origens específicas dos provedores, nunca use *.

# Permitindo iframes do YouTube e Stripe
Content-Security-Policy: frame-ancestors 'self' https://www.youtube.com https://js.stripe.com;

Erros frequentes

  1. Usar ALLOW-FROM — obsoleto, não funciona no Chrome
  2. Esquecer páginas de login — são o alvo principal de clickjacking
  3. Conflito com CSP inline — se usa `script-src 'unsafe-inline'', revise a política
  4. Widgets quebrados — testar exaustivamente após aplicar restrições

Solução para widgets de terceiros

# Para formulários Stripe, a Stripe já fornece documentação específica
# Configure frame-ancestors com a URL exata do seu domínio
Content-Security-Policy: frame-ancestors 'self' https://checkout.stripe.com;

7. Estratégia de defesa em camadas

Combinação de X-Frame-Options + CSP

Para máxima compatibilidade com navegadores antigos, use ambos os cabeçalhos. Quando ambos estão presentes, o navegador prioriza o CSP.

# Cabeçalhos duplos (redundância segura)
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self'

Framebusting JavaScript (técnica complementar)

Embora defensas HTTP sejam preferíveis, o framebusting serve como fallback:

<script>
  if (window.top !== window.self) {
    window.top.location = window.self.location;
  }
</script>

Atenção: invasores podem contornar framebusting com sandbox ou onbeforeunload. Use apenas como camada adicional, nunca como defesa principal.

Monitoramento com CSP-Report-Only

Antes de aplicar restrições severas, use o modo de relatório para identificar violações sem bloquear:

Content-Security-Policy-Report-Only: 
  frame-ancestors 'self';
  report-uri /csp-violation-endpoint;

Checklist final para o dev antes do deploy

  • [ ] X-Frame-Options configurado (DENY ou SAMEORIGIN)
  • [ ] CSP frame-ancestors definido com origens específicas
  • [ ] Páginas de login e formulários sensíveis testados
  • [ ] Widgets de terceiros funcionando com as restrições
  • [ ] CSP-Report-Only ativo em staging para detectar problemas
  • [ ] Teste manual com página HTML contendo iframe
  • [ ] Ferramentas de segurança (OWASP ZAP, observatory.mozilla.org) sem alertas críticos

Referências