Список заборони виконання PHP
PHP_DENY_PATHS блокує пряме виконання файлів .php, що відповідають налаштованим glob-шаблонам. Він спрямований проти поширеного класу вразливостей у застарілих PHP-застосунках: зловмисник завантажує PHP-файл до публічного каталогу, доступного для запису (/uploads, /cache, тимчасові каталоги для зміни розміру зображень), і звертається до нього за прямим URI, щоб отримати виконання коду.
Перевірка відбувається до будь-яких операцій із диском, тож заборонені шляхи повертають однакову відповідь незалежно від того, чи існує файл на диску. Зловмисник не отримує оракула існування, за допомогою якого міг би прощупувати каталоги завантажень.
Коли застосовується
У режимах прямого відображення, тобто в тих, де URI вказує безпосередньо на файл .php на диску:
| Режим маршрутизації | Чи враховується PHP_DENY_PATHS? |
|---|---|
Традиційний (без ENTRY_FILE) |
Так |
SPA (ENTRY_FILE=index.html) |
Так — SPA виконує наявні файли .php напряму, тому список заборони діє |
Фреймворк (ENTRY_FILE=index.php) |
Ні — видається попередження, налаштування ігнорується |
Worker (WORKER_MODE_ENABLED=true) |
Ні — видається попередження, налаштування ігнорується |
У режимі фреймворку кожен запит переписується на фронт-контролер, і довільні файли .php ніколи не виконуються напряму; список заборони лише зламав би маршрути застосунку, які випадково закінчуються на .php. У режимі Worker кожен нестатичний запит передається worker-скрипту, тож списку заборони просто нічого забороняти. Якщо задати PHP_DENY_PATHS в одному з цих режимів, під час запуску зʼявиться попередження, а перевірку буде вимкнено.
Список заборони охоплює й скрипти, до яких звертаються опосередковано: запит до /uploads/, який через пошук індексного файлу каталогу розвʼязався б у uploads/index.php, буде заборонено, якщо в списку є uploads/**, — із шаблонами порівнюється шлях розвʼязаного скрипту, а не лише URI запиту. Для таких заборон OXPHP_DENIED_PATH містить очищений URI запиту без кінцевої скісної риски (/uploads/ повідомляється як /uploads).
Налаштування
# Comma-separated glob patterns
PHP_DENY_PATHS="/uploads/**,/cache/**,/tmp/**"
# What to return on a match (default: 404)
PHP_DENY_FALLBACK="403"Тепер запит до /uploads/shell.php повертає 403, не звертаючись до диска. Запит до /uploads/image.png обслуговується як звичайно — список заборони впливає лише на виконання .php, а не на віддачу статичних файлів.
Синтаксис шаблонів
Шаблони порівнюються з очищеним URI (шляхом запиту, у якому сегменти .. і обходи через percent-кодування вже розвʼязано) за синтаксисом globset. Початковий / у кожному шаблоні необовʼязковий — /uploads/** і uploads/** рівнозначні.
| Шаблон | Відповідає | Не відповідає |
|---|---|---|
/uploads/** |
/uploads/x.php, /uploads/a/b/c.php, /uploads/shell.php/extra |
/uploads.php, /public/uploads/x.php |
/files/*.php |
/files/x.php |
/files/sub/x.php (одинарна * не переходить через /) |
/admin/legacy.php |
/admin/legacy.php |
/admin/legacy.php/x (PATH_INFO не охоплюється — див. нижче) |
/admin/legacy.php{,/**} |
/admin/legacy.php, /admin/legacy.php/x |
/admin/other.php |
/**/wp-config.php |
/wp-config.php, /site/wp-config.php |
/wp-config.txt |
Кілька шаблонів обʼєднуються через АБО — запит потрапляє під список заборони, якщо відповідає будь-якому з них.
Окремі файли та каталоги
Працює і те, і те. /uploads/** блокує ціле піддерево; /admin/legacy.php блокує один конкретний скрипт. Щоб заблокувати одну застарілу точку входу разом з усіма її викликами через PATH_INFO (/admin/legacy.php/foo), використовуйте форму з фігурними дужками: /admin/legacy.php{,/**}.
Чутливість до регістру
Зіставлення чутливе до регістру. На файлових системах, нечутливих до регістру (типові macOS HFS+/APFS, типова Windows NTFS, ext4 із casefold), запит до /uploads/Shell.PHP обійде шаблон /uploads/**/*.php. Якщо файли віддаються з такої файлової системи, використовуйте широкий шаблон каталогу на кшталт /uploads/** (він охоплює всі розширення) або приводьте назви завантажених файлів до нижнього регістру під час запису.
Режими резервної відповіді
PHP_DENY_FALLBACK визначає, що повертається у разі збігу. Він також відповідає на запити, заборонені правилом .oxphpdeny, тож одне налаштування охоплює обидва випадки.
HTTP-статус
Будь-яке значення в діапазоні 400–599 (за замовчуванням 404). Разом з ERROR_PAGES_DIR дає власне HTML-тіло відповіді:
PHP_DENY_PATHS="/uploads/**"
PHP_DENY_FALLBACK="403"
ERROR_PAGES_DIR="/var/www/errors" # serves errors/403.htmlPHP-скрипт
Шлях URI з префіксом / до резервного скрипту всередині DOCUMENT_ROOT:
PHP_DENY_PATHS="/uploads/**"
PHP_DENY_FALLBACK="/_security/denied.php"Скрипт перевіряється під час запуску — він має існувати, після канонізації шляху лежати всередині DOCUMENT_ROOT і сам не повинен відповідати PHP_DENY_PATHS (захист від зациклення; інакше запуск переривається). Перевірка на зациклення стосується лише PHP_DENY_PATHS: правило .oxphpdeny може охоплювати скрипт, і тоді він відповідає на прямий запит до самого себе. Скрипт виконується з двома додатковими ключами $_SERVER, що описують початковий запит:
Ключ $_SERVER |
Значення |
|---|---|
OXPHP_DENIED_PATH |
Початковий очищений URI з початковим / (у тій самій формі, що й PATH_INFO) |
OXPHP_DENIED_PATTERN |
Glob-шаблон, якому відповідав запит |
Для збігу з PHP_DENY_PATHS значення OXPHP_DENIED_PATTERN зберігається без початкового / (нормалізований glob), тоді як OXPHP_DENIED_PATH зберігає / з URI запиту. Якщо ви порівнюєте шлях із шаблоном, спершу виконайте ltrim($_SERVER['OXPHP_DENIED_PATH'], '/'), щоб обидва значення мали однакову форму. Для правила .oxphpdeny шаблон — це рядок у тому вигляді, в якому його записано у файлі (/composer.*, *.sql), з початковим /, якщо він у рядку є. Скрипт, що відповідає на обидва випадки, не повинен покладатися на жодну з цих форм.
Приклад пастки (honeypot):
<?php
// /_security/denied.php — runs in place of any matched .php request.
error_log(sprintf(
"PHP execution denied: path=%s pattern=%s ip=%s ua=%s",
$_SERVER['OXPHP_DENIED_PATH'] ?? '',
$_SERVER['OXPHP_DENIED_PATTERN'] ?? '',
$_SERVER['REMOTE_ADDR'] ?? '',
$_SERVER['HTTP_USER_AGENT'] ?? '-',
));
http_response_code(404);
echo "Not Found";Так ви можете вирішувати, яку відповідь дати, для кожного запиту окремо (повертати 404 зловмисникам, 403 автентифікованим адміністраторам, перенаправляти сканери на sinkhole), а не обмежуватися одним статичним статусом.
Жодного оракула існування
Обидва варіанти резервної відповіді, Status і Script, повертаються без звернення до файлової системи. Запит до /uploads/never-uploaded.php і запит до /uploads/actually-on-disk.php дають ідентичні відповіді — без різниці в часі та без різниці в тілі. Зловмисник, який шукає завантажені шели, не може скористатися списком заборони, щоб зʼясувати, які імена файлів існують.
Єдиний виняток — перевірка розвʼязаного шляху: вона неминуче виконується після розвʼязання маршруту, тому її заборони залежать від існування файлу. /uploads/ забороняється лише тоді, коли uploads/index.php справді є на диску; так само з шаблоном з однією зірочкою, наприклад /uploads/*.php, запит із PATH_INFO /uploads/shell.php/x забороняється лише тоді, коли існує uploads/shell.php (шаблону відповідає не повний URI, а розвʼязаний скрипт). Перевірка прямого URI — саме того шляху, яким прощупують зловмисники, — лишається вільною від оракула.
Спостережуваність
| Метрика | Опис |
|---|---|
oxphp_php_deny_total |
Лічильник, що збільшується за кожного запиту, який забороняє PHP_DENY_PATHS |
Кожна така заборона також записує лог tracing::info:
PHP execution denied by PHP_DENY_PATHS path=uploads/shell.php pattern=uploads/**Запит, який забороняє правило .oxphpdeny, отримує відповідь ще до перевірки PHP_DENY_PATHS, тож він не потрапляє ні в одне з цих місць. Він враховується в oxphp_path_deny_total і записується в лог на рівні debug.
Журнали доступу фіксують підсумковий статус (значення PHP_DENY_FALLBACK або http_response_code() резервного скрипту) — на рівні журналу доступу заборонені запити не відрізняються від звичайних. Щоб зʼясувати причину сплесків, зіставляйте їх із метрикою або структурованим логом.
Продуктивність
Зіставлення — це пошук у globset::GlobSet, зазвичай один SIMD-прохід байтами URI. Збіг також оминає кеш маршрутів (заборонені URI надходять від зловмисника, що масово перебирає шляхи, і їхня кардинальність фактично необмежена; кешування дозволило б зловмиснику витісняти легітимні записи з LRU). Після прогріву обидва шляхи — і збіг, і промах — обходяться без виділень памʼяті.
Обмеження
Чого ця функція не робить:
- Обхід через PATH_INFO для шаблонів із конкретним файлом. Шаблон
/admin/legacy.phpне відповідає/admin/legacy.php/extra. Щоб охопити обидва випадки, використовуйте/admin/legacy.php{,/**}або шаблон каталогу. - Зіставлення чутливе до регістру (див. Чутливість до регістру вище).
- Без регулярних виразів. Шаблони — лише glob: заякорені, з операторами
*/**/?/[abc]/{a,b}. Замість(a|b)використовуйте кілька шаблонів через кому. - Не впливає на
include/require/eval. Список заборони керує лише виконанням за прямим URI. Вразливий скрипт, що робитьinclude $_GET['page'], усе одно може завантажити PHP з будь-якого місця, доступного серверу для читання.
Застарілий псевдонім
Застаріла змінна PHP_DENY_DIRS приймається як застарілий псевдонім і спричиняє попередження під час запуску:
WARN PHP_DENY_DIRS is deprecated, use PHP_DENY_PATHS instead — the alias will be removed in a future releaseЯкщо задано обидві, перевагу має PHP_DENY_PATHS, а про PHP_DENY_DIRS повідомляється, що її проігноровано. Значення не обʼєднуються.
Промпти для вашого агента
Ці промпти розраховані на агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.
Захисти мій застарілий PHP-застосунок від виконання завантажених шелів за допомогою `PHP_DENY_PATHS`. Прочитай сторінку `security/php-deny` через MCP-сервер документації OxPHP.
Зʼясуй, у якому режимі маршрутизації працює цей репозиторій, адже в режимах Framework і Worker список заборони ігнорується, потім знайди його каталоги, доступні для запису, і каталоги завантажень та дай мені glob-шаблони й значення резервної відповіді, щоб їх заборонити.Див. також
- Правила заборони шляхів — заборона віддавати будь-який шлях, статичний чи PHP, у будь-якому режимі маршрутизації
- Маршрутизація — режими маршрутизації та безпека шляхів
- Сторінки помилок — власні HTML-тіла для резервних відповідей зі статусом
- Довідник із налаштування — повний перелік змінних середовища
- Метрики —
oxphp_php_deny_totalта інші