Если DocumentRoot вашего GLPI указывает на корень установки, а не на папку public, файл с паролем от вашей базы данных находится на расстоянии одного GET-запроса. Начиная с GLPI 10 это самое важное решение по безопасности при развёртывании — и именно его мы чаще всего находим неверным в средах, которые приходят к нам на сопровождение, потому что система отлично работает даже при неправильной настройке.
Почему неверный webroot остаётся незамеченным
GLPI 10 продолжает открываться и нормально работать, если направить веб-сервер на корень (/var/www/glpi) вместо /var/www/glpi/public. На экране ничего не ломается, никто не создаёт заявку, и поэтому уязвимость живёт годами. При сопровождении парков GLPI у клиентов это самая частая тихая находка при онбординге: среда отвечает нормально, но https://glpi.cliente.com/files/_log/php-errors.log отдаёт лог приложения как обычную загрузку.
Деталь, которая проявляется только в эксплуатации: в Apache старую установку частично спасали файлы .htaccess, разбросанные внутри config/, files/ и подобных каталогов и запрещавшие доступ. Это создаёт ложное ощущение защиты. В Nginx этих .htaccess просто не существует — Nginx их никогда не читает. Поэтому переход с Apache на Nginx с неверным root превращает частичное раскрытие в полное: files/, config/ и весь исходный код становятся статическим содержимым, доступным кому угодно. Смена веб-сервера — классический момент, когда эта бомба взрывается.
Что оказывается открытым при неверном DocumentRoot
Папка public существует для того, чтобы публиковать только маршрутизатор (index.php), .htaccess и статические файлы (CSS, JS, изображения). Всё остальное должно быть вне досягаемости браузера. Таблица ниже — матрица проверки, которую мы используем: при public в качестве webroot все перечисленные URL должны возвращать 404.
| Путь | Содержимое | Последствия раскрытия | Ожидаемый статус |
|---|---|---|---|
/config/config_db.php | Хост, пользователь и пароль базы | Полная утечка базы данных | 404 |
/files/_log/ | php-errors.log, sql-errors.log | Разведка приложения и утечка персональных данных | 404 |
/files/_dumps/ | SQL-дампы, созданные GLPI | Копия базы, доступная для скачивания | 404 |
/files/_sessions/ | Файлы активных сессий | Перехват сессии и кража CSRF-токена | 404 |
/src/ | Исходный код приложения на PHP | Карта приложения для эксплуатации уязвимостей | 404 |
/vendor/ | Зависимости Composer | Эксплуатация CVE в библиотеках | 404 |
/install/install.php | Мастер установки | Перенастройка и захват среды | 404 (удалить после установки) |
Осторожно с ложной «безопасностью»: при работающем PHP /config/config_db.php может отвечать 200 с пустым телом (файл лишь определяет класс и ничего не выводит). Пустой ответ 200 не означает, что всё в порядке, — он означает, что файл находится внутри webroot. Как загрузка реально утекают не-PHP файлы: files/_log/*.log, files/_dumps/*.sql и любая забытая в дереве резервная копия. Поэтому критерий — 404, а не «пустая страница».
Apache: усиленный виртуальный хост
DocumentRoot указывает на public, листинг каталогов отключён, а чистый HTTP перенаправляется на HTTPS. FallbackResource заменяет маршрутизацию через .htaccess, не требуя AllowOverride All:
# /etc/apache2/sites-available/glpi.conf
<VirtualHost *:80>
ServerName glpi.suaempresa.com
# Nada de GLPI em HTTP puro
Redirect permanent / https://glpi.suaempresa.com/
</VirtualHost>
<VirtualHost *:443>
ServerName glpi.suaempresa.com
DocumentRoot /var/www/glpi/public
<Directory /var/www/glpi/public>
Require all granted
Options -Indexes +FollowSymLinks
# Roteia sem depender de AllowOverride All
FallbackResource /index.php
</Directory>
# Nega qualquer arquivo oculto (.git, .env, .htaccess)
<FilesMatch "^\.">
Require all denied
</FilesMatch>
# Cabeçalhos de segurança
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
SSLEngine On
SSLCertificateFile /etc/ssl/certs/glpi.crt
SSLCertificateKeyFile /etc/ssl/private/glpi.key
</VirtualHost>
Nginx: без страховки в виде .htaccess
В Nginx нет .htaccess, который бы вас спас, поэтому root должен быть public с самого начала. Обратите внимание на две строки, которых почти всегда не хватает: try_files $uri =404; внутри блока PHP и явная блокировка скрытых файлов.
# /etc/nginx/sites-available/glpi.conf
server {
listen 80;
server_name glpi.suaempresa.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
http2 on;
server_name glpi.suaempresa.com;
root /var/www/glpi/public;
index index.php;
ssl_certificate /etc/ssl/certs/glpi.crt;
ssl_certificate_key /etc/ssl/private/glpi.key;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ \.php$ {
# =404 impede execução via PATH_INFO forjado
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# Nginx não lê .htaccess: negue arquivos ocultos na mão
location ~ /\. {
deny all;
}
}
Типичная ошибка здесь — скопировать из интернета универсальный блок PHP без try_files $uri =404;. Без него запрос вроде /uploads/foto.jpg/x.php может быть передан в FPM через PATH_INFO и выполнен, открывая путь к RCE, если злоумышленнику удастся записать файл в дерево. С =404 Nginx отклоняет запрос ещё до PHP.
Проверка после развёртывания: тест на 30 секунд
После перезагрузки веб-сервера запустите этот скрипт против реального домена. Каждая строка должна показывать OK. Любой FALHA (сбой) — это отдаваемый путь, которого не должно существовать:
#!/usr/bin/env bash
BASE="https://glpi.suaempresa.com"
# Todas as URLs abaixo DEVEM retornar 404 num webroot correto
for path in \
/config/config_db.php \
/src/ \
/vendor/ \
/files/_log/php-errors.log \
/install/install.php ; do
code=$(curl -sk -o /dev/null -w '%{http_code}' "${BASE}${path}")
if [ "$code" = "404" ]; then
echo "OK ${path} -> ${code}"
else
echo "FALHA ${path} -> ${code} (EXPOSTO)"
fi
done
При сопровождении мы закрепляем этот скрипт в чек-листе онбординга каждого нового клиента и запускаем его снова при каждой смене веб-сервера, обновлении или изменении обратного прокси — потому что правильный webroot не является постоянным состоянием: невнимательная миграция откатывает его за секунды.
Чек-лист для продуктива
- DocumentRoot / root указывает на
.../public, никогда на корень установки. - Удалите
install/install.php, как только установка/обновление завершится (сам GLPI показывает красное предупреждение, пока файл существует). - Принудительный HTTPS с редиректом 301 с порта 80 и включённым
Strict-Transport-Security. - Дополнительное усиление: вынесите
config/иfiles/за пределы веб-дерева черезGLPI_CONFIG_DIRиGLPI_VAR_DIR, оставив в webroot толькоpublic. - Запустите скрипт проверки и добейтесь 404 на всех путях.
- Проверьте панель безопасности самого GLPI (Настройки) после запуска — она сигнализирует о незакрытых вопросах, таких как присутствующий
install.php.
Исправить webroot — дело десяти минут; обнаружить, что пароль от базы три года был доступен для скачивания, обходится гораздо дороже. Если вы запускаете новый GLPI, переходите с Apache на Nginx или получаете в наследство среду без истории настроек, команда сопровождения GLPI от NexTool проверит webroot, усилит виртуальный хост и проведёт набор проверок в рамках онбординга. Обсудите с нами сопровождение GLPI.
Проверено командой NexTool Solutions.