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

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

Vulnerabilidades críticas en PHP y cómo solucionarlas

Esta guía repasa las vulnerabilidades más comunes en aplicaciones PHP modernas, muestra ejemplos vulnerables y proporciona correcciones concretas y recomendaciones operativas. Está pensada para desarrolladores que quieren mejorar la seguridad desde el código hasta la configuración y la infraestructura.

Índice rápido

  • Inyección SQL
  • XSS (Cross-Site Scripting)
  • CSRF (Cross-Site Request Forgery)
  • Subida de archivos insegura
  • Inclusión remota/Local File Inclusion
  • Ejecución de comandos (RCE / Command Injection)
  • Fuga de sesiones y cookies inseguras
  • Deserialización insegura
  • Configuración de PHP insegura

1) Inyección SQL

Qué pasa: concatenar entradas del usuario en SQL permite que un atacante altere la consulta.

<?php
// VULNERABLE
$unsafe = $_GET['id'] ?? '';
$sql = "SELECT * FROM users WHERE id = $unsafe";
$result = $pdo->query($sql);
?>

Solución: siempre usar sentencias preparadas con parámetros (PDO o mysqli). Ventaja: evita la interpretación del input como SQL.

<?php
// SEGURO
$id = $_GET['id'] ?? '';
$stmt = $pdo->prepare('SELECT id, name, email FROM users WHERE id = :id');
$stmt->execute([':id' => $id]);
$user = $stmt->fetch();
?>

Por qué: el motor de la base de datos trata el parámetro como valor, no como parte de la consulta.

2) XSS (Cross-Site Scripting)

Qué pasa: imprimir contenido controlado por el usuario sin escape permite inyección de HTML/JS.

<?php
// VULNERABLE
echo '<div>Mensaje: ' . $_GET['msg'] . '</div>';
?>

Solución: escapar la salida según el contexto (HTML, atributo, JS). En HTML usar htmlspecialchars con el charset apropiado.

<?php
// SEGURO (salida en HTML)
$msg = $_GET['msg'] ?? '';
echo '<div>Mensaje: ' . htmlspecialchars($msg, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '</div>';
?>

Por qué: se previene que etiquetas o scripts se interpreten en el navegador. Recuerda: validar en entrada y escapar en salida (output encoding).

3) CSRF

Qué pasa: acciones sensibles (cambiar correo, transferencias) pueden ser forzadas por un sitio malicioso si no se protege la petición.

<?php
// Generar token al iniciar formulario
session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];
?>
<form method='post' action='/account/update'>
  <input type='hidden' name='csrf_token' value='<?php echo $token; ?>' />
  <!-- campos -->
</form>

<?php
// Verificar token en el handler
session_start();
if (!hash_equals($_SESSION['csrf_token'] ?? '', $_POST['csrf_token'] ?? '')) {
    http_response_code(400);
    exit('Token inválido');
}
// procesar la acción
?>

Por qué: el token debe ser impredecible, único por sesión (o por formulario) y verificado con hash_equals para evitar timing attacks.

4) Subida de archivos

Riesgo: subir imágenes o archivos sin comprobar puede permitir scripts, overwrite y exfiltración.

Buenas prácticas:

  • Almacenar uploads fuera del directorio público o servirlos mediante un controlador que valide permisos.
  • Renombrar archivos con nombres únicos (hash + extensión permitida).
  • Comprobar MIME con finfo_file y, para imágenes, validar con getimagesize.
  • Limitar tamaño, desinfectar metadatos y establecer permisos restrictivos (600/640).
<?php
$allowedExt = ['jpg','jpeg','png','gif'];
$upload = $_FILES['file'];
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($upload['tmp_name']);
$ext = strtolower(pathinfo($upload['name'], PATHINFO_EXTENSION));

if (!in_array($ext, $allowedExt) || !in_array($mime, ['image/jpeg','image/png','image/gif'])) {
    http_response_code(400);
    exit('Tipo de archivo no permitido');
}

// nombre seguro
$filename = bin2hex(random_bytes(16)) . '.' . $ext;
$destination = __DIR__ . '/../storage/uploads/' . $filename; // fuera de /public
move_uploaded_file($upload['tmp_name'], $destination);
?>

5) Inclusión remota / LFI / RFI

Evita construir rutas dinámicas a ficheros con input del usuario. Desactivar allow_url_include y validar rutas con whitelist o mapear claves a rutas concretas.

<?php
// VULNERABLE
$page = $_GET['page'];
include __DIR__ . '/pages/' . $page . '.php';

// SEGURO (mapa)
$map = ['home' => 'home.php', 'about' => 'about.php'];
$key = $_GET['page'] ?? 'home';
if (!isset($map[$key])) {
    http_response_code(404);
    exit('No encontrado');
}
include __DIR__ . '/pages/' . $map[$key];
?>

6) Ejecución de comandos (Command Injection)

No pases input directo a exec, system, passthru o backticks. Usa funciones nativas o escapes seguros.

<?php
// VULNERABLE
$user = $_GET['user'];
$output = shell_exec('ping -c 1 ' . $user);

// SEGURO: validar formato o usar escapeshellarg
$host = $_GET['host'];
if (!preg_match('/^[a-z0-9\.-]+$/i', $host)) {
    exit('Host inválido');
}
$escaped = escapeshellarg($host);
$output = shell_exec('ping -c 1 ' . $escaped);
?>

7) Sesiones y cookies inseguras

Asegura cookies y gestión de sesiones:

  • Usa session_set_cookie_params para marcar httponly, secure y samesite.
  • Regenera el ID de sesión al autenticar (session_regenerate_id(true)).
  • Mantén el almacenamiento de sesiones en un backend seguro (Redis, memcached con ACLs) y establece lifetime razonable.
<?php
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'domain' => 'example.com',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
]);
session_start();
?>

8) Deserialización insegura

Evita unserialize en datos no confiables: puede crear objetos con comportamiento no esperado (gadgets) que permitan RCE.

Si debes deserializar, usa JSON (json_encode/json_decode) o deserialización segura con allowed_classes en unserialize:

<?php
// Evitar
$data = unserialize($untrusted);

// Mejor
$data = json_decode($untrusted, true);

// Si usas unserialize por necesidad
$allowed = ['MyDTO'];
$data = unserialize($untrusted, ['allowed_classes' => $allowed]);
?>

9) Configuración de PHP y prácticas operativas

  • php.ini: display_errors = Off, log_errors = On, allow_url_include = Off.
  • Deshabilitar funciones peligrosas si no se necesitan: disable_functions = exec,passthru,shell_exec,system,proc_open,popen.
  • Usar HTTPS con HSTS; servir cookies secure.
  • Limitar permisos de archivos y usuarios del proceso PHP-FPM.
  • Actualiza PHP y extensiones; usa versiones LTS soportadas.

Herramientas y prácticas recomendadas

  • Dependabot / Composer audit / Security advisories para dependencias.
  • Escaneo dinámico: OWASP ZAP, Burp Suite.
  • Escaneo estático: SonarQube, Psalm, PHPStan con reglas de seguridad.
  • Pruebas de penetración periódicas y revisión de código con checklist de seguridad.

Ejemplo de estructura de proyecto segura

/project-root
├─ /public              # Document root: sólo index.php, assets
│  └─ index.php
├─ /src                 # Lógica de aplicación
├─ /config              # Config y secrets (fuera de VCS, usar env/secret manager)
├─ /storage
│  └─ /uploads          # uploads fuera de public
├─ /logs
└─ composer.json

Checklist rápido para desplegar una app PHP segura

  1. Usar PDO con parámetros en todas las consultas.
  2. Escapar salida con htmlspecialchars y considerar escaping específico por contexto.
  3. Proteger formularios con tokens CSRF.
  4. Validar y sanear uploads y almacenarlos fuera del webroot.
  5. Configurar cookies y sesiones con secure, httponly, samesite.
  6. Desactivar funciones PHP peligrosas y display_errors en producción.
  7. Usar password_hash y password_verify para contraseñas.
  8. Ejecutar scans automáticos en CI y aplicar parches regularmente.

Recursos y referencias

  • OWASP Top 10
  • PHP: The Right Way (sección security)
  • Documentación de PDO, password_hash, finfo
Consejo avanzado: integra análisis estático (PHPStan/Psalm) con reglas de seguridad personalizadas en tu pipeline, añade escaneo de dependencias en CI y habilita alertas para eventos inusuales en logs (p. ej. múltiples intentos de subida de archivos no válidos). También considera un WAF en frente de la app para filtrar vectores conocidos.

Advertencia: la seguridad es un proceso continuo. Empieza por las correcciones críticas (inyección, XSS, CSRF) y avanza hacia controles operativos y detección.

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