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
= escape($user['name']) ?>
// 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.');
}
});