Cross-Site Scripting (XSS) é uma das vulnerabilidades mais comuns e perigosas em aplicações web. Nesta aula, você aprenderá a identificar e prevenir ataques XSS em suas aplicações PHP, utilizando técnicas de escape adequadas e políticas de segurança de conteúdo. Vamos explorar desde o conceito básico até a implementação prática de defesas eficazes.

O XSS ocorre quando um atacante consegue injetar scripts maliciosos em páginas web visualizadas por outros usuários. Esses scripts podem roubar cookies, sessões, redirecionar usuários ou modificar o conteúdo da página. Para prevenir, é essencial tratar toda saída de dados que possa conter informações fornecidas pelo usuário, além de adotar medidas como CSP.

O que é

XSS (Cross-Site Scripting) é uma vulnerabilidade de segurança que permite a injeção de scripts do lado do cliente em páginas web. O atacante explora a falta de validação ou escape de entradas do usuário, fazendo com que o navegador execute código malicioso no contexto da aplicação. Existem três tipos principais: XSS Refletido, XSS Armazenado e XSS baseado em DOM.

No XSS Refletido, o payload malicioso é refletido imediatamente na resposta, geralmente via parâmetros de URL. No XSS Armazenado, o payload é persistido no servidor (como em um banco de dados) e exibido a todos os usuários que acessarem a página. Já o XSS baseado em DOM ocorre quando o JavaScript manipula o DOM de forma insegura, usando dados não confiáveis. Em PHP, o XSS geralmente acontece quando o desenvolvedor imprime dados sem escapar corretamente.

Exemplo de código vulnerável:

<?php
$nome = $_GET['nome'];
echo "Olá, $nome!";
?>

Se um usuário acessar ?nome=<script>alert('XSS')</script>, o script será executado. Para prevenir, é necessário escapar a saída.

htmlspecialchars

A função htmlspecialchars() é a principal ferramenta em PHP para escapar saídas em HTML. Ela converte caracteres especiais como <, >, ", ' e & em suas entidades HTML, impedindo que sejam interpretados como código. A assinatura é:

htmlspecialchars(string $string, int $flags = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, string $encoding = null, bool $double_encode = true)

O parâmetro $flags controla o comportamento: ENT_QUOTES converte aspas duplas e simples, ENT_NOQUOTES não converte aspas, ENT_SUBSTITUTE substitui caracteres inválidos por U+FFFD em vez de retornar string vazia. É recomendado usar ENT_QUOTES para cobrir todos os contextos de atributos.

Exemplo de uso:

$nome = $_GET['nome'];
echo "Olá, " . htmlspecialchars($nome, ENT_QUOTES, 'UTF-8') . "!";

Com isso, a entrada maliciosa é exibida como texto puro, sem executar. É importante definir o encoding correto (geralmente UTF-8) e usar a função em toda saída que contenha dados não confiáveis.

Alternativas: htmlentities() converte todos os caracteres aplicáveis, mas é mais lento. strip_tags() remove tags, mas não é segura como única defesa. A melhor prática é usar htmlspecialchars() com flags adequadas.

Escape no contexto certo

Escapar dados não é uma solução única; o escape deve ser adequado ao contexto onde o dado será inserido. Por exemplo, em um atributo HTML, é necessário escapar aspas; em um contexto de URL, é necessário codificar a URL; em JavaScript, é necessário escapar strings para evitar injeção de código.

Contextos comuns:

  • HTML body: usar htmlspecialchars() com ENT_QUOTES.
  • Atributos HTML: também usar htmlspecialchars() com ENT_QUOTES.
  • URLs: usar urlencode() ou rawurlencode() para parâmetros, e validar o esquema (http/https) para evitar javascript:.
  • JavaScript: usar json_encode() para inserir dados em contexto JS, ou escapar com addslashes() e codificação Unicode, mas o ideal é evitar interpolar dados diretamente.
  • CSS: escapar usando addcslashes() ou codificação, mas é raro.

Exemplo de URL segura:

$url = "https://exemplo.com/pagina?nome=" . urlencode($nome);
echo "<a href='" . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . "'>Link</a>";

Nunca confie em funções genéricas; entenda onde o dado será exibido e aplique o escape correspondente. Além disso, valide e filtre entradas no servidor, usando filter_var() ou expressões regulares.

CSP (visão geral)

Content Security Policy (CSP) é um cabeçalho HTTP que permite controlar quais recursos o navegador pode carregar, reduzindo o impacto de ataques XSS. Com CSP, você pode restringir a execução de scripts a fontes confiáveis, bloquear inline scripts e até mesmo reportar violações.

Para implementar CSP em PHP, envie o cabeçalho antes de qualquer saída:

header("Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.exemplo.com; style-src 'self' 'unsafe-inline'");

Diretivas comuns: default-src define a política padrão, script-src controla fontes de scripts, style-src para estilos, img-src para imagens, e report-uri para enviar relatórios. Você pode usar 'unsafe-inline' para permitir inline, mas isso reduz a segurança.

É importante testar a política para não quebrar funcionalidades. Uma abordagem gradual é usar Content-Security-Policy-Report-Only para monitorar sem bloquear.

Exemplo de política restritiva:

header("Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; base-uri 'self'; form-action 'self'");

CSP não elimina a necessidade de escapar saídas, mas adiciona uma camada extra de defesa, limitando o que o atacante pode executar mesmo se conseguir injetar algo.

Boas práticas e observações finais

  • Sempre escape saídas com htmlspecialchars() com ENT_QUOTES e UTF-8.
  • Valide e filtre entradas no servidor, não apenas no cliente.
  • Use templates com escape automático, como Twig ou Blade, para reduzir erros.
  • Implemente CSP com uma política restritiva, mas testável.
  • Mantenha o PHP e bibliotecas atualizadas.
  • Considere usar um framework com proteções embutidas.

Referências

Exercícios

  1. Explique o que é XSS e cite os três tipos principais. Dê um exemplo de código PHP vulnerável a XSS refletido.

    ✓ Resposta: XSS é uma vulnerabilidade que permite injeção de scripts maliciosos. Os três tipos são: Refletido, Armazenado e DOM-based. Exemplo vulnerável:
    <?php
    echo $_GET['msg'];
    ?>
  2. Qual a diferença entre htmlspecialchars() e htmlentities()? Em que situação você usaria cada um?

    ✓ Resposta: htmlspecialchars() converte apenas os caracteres especiais principais (<, >, &, ", '), enquanto htmlentities() converte todos os caracteres que têm entidades HTML. Use htmlspecialchars() para a maioria dos casos, pois é mais leve e suficiente. Use htmlentities() se precisar converter caracteres acentuados em entidades para compatibilidade.
  3. Escreva um código PHP que exiba um parâmetro GET 'nome' em um atributo HTML de forma segura, usando htmlspecialchars().

    ✓ Resposta:
    $nome = $_GET['nome'] ?? '';
    echo "<input type='text' value='" . htmlspecialchars($nome, ENT_QUOTES, 'UTF-8') . "'>";
  4. O que é Content Security Policy (CSP) e como ela ajuda a prevenir XSS? Dê um exemplo de cabeçalho CSP em PHP.

    ✓ Resposta: CSP é um cabeçalho HTTP que restringe as fontes de scripts e outros recursos, dificultando a execução de scripts maliciosos. Exemplo:
    header("Content-Security-Policy: default-src 'self'; script-src 'self'");
  5. Considere o código: echo "<a href='" . $_GET['url'] . "'>Link</a>";. Como você o tornaria seguro? Explique as etapas.

    ✓ Resposta: Para tornar seguro, valide a URL para permitir apenas http/https, use filter_var($url, FILTER_VALIDATE_URL) e depois escape com htmlspecialchars(). Exemplo:
    $url = filter_var($_GET['url'] ?? '', FILTER_VALIDATE_URL);
    if ($url !== false) {
        echo "<a href='" . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . "'>Link</a>";
    } else {
        echo "URL inválida";
    }