Vulnerabilidades críticas en PHP y cómo solucionarlas

php Vulnerabilidades críticas en PHP y cómo solucionarlas

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.

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