Segurança em PHP: XSS, CSRF, Injeção e Boas Práticas Finais: Do Básico ao Avançado Já leu

XSS (Cross-Site Scripting) XSS ocorre quando um atacante injeta código JavaScript malicioso que é executado no navegador da vítima. Existem três tipos principais: Stored (armazenado no banco), Reflected (refletido na URL) e DOM-based (manipulação direta do DOM). A vulnerabilidade acontece quando você confia em entrada do usuário sem sanitização adequada. A defesa fundamental é escapar a saída para o contexto HTML correto. Use para converter caracteres especiais em entidades HTML, impedindo que o navegador interprete tags perigosas. Se você permite HTML limitado, use uma biblioteca como HTMLPurifier. Veja o exemplo: Use para escapar aspas duplas e simples, essencial em atributos. Em contextos JSON, use que já escapa caracteres perigosos. Headers de segurança como adicionam uma camada extra de proteção, bloqueando execução de scripts inline não autorizados. CSRF (Cross-Site Request Forgery) CSRF força um usuário autenticado a realizar ações indesejadas em outro site. Um atacante cria um formulário malicioso que, ao ser visualizado por um usuário logado, executa requisições sem consentimento.

XSS (Cross-Site Scripting)

XSS ocorre quando um atacante injeta código JavaScript malicioso que é executado no navegador da vítima. Existem três tipos principais: Stored (armazenado no banco), Reflected (refletido na URL) e DOM-based (manipulação direta do DOM). A vulnerabilidade acontece quando você confia em entrada do usuário sem sanitização adequada.

A defesa fundamental é escapar a saída para o contexto HTML correto. Use htmlspecialchars() para converter caracteres especiais em entidades HTML, impedindo que o navegador interprete tags perigosas. Se você permite HTML limitado, use uma biblioteca como HTMLPurifier. Veja o exemplo:

<?php
// ❌ INSEGURO
$nome = $_GET['nome'];
echo "Bem-vindo, " . $nome;

// ✅ SEGURO
$nome = $_GET['nome'];
echo "Bem-vindo, " . htmlspecialchars($nome, ENT_QUOTES, 'UTF-8');

// Para atributos HTML
$url = $_GET['url'];
echo '<a href="' . htmlspecialchars($url, ENT_QUOTES, 'UTF-8') . '">Link</a>';
?>

Use ENT_QUOTES para escapar aspas duplas e simples, essencial em atributos. Em contextos JSON, use json_encode() que já escapa caracteres perigosos. Headers de segurança como Content-Security-Policy adicionam uma camada extra de proteção, bloqueando execução de scripts inline não autorizados.

CSRF (Cross-Site Request Forgery)

CSRF força um usuário autenticado a realizar ações indesejadas em outro site. Um atacante cria um formulário malicioso que, ao ser visualizado por um usuário logado, executa requisições sem consentimento. A defesa padrão é usar tokens únicos por sessão que devem ser validados em operações sensíveis (POST, PUT, DELETE).

Implemente tokens CSRF gerando um valor aleatório na sessão e validando em cada requisição. O token deve ser incluído no formulário e verificado no servidor. Exemplo prático:

<?php
session_start();

// Gerar token (na primeira carga da página)
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Exibir no formulário
?>
<form method="POST" action="processar.php">
    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
    <input type="text" name="email" required>
    <button type="submit">Atualizar Email</button>
</form>

<?php
// Validar no processamento
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (empty($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        die('Token CSRF inválido!');
    }
    // Processar requisição segura
}
?>

Regenere o token após login para evitar fixação. Use SameSite cookies para adicionar proteção: session.cookie_samesite = "Strict" no php.ini. Isso previne envio automático de cookies em requisições cross-site.

Injeção SQL e Prepared Statements

Injeção SQL ocorre quando entrada do usuário é concatenada diretamente em queries. Um atacante pode manipular a lógica SQL para acessar, modificar ou deletar dados. A defesa comprovada é usar prepared statements com placeholders que separam código SQL de dados.

PDO e MySQLi oferecem proteção nativa via prepared statements. O SQL é compilado separadamente dos parâmetros, tornando impossível alterar a estrutura da query:

<?php
// ❌ INSEGURO - Nunca faça isso
$email = $_POST['email'];
$query = "SELECT * FROM users WHERE email = '$email'";
$resultado = $pdo->query($query);

// ✅ SEGURO - PDO com placeholders nomeados
$email = $_POST['email'];
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->execute([':email' => $email]);
$usuario = $stmt->fetch();

// ✅ SEGURO - PDO com placeholders posicionais
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ? AND status = ?");
$stmt->execute([$email, 'ativo']);

// ✅ SEGURO - MySQLi prepared statements
$mysqli = new mysqli("localhost", "user", "password", "database");
$stmt = $mysqli->prepare("SELECT * FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$resultado = $stmt->get_result();
?>

Nunca confie em tipo de dado. Mesmo um número pode ser vulnerável se não for tratado. Use prepared statements para TODAS as queries dinâmicas, sem exceção. Validação de entrada complementa mas não substitui prepared statements — use ambas as técnicas.

Boas Práticas Finais

Além das vulnerabilidades específicas, implemente defesas em profundidade. Use HTTPS obrigatoriamente para todas as requisições, protegendo dados em trânsito. Implemente rate limiting para dificultar ataques de força bruta. Mantenha PHP, dependências e bibliotecas atualizadas — use Composer para gerenciar versões e receber atualizações de segurança.

Adicione headers HTTP essenciais em cada resposta:

<?php
// Prevenir clickjacking
header("X-Frame-Options: SAMEORIGIN");

// Proteger contra MIME sniffing
header("X-Content-Type-Options: nosniff");

// Habilitar HSTS (obriga HTTPS)
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");

// Content Security Policy forte
header("Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'");

// Proteger contra XSS no IE antigo
header("X-XSS-Protection: 1; mode=block");
?>

Implemente logging e monitoramento de atividades suspeitas. Nunca exponha informações sensíveis em mensagens de erro — use logs internos. Valide TODAS as entradas de usuário, escape TODAS as saídas para o contexto correto, e use prepared statements para TODA interação com banco de dados. Estes três pilares eliminam a maioria das vulnerabilidades comuns.

Conclusão

Os três conceitos mais críticos são: 1) Escapar saída com htmlspecialchars() para XSS, garantindo que entrada malicioso se torne inofensivo no navegador; 2) Usar tokens CSRF em formulários e validar em POST/PUT/DELETE, protegendo contra requisições forçadas; 3) Implementar prepared statements em TODAS as queries, tornando injeção SQL tecnicamente impossível. Segurança não é um recurso opcional — é a base para software confiável. Pratique constantemente e mantenha-se atualizado sobre novas vulnerabilidades.

Referências


Artigos relacionados