Vulnerabilidades críticas en PHP y cómo solucionarlas (guía práctica)

php Vulnerabilidades críticas en PHP y cómo solucionarlas (guía práctica)

Vulnerabilidades críticas en PHP y cómo solucionarlas (guía práctica)

PHP domina muchas aplicaciones web; su rendimiento y velocidad de desarrollo explican su uso masivo. Pero también es un objetivo frecuente para atacantes por malas prácticas y configuraciones inseguras. Aquí tienes una guía práctica con ejemplos de código, configuraciones y contramedidas concretas.

Mapa rápido

  • Inyección SQL
  • XSS (Cross-Site Scripting)
  • CSRF (Cross-Site Request Forgery)
  • Subida de archivos insegura
  • Deserialización insegura
  • Gestión de sesiones
  • Funciones peligrosas y configuración de php.ini
  • Buenas prácticas y herramientas

1) Inyección SQL — la base

Problema: concatenar entrada del usuario en queries. Solución: sentencias preparadas (PDO) y validación.

<?php
// BAD: vulnerable
$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
// GOOD: prepared statements
$pdo = new PDO('mysql:host=localhost;dbname=test', '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(PDO::FETCH_ASSOC);
?>

Tips: usar tipos estrictos cuando sea posible, validar y cast al tipo esperado (por ejemplo (int)$_GET['id']). Para consultas complejas usar ORM o query builder que abstraigan parámetros.

2) XSS — salida insegura

Problema: imprimir HTML con contenido controlado por usuario. Solución: escapar salida según contexto y aplicar Content Security Policy.

<?php
// Escapar para HTML
echo htmlspecialchars($user['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');

// Si necesitas insertar en atributo JS o HTML, usar context-appropriate escaping
?>

Cabeceras recomendadas:

header('Content-Security-Policy: default-src \'self\'; script-src \'self\'');
header('X-Content-Type-Options: nosniff');

3) CSRF — prevenir acciones forzadas

Problema: formularios o endpoints que aceptan acciones con privilegios sin verificar la intención del usuario. Solución: tokens CSRF únicos por sesión/acción.

<?php
// Generar token (en inicio de sesión o creación del formulario)
if (empty($_SESSION['csrf_token'])) {
  $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

// Incluir en formulario
?>
<form method='post' action='/transfer'>
  <input type='hidden' name='csrf_token' value='<?= $_SESSION['csrf_token'] ?>'>
</form>

<?php
// Validar token
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
  http_response_code(403);
  exit('Invalid CSRF token');
}
?>

Si usas frameworks (Laravel, Symfony, etc.) habilita su protección CSRF incorporada.

4) Subida de archivos — control estricto

Problema: aceptar archivos sin validar puede llevar a ejecución remota, traversals, o sobrescritura. Solución: whitelist de extensiones, validación MIME con finfo, renombrado aleatorio, almacenamiento fuera del webroot, uso de move_uploaded_file.

<?php
$allowed_ext = ['jpg','png','pdf'];
$upload = $_FILES['file'] ?? null;
if ($upload && $upload['error'] === UPLOAD_ERR_OK) {
  $finfo = new finfo(FILEINFO_MIME_TYPE);
  $mime = $finfo->file($upload['tmp_name']);

  // Validar mime (ejemplo simple)
  $ext = strtolower(pathinfo($upload['name'], PATHINFO_EXTENSION));
  if (!in_array($ext, $allowed_ext, true)) {
    exit('Extensión no permitida');
  }
  // Nombre seguro
  $newName = bin2hex(random_bytes(16)) . '.' . $ext;
  $target = __DIR__ . '/uploads/' . $newName; // idealmente fuera del webroot
  if (!move_uploaded_file($upload['tmp_name'], $target)) {
    exit('Error al mover archivo');
  }
}
?>

Además: bloquear ejecución en carpeta de uploads mediante .htaccess o configuración del servidor.

5) Deserialización insegura

Problema: unserialize() en datos controlados por usuario permite invocar magia de objetos con side effects. Solución: evitar unserialize; usar json_decode; si imprescindible, restringir allowed_classes.

<?php
// Malo:
$data = unserialize($_POST['data']);

// Mejor:
$data = json_decode($_POST['data'], true);

// Si usas unserialize y confías parcialmente:
$allowed = ['MySafeClass'];
$data = unserialize($raw, ['allowed_classes' => $allowed]);
?>

Evita almacenar objetos serializados que puedan rehidratarse con control del atacante.

6) Gestión de contraseñas y autenticación

Usar hashing moderno y salting incorporado: password_hash / password_verify.

<?php
$hash = password_hash($password, PASSWORD_DEFAULT);
if (password_verify($password, $hash)) {
  // OK
}
?>

No uses MD5 o SHA1 para contraseñas. Implementa políticas de bloqueo por intentos y 2FA si es posible.

7) Sesiones seguras

Configuraciones clave y regeneración de ID.

<?php
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1); // requiere HTTPS
ini_set('session.cookie_samesite', 'Lax');

session_start();
// Regenerar ID después de login
session_regenerate_id(true);
?>

Almacena sesiones en un storage seguro (redis con autenticación o directorio fuera del webroot con permisos correctos).

8) Funciones peligrosas y php.ini

Deshabilitar funciones que no necesitas:

; php.ini
expose_php = Off
display_errors = Off
log_errors = On
allow_url_fopen = Off
allow_url_include = Off
; Deshabilitar funciones peligrosas
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
open_basedir = "/var/www/yourapp/:/tmp/"

Esto reduce la superficie de ataque y evita revelación de versiones.

9) Configuración segura del servidor y despliegue

  • Usa HTTPS siempre (HSTS)
  • Setea cabeceras de seguridad: X-Frame-Options, X-XSS-Protection (aunque CSP es la mejor defensa), Referrer-Policy
  • Minimiza permisos de usuario del proceso PHP-FPM
  • Mantén dependencias actualizadas y ejecuta auditorías de Composer: composer audit

10) Registro, monitoreo y detección

Registra eventos de seguridad (inicios de sesión fallidos, errores fatales), integra con SIEM, y establece alertas. No registres contraseñas ni tokens en texto plano.

11) Herramientas y prácticas recomendadas

  • Static analysis: PHPStan, Psalm
  • Dependency scanning: Roave Security Advisories + composer audit
  • Fuzzing y tests de integración para endpoints críticos
  • CI/CD: ejecutar linters, análisis estático y tests antes de deploy

12) Checklist rápido (prioriza)

  1. Deshabilitar display_errors en producción
  2. Usar prepared statements para todas las consultas
  3. Escapar toda salida por contexto
  4. Implementar tokens CSRF para formularios que modifican estado
  5. Validar y sanear uploads + almacenamiento fuera del webroot
  6. Eliminar uso de unserialize en entradas no confiables
  7. Usar password_hash y políticas de autenticación robustas
  8. Configurar cookies de sesión seguras y regenerar ID al login
  9. Auditar dependencias y aplicar parches

13) Ejemplo de configuración segura mínima (snippet)

; php.ini (fragmento)
expose_php = Off
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
allow_url_fopen = Off
allow_url_include = Off
disable_functions = exec,passthru,shell_exec,system,proc_open,popen
session.use_strict_mode = 1
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"
open_basedir = "/var/www/yourapp/:/tmp/"

14) Respuesta ante incidentes

Si detectas una intrusión: 1) aisla el servicio, 2) toma copias forenses, 3) rota credenciales y claves, 4) revisa logs para vector de entrada, 5) aplica parches y mitigaciones, 6) comunica a usuarios si es necesario.

Seguridad en PHP no es solo parchear fallos individuales, es establecer una defensa en profundidad: código seguro, configuración correcta, monitoreo y procesos de respuesta. Empieza por lo crítico (inyección, XSS, autenticación) y automatiza el resto en CI/CD.

Siguiente paso: habilita análisis estático (PHPStan o Psalm) en tu pipeline y realiza un escaneo de dependencias; si quieres, puedo mostrarte cómo integrar PHPStan y composer audit en GitHub Actions con ejemplos prácticos.

Comentarios
¿Quieres comentar?

Inicia sesión con Telegram para participar en la conversación


Comentarios (0)

Aún no hay comentarios. ¡Sé el primero en comentar!

Iniciar Sesión