chroot()
Узнайте, как функция chroot() в PHP изменяет корневой каталог процесса, создавая изолированную среду файловой системы. Синтаксис, примеры и ограничения.
Функция chroot() в PHP
Функция chroot() изменяет корневой каталог текущего выполняющегося процесса на указанный вами каталог, после чего устанавливает текущий рабочий каталог в /. После вызова процесс больше не может видеть или обращаться к файлам выше нового корня — он «заключён» внутри дерева каталогов. Эта техника обычно называется chroot-тюрьма.
На этой странице объясняется, что делает chroot(), когда следует (и не следует) её использовать, её синтаксис и ограничения, которые необходимо знать перед применением.
Синтаксис
chroot(string $directory): bool| Параметр | Описание |
|---|---|
$directory | Путь к каталогу, который становится новым корнем (/) процесса. |
Возвращаемое значение: true в случае успеха, false в случае ошибки.
Требования
chroot() доступна не везде. Перед использованием учитывайте следующие ограничения:
- Работает только с CLI и CGI SAPI — недоступна для большинства модульных SAPI, таких как
mod_phpили PHP-FPM при обработке обычных веб-запросов. - Не реализована в Windows.
- Вызывающий процесс должен иметь привилегии root (суперпользователя). Обычный пользователь не может изменить корневой каталог.
Из-за этих требований chroot() в основном используется в долгоживущих PHP CLI-демонах и рабочих скриптах, а не в коде, обрабатывающем обычные HTTP-запросы.
Базовый пример
Этот скрипт заключает процесс внутри /var/www/jail, а затем читает путь относительно нового корня:
<?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";
}После вызова chroot() путь /data/config.txt фактически указывает на /var/www/jail/data/config.txt в реальной файловой системе. Процесс просто не может сформировать путь, выходящий за пределы тюрьмы.
Проверка рабочего каталога
Поскольку chroot() также перемещает рабочий каталог в /, вы можете подтвердить изменение с помощью getcwd():
<?php
chroot('/var/www/jail');
echo getcwd(); // "/" (which is /var/www/jail on the real filesystem)Если внутри тюрьмы нужен другой рабочий каталог, установите его явно с помощью chdir() после вызова chroot().
Зачем использовать chroot()
Цель chroot() — изоляция. После того как процесс заключён в тюрьму:
- Он не может открывать, читать или записывать файлы за пределами нового корня, даже по абсолютным путям.
- Ошибка или эксплойт, пытающийся выполнить обход каталогов (
../../etc/passwd), не найдёт куда идти — пути выше/не существует. - Можно использовать минимальное дерево каталогов (только файлы, необходимые рабочему процессу), сокращая поверхность атаки.
Распространённый паттерн — запустить демон от имени root, вызвать chroot() для помещения в песочницу, а затем снизить привилегии с помощью posix_setuid() / posix_setgid(), чтобы заключённый процесс больше не работал от имени root.
chroot() vs open_basedir
Эти функции часто путают. Они решают схожую задачу на принципиально разных уровнях:
chroot() | open_basedir | |
|---|---|---|
| Уровень | Корень процесса на уровне операционной системы | Проверка пути в PHP-движке |
| Где задаётся | В коде во время выполнения | php.ini, .htaccess, пул FPM |
| Работает под веб-SAPI | Нет (только CLI/CGI) | Да |
| Требует привилегий root | Да | Нет |
| Надёжность | Тюрьма на уровне ОС | Рекомендательная, может быть обойдена через симлинки |
Если вам нужно лишь ограничить обычный веб-запрос одним каталогом, open_basedir — практичный инструмент. Используйте chroot(), когда вы управляете CLI-процессом и хотите создать настоящую границу на уровне ОС.
Ограничения и подводные камни
- Не является идеальной границей безопасности. Процесс, продолжающий работать от имени root внутри chroot, часто может выйти за её пределы. Всегда снижайте привилегии после заключения в тюрьму.
- Отсутствующие зависимости. Тюрьма не имеет
/lib,/etc,/usr, если вы их туда не поместите. Функции, зависящие от системных файлов (DNS-запросы, данные локали, часовые пояса, динамические библиотеки), могут не работать внутри тюрьмы. - Необратимо для процесса. Функции
unchroot()не существует; изменение действует на протяжении всего времени жизни процесса. - Зависит от окружения. Поскольку доступность зависит от SAPI и ОС, защищайте вызовы и проверяйте возвращаемое значение, а не считайте выполнение успешным по умолчанию.
Заключение
chroot() ограничивает PHP-процесс единственным деревом каталогов, изменяя его корень на указанный каталог и сбрасывая рабочий каталог в /. Это мощный инструмент изоляции на уровне ОС для привилегированных CLI-демонов, однако он требует прав root, ограничен CLI/CGI и недоступен в Windows. Для ограничений на уровне запроса в обычном веб-стеке используйте open_basedir и рассматривайте chroot() как один из уровней стратегии глубокоэшелонированной защиты. Чтобы узнать больше о работе с путями и файловой системой, смотрите chdir(), getcwd() и главу PHP Filesystem.
Диаграмма
Вот как chroot() изменяет то, к чему процесс получает доступ:
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 /];Примечание: граница обеспечивается операционной системой, поэтому она применяется ко всем файловым операциям процесса, а не только к вызовам PHP-функций.