Lista blokowania wykonywania PHP
PHP_DENY_PATHS blokuje bezpośrednie wykonywanie plików .php pasujących do skonfigurowanych wzorców glob. Celuje w powracającą klasę podatności w starszych aplikacjach PHP: atakujący przesyła plik PHP do zapisywalnego katalogu publicznego (/uploads, /cache, katalogi tymczasowe do zmiany rozmiaru obrazów) i dociera do niego przez bezpośrednie URI, aby uzyskać wykonanie kodu.
Sprawdzenie odbywa się przed jakimikolwiek operacjami I/O na dysku, więc zablokowane ścieżki zwracają tę samą odpowiedź niezależnie od tego, czy plik istnieje na dysku, czy nie. Nie istnieje żadna wyrocznia istnienia, dzięki której atakujący mogliby badać katalogi przesyłania.
Kiedy ma zastosowanie
Tryby bezpośredniego mapowania, czyli tryby, w których URI odwzorowuje się wprost na plik .php na dysku:
| Tryb routingu | PHP_DENY_PATHS respektowane? |
|---|---|
Tradycyjny (bez ENTRY_FILE) |
Tak |
SPA (ENTRY_FILE=index.html) |
Tak — SPA wykonuje istniejące pliki .php bezpośrednio, więc lista blokowania ma zastosowanie |
Framework (ENTRY_FILE=index.php) |
Nie — ostrzeżenie i zignorowanie |
Worker (WORKER_MODE_ENABLED=true) |
Nie — ostrzeżenie i zignorowanie |
W trybie Framework każde żądanie jest przepisywane do kontrolera frontowego i dowolne pliki .php nigdy nie są wykonywane bezpośrednio; lista blokowania psułaby jedynie trasy aplikacji, które przypadkiem kończą się na .php. W trybie Worker każde niestatyczne żądanie jest kierowane do skryptu workera, więc lista blokowania nie ma czego blokować. Ustawienie PHP_DENY_PATHS w którymkolwiek z tych trybów powoduje ostrzeżenie przy starcie i wyłącza sprawdzenie.
Lista blokowania obejmuje również skrypty osiągane pośrednio: żądanie do /uploads/, które odwzorowałoby się na uploads/index.php poprzez wyszukiwanie indeksu katalogu, jest blokowane, gdy uploads/** znajduje się na liście — do wzorców dopasowywana jest rozwiązana ścieżka skryptu, a nie tylko URI żądania. W przypadku takich blokad OXPHP_DENIED_PATH przenosi oczyszczone URI żądania bez końcowego ukośnika (/uploads/ jest raportowane jako /uploads).
Konfiguracja
# Comma-separated glob patterns
PHP_DENY_PATHS="/uploads/**,/cache/**,/tmp/**"
# What to return on a match (default: 404)
PHP_DENY_FALLBACK="403"Żądanie do /uploads/shell.php zwraca teraz 403 bez dotykania dysku. Żądanie do /uploads/image.png jest obsługiwane normalnie — lista blokowania wpływa wyłącznie na wykonywanie .php, nigdy na serwowanie plików statycznych.
Składnia wzorców
Wzorce są dopasowywane do oczyszczonego URI (ścieżki żądania z już rozwiązanymi segmentami .. i obejściami zakodowanymi procentowo) przy użyciu składni globset. Wiodący / w każdym wzorcu jest opcjonalny — /uploads/** i uploads/** są równoważne.
| Wzorzec | Pasuje | Nie pasuje |
|---|---|---|
/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 (pojedyncza * nie przekracza /) |
/admin/legacy.php |
/admin/legacy.php |
/admin/legacy.php/x (PATH_INFO nieobjęte — patrz niżej) |
/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 |
Wiele wzorców łączy się operatorem OR — żądanie pasuje do listy blokowania, jeśli pasuje do dowolnego wzorca.
Pojedyncze pliki a katalogi
Oba działają. /uploads/** blokuje całe poddrzewo; /admin/legacy.php blokuje jeden konkretny skrypt. Aby zablokować pojedynczy starszy punkt wejścia i wszelkie jego wywołania z PATH_INFO (/admin/legacy.php/foo), użyj formy z nawiasami klamrowymi: /admin/legacy.php{,/**}.
Rozróżnianie wielkości liter
Dopasowywanie rozróżnia wielkość liter. W systemach plików nierozróżniających wielkości liter (domyślne macOS HFS+/APFS, domyślne Windows NTFS, ext4 z casefold) żądanie do /uploads/Shell.PHP obeszłoby wzorzec /uploads/**/*.php. Podczas serwowania z takiego systemu plików użyj szerokiego wzorca katalogu, takiego jak /uploads/** (który pasuje do wszystkich rozszerzeń), albo normalizuj nazwy przesyłanych plików do małych liter w momencie zapisu.
Tryby awaryjne
PHP_DENY_FALLBACK kontroluje, co jest zwracane przy dopasowaniu.
Status HTTP
Dowolna wartość z zakresu 400–599 (domyślnie 404). Łączy się z ERROR_PAGES_DIR w celu uzyskania niestandardowego ciała HTML:
PHP_DENY_PATHS="/uploads/**"
PHP_DENY_FALLBACK="403"
ERROR_PAGES_DIR="/var/www/errors" # serves errors/403.htmlSkrypt PHP
Ścieżka URI poprzedzona /, wskazująca skrypt awaryjny wewnątrz DOCUMENT_ROOT:
PHP_DENY_PATHS="/uploads/**"
PHP_DENY_FALLBACK="/_security/denied.php"Skrypt jest walidowany przy starcie — musi istnieć, kanonikalizować się wewnątrz DOCUMENT_ROOT i sam nie może pasować do PHP_DENY_PATHS (zapobieganie pętli; w przeciwnym razie start jest przerywany). Skrypt działa z dwoma dodatkowymi kluczami $_SERVER identyfikującymi oryginalne żądanie:
Klucz $_SERVER |
Wartość |
|---|---|
OXPHP_DENIED_PATH |
Oryginalne oczyszczone URI, z wiodącym / (ta sama forma co PATH_INFO) |
OXPHP_DENIED_PATTERN |
Wzorzec glob, który dopasował |
OXPHP_DENIED_PATTERN jest przechowywany bez wiodącego / (znormalizowany dla glob), podczas gdy OXPHP_DENIED_PATH zachowuje / z URI żądania. Jeśli porównujesz ścieżkę ze wzorcem, najpierw zastosuj ltrim($_SERVER['OXPHP_DENIED_PATH'], '/'), aby obie były w tej samej formie.
Przykładowa pułapka 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";Pozwala to zdecydować o odpowiedzi dla każdego żądania z osobna (zwrócić 404 atakującym, 403 uwierzytelnionym administratorom, przekierować skanery sondujące do sinkhole) zamiast ograniczać się do jednego statycznego statusu.
Brak wyroczni istnienia
Zarówno awaryjny Status, jak i Script są zwracane bez dotykania systemu plików. Żądanie do /uploads/never-uploaded.php oraz żądanie do /uploads/actually-on-disk.php dają identyczne odpowiedzi — brak różnicy w czasie, brak różnicy w ciele. Atakujący skanujący w poszukiwaniu przesłanych powłok nie może użyć listy blokowania do wyliczenia, które nazwy plików istnieją.
Ekran rozwiązanej ścieżki jest jedynym wyjątkiem: z konieczności działa po rozwiązaniu trasy, więc jego blokady zależą od istnienia. /uploads/ jest blokowane tylko wtedy, gdy uploads/index.php faktycznie istnieje na dysku; podobnie, przy wzorcu z pojedynczą gwiazdką, takim jak /uploads/*.php, żądanie z PATH_INFO /uploads/shell.php/x jest blokowane tylko wtedy, gdy uploads/shell.php istnieje (pełne URI nie pasuje do wzorca — pasuje rozwiązany skrypt). Ekran bezpośredniego URI — ten, który atakujący sondują — pozostaje wolny od wyroczni.
Obserwowalność
| Metryka | Opis |
|---|---|
oxphp_php_deny_total |
Licznik zwiększany przy każdym zablokowanym żądaniu |
Każda blokada generuje również log tracing::info:
PHP execution denied by PHP_DENY_PATHS path=uploads/shell.php pattern=uploads/**Logi dostępu rejestrują wynikowy status (wartość PHP_DENY_FALLBACK lub http_response_code() skryptu awaryjnego) — zablokowane żądania nie są odróżniane od normalnych żądań na poziomie logu dostępu. Skoreluj z metryką lub logiem strukturalnym, aby przypisać skoki.
Wydajność
Dopasowywanie to wyszukiwanie globset::GlobSet — zazwyczaj pojedynczy przebieg SIMD po bajtach URI. Trafienie omija również pamięć podręczną tras (zablokowane URI pochodzą z kontrolowanego przez atakującego rozpylania o praktycznie nieograniczonej liczności; ich buforowanie pozwoliłoby atakującemu wypierać legalne wpisy z LRU). Zarówno ścieżka trafienia, jak i chybienia jest wolna od alokacji po rozgrzaniu.
Ograniczenia
Czego ta funkcja nie robi:
- Obejście PATH_INFO dla wzorców literalnych plików. Wzorzec
/admin/legacy.phpnie pasuje do/admin/legacy.php/extra. Użyj/admin/legacy.php{,/**}, aby objąć oba, lub użyj wzorca katalogu. - Dopasowywanie rozróżniające wielkość liter (patrz Rozróżnianie wielkości liter powyżej).
- Brak wyrażeń regularnych. Wzorce to wyłącznie globy — zakotwiczone, z operatorami
*/**/?/[abc]/{a,b}. Zamiast(a|b)użyj wielu wzorców oddzielonych przecinkami. - Brak wpływu na
include/require/eval. Lista blokowania reguluje wyłącznie wykonywanie przez bezpośrednie URI. Podatny skrypt wykonującyinclude $_GET['page']nadal może ładować PHP z dowolnego miejsca odczytywalnego przez serwer.
Przestarzały alias
Starsza zmienna PHP_DENY_DIRS jest akceptowana jako przestarzały alias i generuje ostrzeżenie przy starcie:
WARN PHP_DENY_DIRS is deprecated, use PHP_DENY_PATHS instead — the alias will be removed in a future releaseGdy obie są ustawione, PHP_DENY_PATHS wygrywa, a PHP_DENY_DIRS jest raportowane jako zignorowane. Wartości nie są scalane.
Zobacz też
- Routing — tryby routingu i bezpieczeństwo ścieżek
- Strony błędów — niestandardowe ciała HTML dla odpowiedzi awaryjnych ze statusem
- Dokumentacja konfiguracji — pełna lista zmiennych środowiskowych
- Metryki —
oxphp_php_deny_totali pokrewne