FlawOpen

flawopen.com/Inclusión de archivos/PHP include()

¿Es seguro usar include() de PHP con datos del usuario?

CWE-98: PHP Remote File InclusionPágina de referencia
Respuesta breve

No. include() analiza el archivo seleccionado como PHP, pero el impacto depende del archivo alcanzado y de su contenido. Un fallo puede revelar código fuente mediante un wrapper, incluir un archivo local inesperado o ejecutar PHP controlado por un atacante solo si este puede influir en contenido con etiquetas PHP y la aplicación lo incluye. La inclusión de URL remota también depende de la configuración de PHP. Asocia una clave elegida por el usuario a una lista fija; nunca construyas la ruta de include con datos de la petición.

VULNERABLE
<?php
// User-controlled path: unintended local file
include $_GET['page'] . '.php';

// Example selector: ../../private/sample
// Impact depends on the resolved file and
// whether it contains PHP code to interpret.

// Remote URL inclusion requires the relevant
// URL wrapper and allow_url_include setting.

// Wrappers can change which resource is read;
// php://filter is a read filter, not a path
// containment check.
CORREGIDO
<?php
// Allowlist: input selects a KEY,
// never a path or wrapper.
$pages = [
    'home'    => __DIR__ . '/pages/home.php',
    'about'   => __DIR__ . '/pages/about.php',
    'contact' => __DIR__ . '/pages/contact.php',
];

$key = $_GET['page'] ?? 'home';

if (!is_string($key) || !array_key_exists($key, $pages)) {
    http_response_code(404);
    exit;
}

include $pages[$key];

Por qué la inclusión local no implica ejecución de código automáticamente

La inclusión local no ejecuta código automáticamente. PHP analiza el archivo incluido; las instrucciones dentro de etiquetas PHP válidas se ejecutan en el contexto de inclusión, mientras que otros bytes pueden mostrarse como salida. La ejecución requiere un archivo accesible con PHP controlado por el atacante u otro comportamiento explotable. Por ejemplo, un fallo de carga o escritura de registros puede formar una cadena si coloca contenido PHP controlado en un archivo que después se incluye.

Por separado, php://filter aplica un filtro al abrir un flujo. En un flujo vulnerable de lectura o inclusión, un filtro adecuado puede exponer texto fuente sin ejecutarlo; la lectura depende del wrapper, los permisos y la configuración de la aplicación.

Por qué sanitizar la ruta no es la solución

Eliminar ../, añadir una extensión fija o anteponer un directorio no demuestra que el recurso final esté aprobado. Las codificaciones, rutas absolutas, reglas de la plataforma y wrappers pueden cambiar la interpretación. Elimina el control del usuario sobre el recurso: permite elegir solo claves de una lista fija definida por la aplicación.

Si debes resolver una ruta, canonicalízala con realpath() y verifica que permanezca dentro del directorio, como explica la guía de Path Traversal. Limita el resultado a archivos aprobados y considera los symlinks y las carreras entre comprobación y uso. Una lista permitida de destinos fijos es más segura.

Preguntas frecuentes

¿Desactivar allow_url_include hace que sea seguro?

Esta opción desactiva los wrappers URL en include/require. PHP indica que está desactivada por defecto y obsoleta desde PHP 7.4. Reduce la exposición a inclusión remota, pero no evita el traversal local ni los includes locales inseguros. Mantenla desactivada y elimina el control del usuario sobre el destino.

¿Sigue funcionando la técnica del byte nulo?

PHP 5.3.4 corrigió la terminación por byte nulo en el manejo de rutas. No uses la ausencia de esta técnica antigua como defensa: los destinos de include no confiables siguen siendo inseguros.

¿Y require, include_once y readfile?

require y las variantes _once también incluyen y analizan el destino; difieren en el manejo de errores y repeticiones, pero no hacen seguro un destino no confiable. readfile() y file_get_contents() devuelven datos sin analizarlos como PHP; una ruta no confiable puede causar traversal o divulgación, no ejecución mediante include.

Referencias

Véase también Path traversal (CWE-22): cómo funciona y cómo evitarlo en todos los lenguajes — flawopen.com Corregir path traversal con Python os.path.join() ¿Qué es una vulnerabilidad Zip Slip? Node.js path.join() no evita Path Traversal