gpp_maybe 16. Proteção contra Ataques - XSS, CSRF, SQL Injection

Segurança não é opcional. Os ataques mais comuns em aplicações web são: XSS (Cross-Site Scripting), CSRF (Cross-Site Request Forgery), SQL Injection e brute force. Nesta seção, você vai aprender a se defender contra cada um deles com técnicas comprovadas.

error
OWASP Top 10: Estes ataques estão entre os mais perigosos segundo a OWASP. Uma única falha pode comprometer toda a aplicação e dados dos usuários.

code_off 1. Proteção contra XSS (Cross-Site Scripting)

// PHP - Sanitização de Output
// React - Proteção Automática // React já escapa automaticamente: const UserProfile = ({ name }) => { return
{name}
; // ✅ Seguro }; // PERIGO: dangerouslySetInnerHTML const DangerousComponent = ({ html }) => { // ❌ NUNCA faça isso com dados de usuário não sanitizados return
; }; // Sanitize com DOMPurify: import DOMPurify from 'dompurify'; const SafeHtml = ({ html }) => { const clean = DOMPurify.sanitize(html); return
; };

verified_user 2. Proteção contra CSRF

// PHP - CSRF Token

// Validar no POST: if ($_SERVER['REQUEST_METHOD'] === 'POST') { $token = $_POST['csrf_token'] ?? ''; if (!validateCsrfToken($token)) { http_response_code(403); die('CSRF token inválido'); } // Processar requisição... } // React + Axios - CSRF com Cookies // Backend PHP configura cookie CSRF: setcookie('XSRF-TOKEN', generateCsrfToken(), [ 'httponly' => false, // JS precisa ler 'samesite' => 'Strict', 'secure' => true ]); // Axios envia automaticamente: axios.defaults.xsrfCookieName = 'XSRF-TOKEN'; axios.defaults.xsrfHeaderName = 'X-XSRF-TOKEN'; // OU manual: const getCsrfToken = () => { return document.cookie .split('; ') .find(row => row.startsWith('XSRF-TOKEN=')) ?.split('=')[1]; }; axios.post('/api/action', data, { headers: { 'X-CSRF-Token': getCsrfToken() } });

storage 3. Proteção contra SQL Injection

// ❌ VULNERÁVEL (NUNCA FAÇA ISSO)
$email = $_POST['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";
$result = mysqli_query($conn, $sql);

// Atacante envia: ' OR '1'='1
// Query resultante: SELECT * FROM users WHERE email = '' OR '1'='1'
// Retorna TODOS os usuários!


// ✅ SEGURO - Prepared Statements (PDO)
$email = $_POST['email'];

$stmt = $db->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);

// OU com named parameters:
$stmt = $db->prepare("SELECT * FROM users WHERE email = :email AND status = :status");
$stmt->execute([
    'email' => $email,
    'status' => 'active'
]);


// Query Builder (ainda mais seguro):
class QueryBuilder {
    private $pdo;
    
    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }
    
    public function select($table, $conditions = []) {
        $sql = "SELECT * FROM {$table}";
        
        if (!empty($conditions)) {
            $where = [];
            $params = [];
            
            foreach ($conditions as $key => $value) {
                $where[] = "{$key} = ?";
                $params[] = $value;
            }
            
            $sql .= " WHERE " . implode(' AND ', $where);
        }
        
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        
        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }
}

// Uso:
$qb = new QueryBuilder($db);
$users = $qb->select('users', [
    'email' => $email,
    'status' => 'active'
]);

speed 4. Rate Limiting (Proteção contra Brute Force)

// PHP - Rate Limiter Simples
class RateLimiter {
    private $maxAttempts = 5;
    private $decayMinutes = 15;
    private $storageDir;
    
    public function __construct() {
        $this->storageDir = sys_get_temp_dir() . '/rate_limits';
        if (!is_dir($this->storageDir)) {
            mkdir($this->storageDir, 0755, true);
        }
    }
    
    public function tooManyAttempts($key): bool {
        $file = $this->getFile($key);
        
        if (!file_exists($file)) {
            return false;
        }
        
        $data = json_decode(file_get_contents($file), true);
        
        // Expirou? Limpar
        if ($data['expires_at'] < time()) {
            unlink($file);
            return false;
        }
        
        return $data['attempts'] >= $this->maxAttempts;
    }
    
    public function hit($key): void {
        $file = $this->getFile($key);
        
        if (!file_exists($file)) {
            $data = [
                'attempts' => 1,
                'expires_at' => time() + ($this->decayMinutes * 60)
            ];
        } else {
            $data = json_decode(file_get_contents($file), true);
            
            if ($data['expires_at'] < time()) {
                $data = [
                    'attempts' => 1,
                    'expires_at' => time() + ($this->decayMinutes * 60)
                ];
            } else {
                $data['attempts']++;
            }
        }
        
        file_put_contents($file, json_encode($data));
    }
    
    public function clear($key): void {
        $file = $this->getFile($key);
        if (file_exists($file)) {
            unlink($file);
        }
    }
    
    private function getFile($key): string {
        return $this->storageDir . '/' . md5($key) . '.json';
    }
}

// Uso em login:
$limiter = new RateLimiter();
$key = 'login:' . $_POST['email'];

if ($limiter->tooManyAttempts($key)) {
    http_response_code(429);
    echo json_encode([
        'error' => 'Muitas tentativas. Tente novamente em 15 minutos.'
    ]);
    exit;
}

// Tentar login
$user = attemptLogin($_POST['email'], $_POST['password']);

if (!$user) {
    // Login falhou, incrementar contador
    $limiter->hit($key);
    
    http_response_code(401);
    echo json_encode(['error' => 'Credenciais inválidas']);
    exit;
}

// Login OK, limpar contador
$limiter->clear($key);


// Frontend - Tratamento de Rate Limit
axios.post('/api/auth/login', credentials)
    .catch(error => {
        if (error.response?.status === 429) {
            alert('Muitas tentativas. Aguarde 15 minutos.');
        }
    });

lock 5. Headers de Segurança

// PHP - Configurar headers de segurança
// .htaccess (Apache)

    # Prevenir clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"
    
    # Prevenir MIME sniffing
    Header always set X-Content-Type-Options "nosniff"
    
    # XSS Protection
    Header always set X-XSS-Protection "1; mode=block"
    
    # Content Security Policy
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com;"
    
    # Strict Transport Security (HTTPS)
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    
    # Referrer Policy
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    
    # Permissions Policy
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"


// OU em PHP:
header("X-Frame-Options: SAMEORIGIN");
header("X-Content-Type-Options: nosniff");
header("X-XSS-Protection: 1; mode=block");
header("Content-Security-Policy: default-src 'self'");
header("Strict-Transport-Security: max-age=31536000; includeSubDomains");

check_circle Checklist de Segurança

✅ Backend

  • Sempre use prepared statements
  • Escape todo output HTML
  • Valide CSRF tokens
  • Implemente rate limiting
  • Configure headers de segurança
  • Use HTTPS em produção
  • Mantenha PHP atualizado

✅ Frontend

  • Nunca confie em dados do usuário
  • Use DOMPurify para HTML não sanitizado
  • Implemente CSRF tokens
  • Valide inputs no frontend (UX)
  • Use Content Security Policy
  • Mantenha dependências atualizadas
rocket_launch
Próximo Passo: Segurança implementada! Agora vamos adicionar logs e auditoria para rastrear todas as ações críticas do sistema.