Vulnerabilidades críticas en PHP y cómo solucionarlas
Revisa las 12 vulnerabilidades más comunes en aplicaciones PHP, con ejemplos vulnerables y contramedidas prácticas. Ve al grano: código, configuración y patrones seguros que puedes aplicar hoy.
1) Inyección SQL
Problema: concatenar entrada de usuario en consultas SQL permite inyección.
<?php
// Vulnerable
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$result = $pdo->query($sql);
?>
Solución: usar consultas preparadas (PDO) y never trust input.
<?php
// Seguro
$id = $_GET['id'];
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = :id');
$stmt->execute([':id' => $id]);
$user = $stmt->fetch();
?>
2) Cross-Site Scripting (XSS)
Problema: salida sin escapar permite que atacantes inyecten HTML/JS.
<?php
// Vulnerable
echo "<div>Mensaje: $user_input</div>";
?>
Solución: escapar toda salida según el contexto. Para HTML, usar htmlspecialchars con ENT_QUOTES y UTF-8.
<?php
echo '<div>Mensaje: '.htmlspecialchars($user_input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8').'</div>';
?>
Complementa con políticas CSP y evita inline scripts/styles.
3) CSRF (Cross-Site Request Forgery)
Problema: peticiones forjadas por otra página pueden ejecutar acciones sensibles.
Implementa tokens CSRF: un token almacenado en sesión y comprobado en formularios.
<?php
// Al mostrar formulario
session_start();
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf'];
?>
<form method="post" action="/transfer.php">
<input type="hidden" name="csrf_token" value="<?php echo $token; ?>"/>
<!-- campos -->
</form>
// Al procesar
<?php
session_start();
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('Invalid CSRF token');
}
?>
4) Subida de ficheros insegura
Problema: permitir subir ficheros sin validación puede llevar a ejecución remota, XSS, etc.
Buenas prácticas:
- Validar MIME con finfo_file y extensión esperada.
- Renombrar archivos con nombres únicos (random + extensión segura).
- Almacenar fuera del webroot y servir con un script que valide permissos.
- Establecer permisos restrictivos (0640) y desactivar ejecución en el directorio.
<?php
// Ejemplo de validación mínima
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($_FILES['avatar']['tmp_name']);
$allowed = ['image/png' => 'png', 'image/jpeg' => 'jpg'];
if (!isset($allowed[$mime])) {
throw new RuntimeException('Tipo de archivo no permitido');
}
$ext = $allowed[$mime];
$filename = bin2hex(random_bytes(16)).".".$ext;
$target = __DIR__ . '/../uploads/' . $filename; // fuera de webroot
move_uploaded_file($_FILES['avatar']['tmp_name'], $target);
?>
5) Unserialize / Deserialización insegura
Problema: unserialize() en datos no confiables puede instanciar objetos y ejecutar código mágico (__wakeup, __destruct), llevando a RCE o control de flujo.
Solución: evita unserialize en datos externos. Usa json_decode con validación. Si debes usar unserialize, limita clases permitidas:
<?php
// Más seguro (PHP 7+)
$data = $_POST['payload'] ?? '';
$val = @unserialize($data, ['allowed_classes' => false]);
if ($val === false && $data !== 'b:0;') {
throw new RuntimeException('Datos no válidos');
}
?>
6) Sesiones y fijación de sesión
Mejora la seguridad de las cookies de sesión:
<?php
// Configuración recomendada
session_start();
session_regenerate_id(true); // en login
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // requiere HTTPS
ini_set('session.cookie_samesite', 'Lax');
?>
Evita poner identificadores sensibles en URLs.
7) Ejecución de comandos (Command Injection)
Problema: pasar input directo a funciones como exec() o system().
Solución: evitar llamadas al shell. Si es imprescindible, usa escapeshellarg y valida estrictamente.
<?php
// Evitar
$domain = $_GET['domain'];
exec("ping -c 1 $domain", $out);
// Mejor: validar con whitelist o usar librería/network API
if (!preg_match('/^[a-z0-9.-]+$/i', $domain)) {
throw new RuntimeException('Dominio no válido');
}
$escaped = escapeshellarg($domain);
exec("ping -c 1 $escaped", $out);
?>
8) Configuración de PHP insegura
Asegúrate de:
- Desactivar display_errors en producción (display_errors = Off).
- Mantener log_errors = On y revisar logs.
- Desactivar allow_url_include y, si es posible, allow_url_fopen según necesidad.
- Actualizar PHP y extensiones regularmente.
9) Gestión de contraseñas débil
Problema: almacenar contraseñas con md5/sha1 o sin sal.
Solución: usar password_hash + password_verify:
<?php
$hash = password_hash($password, PASSWORD_DEFAULT);
if (password_verify($password_attempt, $hash)) {
// ok
}
?>
10) Headers HTTP de seguridad
Añade estos headers a las respuestas:
- Content-Security-Policy
- X-Frame-Options: DENY
- X-Content-Type-Options: nosniff
- Referrer-Policy: no-referrer-when-downgrade (o stricter)
<?php
header("X-Frame-Options: DENY");
header("X-Content-Type-Options: nosniff");
header("Referrer-Policy: no-referrer");
header("Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';");
?>
11) Dependencias vulnerables
Usa composer audit y verifica advisories regularmente. Integra comprobaciones automáticas en CI:
- composer audit
- Dependabot/renovate para PRs automáticos
- Herramientas SCA/SAST: Psalm, PHPStan con plugins de seguridad
12) Control de acceso e IDOR
Problema: acceder por ID sin verificar permisos (IDOR).
Siempre verifica que el usuario tenga permiso para acceder al recurso solicitado:
<?php
$document = $repo->find($id);
if ($document->owner_id !== $currentUser->id) {
http_response_code(403);
exit('Forbidden');
}
?>
Checklist rápido
- Usar consultas preparadas (PDO/MySQLi).
- Escapar salida con htmlspecialchars o templates seguros (Twig, Plates).
- Tokens CSRF en formularios y CABECERAS mutating requests.
- No usar unserialize en datos no confiables.
- Validar y almacenar ficheros fuera del webroot.
- Configurar cookies de sesión (HttpOnly, Secure, SameSite).
- Proteger headers y establecer CSP.
- Auditar dependencias y usar SAST/DAST.
Herramientas recomendadas
- Composer audit
- PHPStan / Psalm (análisis estático)
- OWASP ZAP o Burp Suite (DAST)
- WAP/ModSecurity para protección adicional
Aplicar estas prácticas reduce drásticamente el riesgo. Empieza por las entradas/salidas: valida todo lo que entra, escapa todo lo que sale, y minimiza la superficie de ataque (menor código con privilegios, ficheros fuera del webroot, menor exposición de configuración).
Consejo avanzado: integra comprobaciones de seguridad en tu pipeline CI (composer audit, PHPStan+rules, tests automatizados) y añade pruebas de fuzzing/DAST periódicas. Advertencia: nunca confíes en la seguridad solo por la ausencia de errores en dev; exige auditorías y pruebas en un entorno similar a producción.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación