chroot()
Aprende cómo la función chroot() de PHP cambia el directorio raíz de un proceso para crear un entorno de sistema de archivos aislado, con sintaxis y ejemplos.
Función PHP chroot()
La función chroot() cambia el directorio raíz del proceso en ejecución al directorio que se le indica y luego establece el directorio de trabajo actual en /. Tras la llamada, el proceso no puede ver ni acceder a ningún archivo por encima de esa nueva raíz — queda "enjaulado" dentro del árbol de directorios. Esta técnica se conoce comúnmente como chroot jail.
Esta página explica qué hace chroot(), cuándo usarla (y cuándo no), su sintaxis y las limitaciones que debes conocer antes de depender de ella.
Sintaxis
chroot(string $directory): bool| Parámetro | Descripción |
|---|---|
$directory | La ruta al directorio que se convierte en la nueva raíz (/) del proceso. |
Valor de retorno: true si tiene éxito, false si falla.
Requisitos
chroot() no está disponible en todos los entornos. Antes de usarla, ten en cuenta estas restricciones:
- Funciona solo con SAPIs de CLI y CGI — no está disponible en la mayoría de los SAPIs de módulo, como
mod_phpo PHP-FPM ejecutando una solicitud web típica. - No está implementada en Windows.
- El proceso que realiza la llamada debe tener privilegios de superusuario (root). Un usuario normal no puede cambiar el directorio raíz.
Debido a estos requisitos, chroot() se usa principalmente en demonios CLI de larga ejecución y scripts de trabajo, no en código que gestiona solicitudes HTTP ordinarias.
Ejemplo básico
Este script enjaulan el proceso dentro de /var/www/jail y luego lee una ruta relativa a la nueva raíz:
<?php
// Must be run as root, on CLI.
if (chroot('/var/www/jail')) {
echo "Root directory changed.\n";
// Paths are now relative to /var/www/jail.
// What was /var/www/jail/data/config.txt is now /data/config.txt
$contents = file_get_contents('/data/config.txt');
echo $contents;
} else {
echo "Failed to change root directory.\n";
}Después de la llamada a chroot(), la ruta /data/config.txt hace referencia en realidad a /var/www/jail/data/config.txt en el sistema de archivos real. El proceso simplemente no puede expresar una ruta que escape de la jaula.
Confirmar el directorio de trabajo
Dado que chroot() también mueve el directorio de trabajo a /, puedes confirmar el cambio con getcwd():
<?php
chroot('/var/www/jail');
echo getcwd(); // "/" (which is /var/www/jail on the real filesystem)Si necesitas un directorio de trabajo diferente dentro de la jaula, establécelo explícitamente con chdir() después de la llamada a chroot().
Por qué usar chroot()
El objetivo de chroot() es el aislamiento. Una vez que un proceso está enjaulado:
- No puede abrir, leer ni escribir archivos fuera de la nueva raíz, ni siquiera con rutas absolutas.
- Un error o exploit que intente un recorrido de directorio (
../../etc/passwd) no tiene nada hacia donde recorrer — no existe ninguna ruta por encima de/. - Puedes incluir un árbol de directorios mínimo (solo los archivos que el trabajador necesita realmente), reduciendo la superficie de ataque.
Un patrón común es iniciar un demonio como root, llamar a chroot() para encerrarlo en un entorno aislado y luego eliminar los privilegios con posix_setuid() / posix_setgid() para que el proceso enjaulado ya no se ejecute como root.
chroot() vs open_basedir
Estos dos mecanismos se confunden con frecuencia. Resuelven un problema similar en niveles muy diferentes:
chroot() | open_basedir | |
|---|---|---|
| Nivel | Raíz del proceso a nivel de sistema operativo | Verificación de ruta del motor PHP |
| Dónde se configura | En el código en tiempo de ejecución | php.ini, .htaccess, grupo FPM |
| Funciona con SAPIs web | No (solo CLI/CGI) | Sí |
| Requiere privilegios root | Sí | No |
| Solidez | Jaula impuesta por el SO | Orientativa, puede debilitarse con enlaces simbólicos |
Si solo necesitas mantener una solicitud web normal dentro de un directorio, open_basedir es la herramienta práctica. Usa chroot() cuando controlas un proceso CLI y quieres un límite real a nivel de sistema operativo.
Limitaciones y advertencias
- No es un límite de seguridad perfecto. Un proceso que aún se ejecute como root dentro de un chroot puede frecuentemente escapar de él. Siempre elimina los privilegios después de enjaular.
- Dependencias faltantes. La jaula no tiene
/lib,/etc,/usra menos que los incluyas. Las funciones que dependen de archivos del sistema (consultas DNS, datos de configuración regional, zonas horarias, librerías dinámicas) pueden fallar dentro de la jaula. - Unidireccional para el proceso. No existe
unchroot(); el cambio dura toda la vida del proceso. - Depende del entorno. Dado que la disponibilidad depende del SAPI y del sistema operativo, protege las llamadas y verifica el valor de retorno en lugar de asumir que tendrán éxito.
Conclusión
chroot() confina un proceso PHP a un único árbol de directorios cambiando su raíz a ese directorio y restableciendo el directorio de trabajo a /. Es una potente herramienta de aislamiento a nivel de sistema operativo para demonios CLI con privilegios, pero requiere acceso root, está limitada a CLI/CGI y no está disponible en Windows. Para restricciones por solicitud en una pila web normal, recurre a open_basedir y trata chroot() como una capa más de una estrategia de defensa en profundidad. Para aprender más sobre cómo trabajar con rutas y el sistema de archivos, consulta chdir(), getcwd() y el capítulo PHP Filesystem.
Diagrama
Así es como chroot() transforma lo que un proceso puede alcanzar:
graph TD;
A[PHP Process] --> B{chroot('/var/www/jail')};
B --> C[New root = /var/www/jail];
C --> D[Working dir set to /];
D -->|Path /data/config.txt| E[Allowed: inside jail];
D -->|Path ../../etc/passwd| F[Blocked: nothing above /];Nota: El límite es impuesto por el sistema operativo, por lo que se aplica a cada operación de archivo que realiza el proceso, no solo a las llamadas de funciones PHP.