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

PHP sigue siendo una pieza clave en la web. Pero también es un objetivo frecuente para atacantes. Aquí tienes un compendio práctico: vulnerabilidad, ejemplo vulnerable, por qué ocurre y cómo corregirla con código real y recomendaciones de configuración.

1. Inyección SQL

Qué pasa: input sin sanear se concatena en consultas SQL, permitiendo manipulación del query.

Ejemplo vulnerable:

<?php
// vulnerable.php (NO usar)
$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '" . $username . "'";
$result = $pdo->query($query);
?>

Por qué: concatenación directa y ausencia de parámetros. Un atacante puede enviar "' OR '1'='1".

Solución (PDO con prepared statements):

<?php
// safe-sql.php
$stmt = $pdo->prepare('SELECT id, username FROM users WHERE username = :username');
$stmt->execute(['username' => $_POST['username'] ?? '']);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
?>

Por qué funciona: parámetros ligan valores sin alterar la estructura del SQL.

2. Cross-Site Scripting (XSS)

Qué pasa: salida de datos controlados por el usuario sin escapar en HTML.

Ejemplo vulnerable:

<?php
// profile.php (vulnerable)
echo "<h1>" . $user['display_name'] . "</h1>";
?>

Solución: escapar o codificar la salida según el contexto. Para HTML: htmlspecialchars.

<?php
// profile-safe.php
function e($s) { return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
echo "<h1>" . e($user['display_name']) . "</h1>";
?>

Consejo: para atributos, JS, URLs y CSS usa el método de escape apropiado o una librería de templates que lo haga por ti (Twig, Plates, Blade).

3. CSRF (Cross-Site Request Forgery)

Qué pasa: peticiones que tienen privilegios del usuario autenticado pueden ser forzadas desde otra web.

Ejemplo vulnerable:

<form method="post" action="/transfer.php">
  <input name="amount">
  <button>Send</button>
</form>

Solución: tokens CSRF por sesión y verificación en POST/acciones mutativas.

<?php
// csrf.php - helpers
session_start();
if (!isset($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
function csrf_input() {
  return '';
}
// On form processing
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
  http_response_code(403);
  exit('Invalid CSRF token');
}
?>

Nota: usa SameSite cookies y verifica Origin/Referer cuando sea posible.

4. Subida de archivos insegura

Qué pasa: aceptar y mover archivos sin validar tipo, extensión, tamaño o ruta puede permitir ejecución remota.

Ejemplo vulnerable:

<?php
// upload.php (vulnerable)
move_uploaded_file($_FILES['file']['tmp_name'], '/var/www/html/uploads/' . $_FILES['file']['name']);
?>

Solución: validar MIME real, renombrar archivos, restringir rutas y ubicaciones fuera del webroot, establecer permisos mínimos.

<?php
// upload-safe.php
$allowed = ['image/png','image/jpeg'];
$f = $_FILES['file'] ?? null;
if (!$f || $f['error'] !== UPLOAD_ERR_OK) exit('Upload error');
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$type = finfo_file($finfo, $f['tmp_name']);
if (!in_array($type, $allowed, true)) exit('Invalid file type');
$ext = $type === 'image/png' ? '.png' : '.jpg';
$destName = bin2hex(random_bytes(16)) . $ext;
$destPath = '/var/uploads/' . $destName; // fuera del webroot
move_uploaded_file($f['tmp_name'], $destPath);
?>

Adicional: escanea los ficheros con antivirus y establece permisos 0600/0640.

5. Remote/Local File Inclusion (RFI/LFI)

Qué pasa: incluir archivos basados en input del usuario puede ejecutar código arbitrario o leer archivos del sistema.

Vulnerable:

<?php
// index.php (vulnerable)
$page = $_GET['page'];
include 'pages/' . $page . '.php';
?>

Arreglo: whitelist de páginas y/o mapear rutas explícitamente.

<?php
$allowed = ['home' => 'pages/home.php', 'about' => 'pages/about.php'];
$page = $_GET['page'] ?? 'home';
if (!isset($allowed[$page])) { http_response_code(404); exit('Not found'); }
include $allowed[$page];
?>

6. Command Injection

Qué pasa: pasar input directamente a funciones como exec(), system(), shell_exec() permite ejecución de comandos.

Vulnerable:

<?php
$ip = $_GET['ip'];
$output = shell_exec('ping -c 1 ' . $ip);
echo <pre> . $output . </pre>;
?>

Solución: evita invocar shell; usa librerías nativas. Si debes, valida estrictamente y usa escapeshellarg.

<?php
$ip = $_GET['ip'];
if (!filter_var($ip, FILTER_VALIDATE_IP)) { exit('Invalid IP'); }
$output = shell_exec('ping -c 1 ' . escapeshellarg($ip));
?>

7. Gestión de sesiones insegura

Problemas comunes: session fixation, cookies sin flags, sesión no regenerada al login.

Buenas prácticas:

  • session_start(); luego session_regenerate_id(true) tras autenticación.
  • Configurar cookies: session.cookie_httponly = On, session.cookie_secure = On (HTTPS), session.use_strict_mode = 1
  • Limitar duración y destruir sesión en logout.
<?php
// after successful login
session_regenerate_id(true);
$_SESSION['user_id'] = $userId;
?>

8. Insecure deserialization

Qué pasa: unserialize() puede instanciar objetos con efectos secundarios (magic methods) o ejecutar __wakeup/__destruct.

Evitar usar unserialize() en datos no confiables. Usa json_encode/json_decode en su lugar o whitelist de clases.

// Evitar
$data = unserialize($_POST['data']);

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

9. Fugas de información y manejo de errores

Qué pasa: mostrar errores con stack traces o detalles de DB en producción revela vectores de ataque.

Configurar en production php.ini:

display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
expose_php = Off

En tu app, captura excepciones y muestra mensajes genéricos al usuario mientras registras detalles internamente.

10. Configuración segura de PHP

  • disable_functions = exec,passthru,shell_exec,system,popen,proc_open (bloquea funciones peligrosas)
  • allow_url_fopen = Off y allow_url_include = Off (previene inclusiones remotas)
  • open_basedir = /var/www/html/:/tmp/ (limita accesos de filesystem)
  • session.cookie_httponly = 1, session.cookie_secure = 1, session.use_strict_mode = 1

Herramientas y flujo de trabajo

Integra estas prácticas en CI/CD:

  • Static analysis (PHPStan, Psalm)
  • SAST/DAST (SonarQube, OWASP ZAP)
  • Dependency checks (Composer audit, SensioLabs Security Checker)
  • Pen testing y fuzzing regulares

Ejemplo: estructura mínima segura para un endpoint

Carpeta propuesta:

project/
  public/index.php
  src/
    App.php
    Controllers/
    Middleware/
  vendor/
  logs/
  .env

public/index.php (abridor seguro):

<?php
require __DIR__ . '/../vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../');
$dotenv->load();
// Force HTTPS (reverse proxy aware)
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') {
  // redirigir o aplicar políticas
}
// Dispatcher simple: valida rutas, usa middleware para auth, CSRF y sanitización
?>

Checklist rápido para revisar una app PHP

  • ¿Todas las queries usan prepared statements?
  • ¿Salida HTML escapada por defecto?
  • ¿Tokens CSRF en formularios mutativos?
  • ¿Subidas fuera del webroot y validadas?
  • ¿Funciones shell deshabilitadas si no son necesarias?
  • ¿Errores ocultos en producción y logs seguros?
  • ¿Sesiones con flags secure/httponly y regeneración de id?

Implementar estas medidas reduce significativamente el riesgo. Un paso avanzado: añade pruebas de seguridad automatizadas en tu CI (p. ej. OWASP ZAP en GitHub Actions) y reglas de seguridad en el código (PHPStan/Psalm con niveles altos).

Advertencia final: incluso con configuraciones seguras, las dependencias y el despliegue (exposición de puertos, permisos de filesystem, backups inseguros) son vectores críticos — revisa también la superficie de red y la pipeline de despliegue como siguiente paso.

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