Situación actual: PHP2Shell. Dos vulnerabilidades críticas de PHP (CVSS 9.2) permiten ejecutar código malicioso sin autenticación. Parches desde el 21/10/2026.Qué hacer ahora →

Evitar Local File Inclusion en PHP: LFI y RFI

Cuando include o require toman rutas de la petición, los atacantes pueden cargar archivos ajenos. Cómo construir con seguridad la selección de páginas y las plantillas y proteger además el servidor.

Actualizado 6 min de lecturaGuías

En resumen: la Local File Inclusion en PHP surge cuando include, require o las funciones de archivos usan una ruta que el visitante puede influir. La solución segura es una lista fija de permitidos: el parámetro solo elige entre valores conocidos, y la ruta en sí nunca procede de la petición. En el servidor, allow_url_include=Off, open_basedir y unos permisos de archivos restrictivos completan la protección.

¿Qué es la Local File Inclusion en PHP?

Muchas aplicaciones PHP antiguas cargan las páginas con un patrón sencillo: un parámetro de la dirección determina qué archivo se incluye. Si ese parámetro se pasa a include sin verificar, un atacante puede pedir otros archivos del servidor. En el caso más benigno, lee archivos de configuración con credenciales. En el peor, consigue que PHP ejecute contenidos que antes dejó en otro lugar, por ejemplo en una subida o en un archivo de registro.

Para la dirección es importante saber que esta vulnerabilidad rara vez está en el núcleo del CMS, sino sobre todo en desarrollos propios, extensiones antiguas o páginas adicionales hechas deprisa. En el día a día no llama la atención, porque el sitio web funciona con normalidad.

En la Remote File Inclusion, PHP descarga código de una dirección ajena. Eso presupone que allow_url_include está activado. En las versiones actuales de PHP, este ajuste está desactivado por defecto, pero en servidores antiguos o mal configurados todavía se encuentra.

Patrones vulnerables típicos en el código

Están en peligro los puntos en los que un valor de la petición, de las cookies o de la base de datos pasa a formar parte de una ruta de archivo. Ejemplos frecuentes son los selectores de idioma que cargan un archivo de idioma según un parámetro, la selección de páginas mediante un parámetro de la dirección, la elección de plantillas o temas y los scripts de descarga de documentos. Un .php añadido al final de la ruta no protege de forma fiable, porque los atacantes pueden recurrir a archivos PHP existentes.

Una primera visión de conjunto se obtiene con una búsqueda de inclusiones que contengan variables. También arroja coincidencias inofensivas, pero muestra dónde hay que mirar con más detalle:

grep -rnE --include='*.php' '(include|require)(_once)?\s*\(?\s*\$' /var/www/projekt \
  | grep -v '/vendor/'

Revise cada coincidencia: de dónde procede la variable y si un visitante puede influir en ella.

El patrón seguro: lista de permitidos en lugar de ruta de la petición

La protección más fiable es que el visitante solo elija una clave y que la propia aplicación decida qué archivo le corresponde. La ruta nunca se compone a partir de entradas.

<?php
declare(strict_types=1);

$seiten = [
    'start'      => __DIR__ . '/../vorlagen/start.php',
    'leistungen' => __DIR__ . '/../vorlagen/leistungen.php',
    'kontakt'    => __DIR__ . '/../vorlagen/kontakt.php',
];

$wahl = is_string($_GET['seite'] ?? null) ? $_GET['seite'] : 'start';

if (!array_key_exists($wahl, $seiten)) {
    http_response_code(404);
    $wahl = 'start';
}

require $seiten[$wahl];

Este patrón es corto, fácil de revisar y no deja margen. En los frameworks modernos, el router asume esa tarea; allí, los controladores y las plantillas tampoco deberían determinarse nunca a partir de parámetros libres.

Cuando los nombres de archivo deben ser dinámicos: realpath y directorio base

A veces una lista fija no es práctica, por ejemplo en descargas desde una carpeta de documentos. Entonces se comprueba la ruta resuelta contra un directorio base fijo y solo se admiten nombres de archivo sencillos.

<?php
declare(strict_types=1);

const BASIS = '/var/www/daten/dokumente';

function sichererPfad(string $name): string
{
    if (!preg_match('/^[a-z0-9_-]{1,64}\.pdf$/', $name)) {
        throw new InvalidArgumentException('Ungültiger Dateiname.');
    }

    $basis = realpath(BASIS);
    $pfad  = realpath($basis . DIRECTORY_SEPARATOR . $name);

    if ($basis === false || $pfad === false
        || !str_starts_with($pfad, $basis . DIRECTORY_SEPARATOR)) {
        throw new RuntimeException('Datei nicht gefunden.');
    }

    return $pfad;
}

// Entregar en lugar de incluir: readfile() no ejecuta código.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);

Dos puntos son decisivos. La expresión regular no permite separadores de directorio ni caracteres especiales. Y los archivos que solo deben entregarse se leen con readfile(), no con include, para que el código que contengan no se ejecute nunca. str_starts_with() está disponible desde PHP 8.0.

Protección en el servidor: php.ini, open_basedir y permisos

El código seguro es la primera línea de defensa. La configuración del servidor hace que un error pasado por alto cause menos daño.

; php.ini o pool de FPM (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen   = Off   ; solo si la aplicación no carga URL externas
open_basedir      = /var/www/kunde-a/:/tmp/kunde-a/
display_errors    = Off
log_errors        = On

Algunas aplicaciones necesitan allow_url_fopen para actualizaciones o interfaces. Pruebe primero su desactivación en un entorno de pruebas. open_basedir limita PHP a los directorios propios del sitio web, algo especialmente importante en servidores con varios clientes. Encontrará los detalles en las guías open_basedir y aislamiento por sitio web y Configurar php.ini de forma segura.

Como complemento, los directorios de subidas y de caché no deberían ser ejecutables y los archivos de registro no deberían estar en el webroot. Así, al atacante le faltan los lugares donde podría depositar contenido propio para una inclusión.

Cómo reconocer intentos de explotación en los registros

Los intentos de explotar una file inclusion suelen dejar rastros en el registro de acceso. Llaman la atención los parámetros que contienen cambios de directorio, rutas absolutas del sistema, stream wrappers o direcciones ajenas completas, sobre todo en parámetros como page, file, template o lang. Una única coincidencia suele ser un escaneo automatizado. Lo importante es si una petición así se respondió con código de estado 200 y un tamaño de respuesta inusual.

Cómo proceder de forma sistemática lo describe Analizar los registros del servidor web tras un ataque. Si encuentra indicios de accesos exitosos, dé por hecho que se leyeron archivos de configuración y renueve las credenciales que contenían.

Entre ellas están sobre todo las contraseñas de la base de datos, las claves de los servicios de pago y de envío, los accesos SMTP y las claves secretas del CMS. Compruebe además si han aparecido archivos nuevos desde el primer acceso sospechoso. Quien pudo leer archivos quizá también intentó dejar los suyos. En ese caso ya no se trata solo de una vulnerabilidad en el código, sino de un incidente de seguridad que debería tratarse de forma estructurada.

Asegurar las correcciones de forma duradera

Tras una corrección merece la pena asegurar la función de protección con pruebas automatizadas. Una prueba sencilla pasa a la selección de páginas valores desconocidos, cambios de directorio y entradas vacías, y espera en todos los casos la página por defecto o un error 404. Esas pruebas evitan que un cambio posterior vuelva a abrir la vulnerabilidad sin que nadie lo note.

Para la dirección y los responsables de proyectos resulta útil una pregunta a la agencia: ¿hay en la aplicación puntos en los que se carguen archivos según parámetros y cómo están protegidos? Una respuesta concreta con los puntos y las pruebas es una buena señal.

¿Cómo ayuda PHP SOS en concreto?

  • Buscamos en su código, de forma específica, include, require y funciones de archivos con entradas de la petición y valoramos cada hallazgo.
  • Configuramos allow_url_include, open_basedir y los permisos adecuados por sitio web y comprobamos que la aplicación sigue funcionando después.
  • Analizamos los registros en busca de intentos de explotación y aclaramos si hay que renovar credenciales.
  • Para una revisión independiente ofrecemos una auditoría de seguridad con prueba de penetración para aplicaciones PHP.

Preguntas frecuentes

¿Cuál es la diferencia entre LFI y RFI?

En la Local File Inclusion, la aplicación carga un archivo que ya está en el servidor pero que no debería cargarse. En la Remote File Inclusion se descarga código desde una dirección ajena. RFI presupone un ajuste inseguro de PHP; LFI no.

¿Basta con eliminar ../ del parámetro?

No. Esos filtros suelen poder eludirse con codificaciones o variantes y pasan por alto las rutas absolutas. Solo es seguro una asignación fija de valores permitidos a archivos o una comprobación de la ruta resuelta contra un directorio base.

¿Protege open_basedir por completo frente a la file inclusion?

No, solo limita los directorios a los que puede acceder PHP. Dentro de esos límites, la vulnerabilidad sigue siendo explotable. open_basedir es una capa adicional de protección y no sustituye a un código seguro.

¿Afecta la LFI a los CMS como WordPress?

El núcleo de los CMS habituales se considera bien revisado en este punto. Las vulnerabilidades aparecen más bien en extensiones, temas y desarrollos propios. Por eso son importantes las actualizaciones de las extensiones y echar un vistazo a sus avisos de seguridad.

Fuentes

Contacto inmediato

Cada hora cuenta. Escríbanos ahora.

Describa brevemente el sistema, la versión de PHP y lo que ha observado. Nos pondremos en contacto, aclararemos la situación y trabajaremos en remoto hasta que el incidente quede cerrado y documentado.

Ayuda inmediata