Vulnerabilidades críticas en PHP y cómo solucionarlas
Esta guía repasa las vulnerabilidades más comunes en aplicaciones PHP, ejemplos de código vulnerables y soluciones prácticas y reproducibles. Está pensada para desarrolladores que quieren asegurar sus aplicaciones sin vaporware: código real, razones y buenas prácticas inmediatas.
1) Inyección SQL
Qué ocurre: concatenar entrada del usuario en consultas SQL permite que un atacante manipule la consulta.
<?php
// VULNERABLE
$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '" . $username . "'";
$rows = $db->query($sql);
?>
Solución: usar sentencias preparadas (PDO) y validación mínima.
<?php
// SEGURO (PDO)
$stmt = $pdo->prepare('SELECT id, username FROM users WHERE username = :u');
$stmt->execute([':u' => $_POST['username']]);
$user = $stmt->fetch();
?>
2) Cross-Site Scripting (XSS)
Qué ocurre: salida no escapada permite inyección de JavaScript en el navegador de la víctima.
<?php
// VULNERABLE
echo 'Comentario: ' . $comment_from_db;
?>
Solución: escapar la salida con htmlspecialchars usando la codificación correcta y ENT_QUOTES.
<?php
echo 'Comentario: ' . htmlspecialchars($comment_from_db, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
?>
Adicional: para HTML rico, usa una whitelist (ej. HTML Purifier) en lugar de strip_tags.
3) CSRF (Cross-Site Request Forgery)
Qué ocurre: acciones sensibles (cambio de contraseña, transferencia) se ejecutan sin consentimiento por petición forjada.
<?php
// Añadir token al formulario
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
<form method='post' action='/save'>
<input type='hidden' name='csrf_token' value='<?= $_SESSION['csrf_token'] ?>' />
...
</form>
<?php
// Validar al recibir
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('CSRF token invalido');
}
?>
4) Deserialización insegura (unserialize)
Qué ocurre: unserialize puede reconstruir objetos y ejecutar métodos mágicos que llevan a RCE o escalado.
<?php
// VULNERABLE
$data = $_POST['payload'];
$object = unserialize($data); // peligroso
?>
Solución: usa formatos seguros como JSON. Si debes usar unserialize, limita clases permitidas (PHP 7+).
<?php
// SEGURO
$decoded = json_decode($raw, true);
// Si usas unserialize por compatibilidad
$object = unserialize($data, ['allowed_classes' => false]);
?>
5) Subida de archivos insegura
Qué ocurre: subir archivos sin validación puede permitir ejecución de código, sobrescritura o exfiltración.
<?php
// RECOMENDACIONES
// 1) Validar MIME con finfo
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['file']['tmp_name']);
$allowed = ['image/png', 'image/jpeg'];
if (!in_array($mime, $allowed, true)) {
throw new RuntimeException('Tipo no permitido');
}
// 2) Generar nombre aleatorio y guardar fuera del webroot
$target = '/var/www/uploads/' . bin2hex(random_bytes(16));
move_uploaded_file($_FILES['file']['tmp_name'], $target);
// 3) Establecer permisos restrictivos
chmod($target, 0640);
?>
6) Gestión de sesiones y fijación
Qué ocurre: sesiones robadas o fijadas por un atacante permiten suplantación.
<?php
// Mejores prácticas al iniciar sesión
session_start();
// Regenerar id tras login
session_regenerate_id(true);
// Configurar cookies seguras en bootstrap de la app
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1'); // HTTPS
ini_set('session.use_strict_mode', '1');
?>
7) Command injection
Qué ocurre: concatenar entrada en llamadas al sistema (exec, shell_exec, system) permite ejecución de comandos arbitrarios.
<?php
// VULNERABLE
$user = $_GET['user'];
$output = shell_exec('ls ' . $user);
// SEGURO: evitar la shell, usar escapeshellarg o mejor aún APIs nativas
$escaped = escapeshellarg($user);
$output = shell_exec('ls ' . $escaped);
?>
Mejor: evitar shell a favor de funciones nativas de PHP o bibliotecas.
8) Directory traversal
<?php
// VULNERABLE
$file = $_GET['file'];
include 'pages/' . $file . '.php';
// SEGURO: whitelist y realpath
$allowed = ['home', 'about', 'contact'];
if (!in_array($file, $allowed, true)) {
http_response_code(404);
exit;
}
include 'pages/' . $file . '.php';
// O validar ruta
$base = realpath(__DIR__ . '/pages');
$path = realpath($base . '/' . $file . '.php');
if ($path === false || strpos($path, $base) !== 0) {
http_response_code(404);
exit;
}
include $path;
?>
9) Criptografía y almacenamiento de contraseñas
Qué ocurre: usar MD5/SHA1 o cifrados mal configurados hace que contraseñas sean fáciles de romper.
<?php
// INCORRECTO: md5
$hash = md5($password);
// CORRECTO: password_hash / password_verify
$hash = password_hash($password, PASSWORD_DEFAULT);
if (!password_verify($attempt, $hash)) {
// falla
}
// Para cifrado de datos sensibles, usa libsodium (ext-sodium)
$key = sodium_crypto_secretbox_keygen();
$ciphertext = sodium_crypto_secretbox($plaintext, $nonce, $key);
?>
10) Filtrado y validación insuficiente
No confíes en el cliente ni en filtros superficiales. Valida tipo, longitud, formato y rango. Aplica whitelist más que blacklist.
Herramientas recomendadas y prácticas
- Usar PDO y sentencias preparadas.
- Escapar siempre salida con htmlspecialchars o plantillas seguras (Twig, Blade).
- password_hash y libsodium para contraseñas y cifrado.
- Content Security Policy (CSP) bien definida para mitigar XSS.
- HTTPS estricto, HSTS y cookies Secure/HttpOnly/SameSite.
- Regenerar session id al login y habilitar session.use_strict_mode.
- Revisión de dependencias: composer audit, getcomposer.org/alerts o herramientas de SCA.
- Escaneo estático: PHPStan, Psalm con reglas de seguridad; escaneo dinámico: OWASP ZAP, Burp.
Checklist rápida para deploy seguro
- No mostrar errores en producción (display_errors=0), loggear en fichero.
- Desactivar funciones peligrosas si no se usan (exec, system, passthru).
- Limitar permisos de archivos y directorios (principio de menor privilegio).
- Almacenar secretos fuera del repo; usar vaults o variables de entorno.
- Escanear dependencias y aplicar actualizaciones regularmente.
Para no quedarte en lo teórico: integra en tu CI un pipeline que incluya PHPStan/Psalm, composer audit y un escaneo dinámico nocturno con OWASP ZAP. Esto encuentra regresiones antes de salir a producción.
Consejo avanzado: combina análisis estático con fuzzing en endpoints críticos y pruebas de inyección automatizadas. No confíes solo en filtros del lado cliente ni en validaciones ad-hoc — la seguridad debe ser parte del ciclo de desarrollo.
Si quieres, puedo generar plantillas de middleware para CSRF, validadores de entrada, configuración segura de PHP-FPM/Nginx o un workflow de GitHub Actions que incluya PHPStan + Trivy para escaneo de dependencias; dime cuál prefieres y lo preparo.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación