Vulnerabilidades críticas en PHP y cómo solucionarlas
Un repaso práctico a las vulnerabilidades más comunes en aplicaciones PHP y patrones concretos para mitigarlas. Aquí encontrarás ejemplos de código seguros, configuraciones recomendadas y herramientas para automatizar la detección.
1) Inyección SQL
Problema: concatenar entradas de usuario en consultas SQL permite inyección.
// MAL: vulnerable a SQL injection
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$result = $db->query($sql);
// BIEN: usar PDO con prepared statements
$pdo = new PDO('mysql:host=localhost;dbname=mydb', 'user', 'pass', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute(['id' => $_GET['id']]);
$user = $stmt->fetch();
Por qué: los prepared statements separan estructura de datos, evitando que un input modifique la consulta.
2) Cross-Site Scripting (XSS)
Problema: imprimir datos no sanitizados en HTML permite ejecución de scripts maliciosos.
// MAL
echo "Comentario: " . $comment . "";
// BIEN: escapar según el contexto
echo 'Comentario: ' . htmlspecialchars($comment, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '';
Consejo: usa funciones de escape contextuales. Para atributos HTML usa htmlspecialchars, para URLs usa rawurlencode y para datos JS serialízalos con json_encode dentro de un script seguro.
3) CSRF (Cross-Site Request Forgery)
Protege las acciones que cambian estado con tokens anti-CSRF.
// Generar token al inicio de sesión o al cargar el form
function csrf_token() {
if (!isset($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf'];
}
// En el formulario
echo "";
// Verificar al procesar
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
http_response_code(403);
exit('Invalid CSRF token');
}
Usa hash_equals para evitar timing attacks.
4) Subida de archivos insegura
Problema: aceptar archivos sin validar puede llevar a ejecución remota o traversal.
// Validación mínima segura
$allowed = ['image/jpeg','image/png'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);
if (!in_array($mime, $allowed, true)) {
throw new RuntimeException('Tipo de archivo no permitido');
}
$ext = pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
$filename = bin2hex(random_bytes(16)) . '.' . $ext; // nombre aleatorio
$target = __DIR__ . '/uploads/' . $filename; // carpeta fuera del webroot preferible
move_uploaded_file($_FILES['file']['tmp_name'], $target);
Recomendaciones: almacenar fuera del webroot, establecer permisos estrictos, no confiar en la extension y validar con finfo, limitar tamaño y procesar archivos (p. ej. re-encodificar imágenes) antes de servirlos.
5) Remote Code Execution y eval
Evita eval(), create_function() y cualquier ejecución de código basado en input del usuario. Si necesitas cargar módulos dinámicamente, usa una lista blanca de rutas o nombres:
$allowed = ['reports','invoices'];
$module = $_GET['module'] ?? '';
if (!in_array($module, $allowed, true)) {
http_response_code(400);
exit('Módulo inválido');
}
require __DIR__ . '/modules/' . $module . '.php';
6) Manejo inseguro de sesiones
Configura cookies de sesión y regenera el id al autenticar.
0,
'path' => '/',
'domain' => 'example.com',
'secure' => true, // solo HTTPS
'httponly' => true,
'samesite' => 'Lax',
]);
session_start();
// Al iniciar sesión
session_regenerate_id(true);
Habilita session.use_strict_mode en php.ini para evitar session fixation.
7) Contraseñas y autenticación
Usa password_hash y password_verify. Nunca reinventes esquemas de hashing.
Agrega límites de intentos y bloqueo gradual (rate limiting) y, cuando sea posible, MFA.
8) Deserialización insegura
Evita unserialize() en datos no confiables. Usa json_encode/json_decode o implementa un deserializador seguro con lista blanca de clases.
9) Exposición de errores y configuración
No muestres errores en producción. En php.ini establece:
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
Evita revelar stack traces o rutas internas al usuario.
10) Inclusiones y traversal
Valida rutas y usa realpath para garantizar que un archivo resida en un directorio permitido:
$base = realpath(__DIR__ . '/pages');
$file = realpath($base . '/' . $_GET['page'] . '.php');
if ($file === false || strpos($file, $base) !== 0) {
http_response_code(404);
exit('Not found');
}
require $file;
Encabezados HTTP de seguridad
Herramientas y automatización
- Composer audit: detectar dependencias con vulnerabilidades.
- PHPStan / Psalm: análisis estático para encontrar bugs y problemas de seguridad.
- Semgrep / SonarQube / Snyk: escaneo de reglas de seguridad y secretos.
- Dependabot / Renovate: mantener dependencias actualizadas.
Checklist rápido
- Usar prepared statements en todas las consultas.
- Escapar todas las salidas según contexto.
- Implementar tokens CSRF en formularios y endpoints state-changing.
- Validar y procesar uploads, almacenarlos fuera del webroot.
- Configurar cookies: Secure, HttpOnly, SameSite.
- No usar eval/exec con input; usar whitelists para includes.
- Deshabilitar display_errors en producción y registrar errores.
- Ejecutar análisis estático y auditorías de dependencias en CI.
Avanza: implementar pruebas de seguridad automatizadas en tu pipeline (Semgrep + composer audit + PHPStan), desplegar CSP de forma incremental y activar un WAF para mitigar ataques en tiempo real. Advertencia: no asumas que una sola medida te hace seguro; aplica defensa en profundidad y monitoriza logs y métricas para detectar anomalías.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación