Krótko: Aby zabronić wykonywania PHP w uploadach, ustalają Państwo najpierw wszystkie katalogi, do których PHP może zapisywać, a następnie blokują w każdym z nich wykonywanie skryptów w konfiguracji serwera, a nie przez .htaccess. W Nginx blokada musi zadziałać przed ogólnym blokiem PHP, w Apache należy do VirtualHost z AllowOverride None. security.limit_extensions = .php w puli FPM i test z nieszkodliwym plikiem kończą pracę.
Dlaczego katalogi uploadu liczą się właśnie teraz
Po krytycznej luce takiej jak PHP2Shell atakujący rzadko chcą wykonać kod tylko raz. Chcą wrócić, także gdy serwer zostanie później załatany. Najprostszą drogą jest webshell: mały plik PHP, zapisany w zapisywalnym katalogu i później wywołany przez przeglądarkę.
Poprawka takiego pliku nie usuwa. Kto od 20 października 2026 był w internecie niezałatany, powinien więc nie tylko zaktualizować, lecz także sprawdzić, czy gdzieś nie leżą nowe pliki PHP, i na przyszłość zapobiec ich wykonywaniu. Jak interpretować znaleziska, opisuje artykuł Pliki PHP w katalogu uploadu: jak właściwie interpretować znaleziska.
Blokada wykonywania uderza w drugi warunek. Nie zapobiega zapisaniu pliku, ale temu, by serwer traktował go jak program.
Najpierw inwentaryzacja: gdzie PHP może zapisywać?
Większość instrukcji wymienia tylko katalog uploadu. W praktyce aplikacja PHP zapisuje w znacznie większej liczbie miejsc. Każde z nich, osiągalne przez serwer WWW, wymaga blokady.
| Typ katalogu | Typowa zawartość | Dlaczego zapisywalny |
|---|---|---|
| Uploady, media | obrazy, PDF-y, załączniki | użytkownicy i redakcja przesyłają pliki |
| Pamięć podręczna | wygenerowane strony, miniatury | aplikacja przyspiesza dostarczanie |
| Tymczasowe, import, eksport | pliki CSV, stany pośrednie | interfejsy i wymiana danych |
| Kopie zapasowe w katalogu głównym | .zip, .sql |
rozszerzenia zabezpieczają „na miejscu” |
| Logi w katalogu głównym | logi błędów i debugowania | błędnie skonfigurowane aplikacje |
Które to katalogi na Państwa serwerze, pokazuje wyszukiwanie z uprawnieniami użytkownika środowiska uruchomieniowego. Niczego nie zmienia:
# Wszystkie katalogi w /var/www, do których użytkownik PHP może zapisywać
# (dopasować użytkownika: www-data, nginx, apache lub własny użytkownik puli)
sudo -u www-data find /var/www -type d -writable 2>/dev/null | sort
# Czy w tych katalogach leżą już pliki PHP?
sudo -u www-data find /var/www -type d -writable -print0 2>/dev/null \
| xargs -0 -I{} find {} -maxdepth 1 -type f -iname "*.ph*" 2>/dev/null
Jeśli pierwsze wyszukiwanie zwraca cały katalog główny strony, kod programu należy do użytkownika środowiska uruchomieniowego. Należy to wcześniej zmienić, jak opisuje poradnik Uprawnienia do plików na serwerze WWW. W przeciwnym razie nie ma katalogu, którego nie trzeba by blokować.
Zakaz wykonywania PHP w uploadach: Nginx
W Nginx rozstrzyga kolejność. Według dokumentacji Nginx wyrażenia regularne są sprawdzane w kolejności z pliku konfiguracyjnego, a wygrywa pierwsze trafienie. Jeśli ogólny blok location ~ \.php$ stoi przed Państwa blokadą, plik zostanie wykonany. Zwykła lokalizacja z prefiksem bez ^~ z tego samego powodu nie chroni.
Bezpieczne jest wyrażenie regularne dla wszystkich zablokowanych katalogów, stojące przed blokiem PHP. Obejmuje także rzadko używane rozszerzenia:
server {
# ... konfiguracja podstawowa ...
# 1. Blokada zapisywalnych katalogów, MUSI stać przed blokiem PHP
location ~* ^/(uploads|media|cache|tmp|export)/.*\.(php\d?|phtml|phar|pht|phps)$ {
deny all;
}
# 2. Ogólny blok PHP: przekazywać tylko istniejące pliki
location ~ \.php$ {
try_files $uri =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/seite1.sock;
}
}
Nazwy katalogów należy dopasować do swojej inwentaryzacji. W aplikacjach z jednym punktem wejścia droga przez listę dozwolonych jest jeszcze silniejsza; wzorzec pokazuje artykuł Bezpieczna konfiguracja Nginx dla PHP.
Apache i PHP-FPM: blokada we właściwym miejscu
W Apache blokada należy do konfiguracji serwera lub VirtualHost. .htaccess w katalogu uploadu jest nieodpowiedni, ponieważ może go zmienić każdy, kto tam zapisuje. Dla całego katalogu głównego strony obowiązuje więc AllowOverride None. Według dokumentacji Apache AllowOverride jest dozwolone tylko w blokach <Directory> bez wyrażeń regularnych, więc ustawia się je na katalogu głównym, a blokadę przez <DirectoryMatch> poniżej:
<Directory /var/www/seite1/public>
AllowOverride None
Require all granted
</Directory>
<DirectoryMatch "^/var/www/seite1/public/(uploads|media|cache|tmp|export)/">
<FilesMatch "\.(php\d?|phtml|phar|pht|phps)$">
Require all denied
</FilesMatch>
</DirectoryMatch>
Jeśli nadal działa mod_php, w tym samym bloku dodają Państwo php_admin_flag engine off. W PHP-FPM działa druga zapora w puli: security.limit_extensions określa, jakie rozszerzenia FPM w ogóle wykonuje. Według podręcznika PHP wartość domyślna to .php .phar; zaleca się ograniczenie do .php.
; w puli, np. /etc/php/8.3/fpm/pool.d/seite1.conf
security.limit_extensions = .php
Test z nieszkodliwym plikiem
Czy blokada działa, pokazuje tylko test. Proszę umieścić na chwilę w każdym zablokowanym katalogu plik, który tylko wypisuje tekst, wywołać go przez domenę, a potem usunąć.
- Sprawdzić konfigurację:
nginx -tlubapachectl configtest, następnie przeładować. - Utworzyć plik testowy, na przykład
uploads/sperrtest.phpo zawartości<?php echo 'ausgefuehrt';. - Wywołać za pomocą
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/uploads/sperrtest.php. Oczekiwane jest403. - Jeśli zamiast tego pojawi się
200i tekst „ausgefuehrt”, blokada nie działa. Sprawdzić konfigurację i kolejność. - Usunąć plik testowy i powtórzyć test dla każdego katalogu i każdej strony.
Jeśli podczas inwentaryzacji znajdą Państwo już nieznane pliki PHP w zapisywalnych katalogach, nie wolno niczego pochopnie usuwać. Najpierw zabezpieczyć stan i logi, ponieważ te pliki są materiałem dowodowym. Jak wtedy postępować, opisuje artykuł Rozpoznawanie i usuwanie webshelli.
Jak bezpiecznie budować funkcje uploadu w kodzie, ze sprawdzaniem typu i zapisem poza katalogiem głównym strony, opisuje poradnik Bezpieczna implementacja uploadu plików w PHP.
Wniosek: zapisywać tak, wykonywać nie
Blokada wykonywania w zapisywalnych katalogach jest jednym z najskuteczniejszych pojedynczych środków przeciw trwałym tylnym furtkom. Kosztuje kilka wierszy konfiguracji i w czysto zbudowanych aplikacjach niczego nie psuje. Decydujące są trzy rzeczy: pełna inwentaryzacja zamiast samego oczywistego katalogu uploadu, właściwa kolejność w Nginx lub właściwy poziom w Apache oraz prawdziwy test z nieszkodliwym plikiem.
Uzupełnione o oddzielnych właścicieli kodu programu, security.limit_extensions w puli i regularne wyszukiwanie nowych plików PHP, daje to stan, w którym zapisany plik jest wprawdzie uciążliwy, ale prawie niemożliwy do wykorzystania. Nie zastępuje to aktualizacji ani poszukiwania śladów, ale odbiera atakującym jedno z ich najważniejszych narzędzi.
Jak konkretnie pomaga PHP SOS?
- Inwentaryzacja: ustalamy wszystkie zapisywalne katalogi każdej strony i sprawdzamy je pod kątem obecnych plików PHP.
- Ustawienie blokad: wpisujemy blokady wykonywania w Nginx, Apache i PHP-FPM, we właściwej kolejności i bez zależności od
.htaccess. - Testowanie: każdą blokadę sprawdzamy nieszkodliwym plikiem i dokumentujemy wynik.
- Oczyszczanie: w razie znalezisk przejmujemy zabezpieczenie śladów i usuwanie malware.
Częste pytania
Czy .htaccess w katalogu uploadu wystarczy, aby zabronić PHP?
Niezawodnie nie. Kto może zapisywać w katalogu, może też zmienić lub zastąpić .htaccess. Blokada należy do konfiguracji serwera lub VirtualHost, a dla katalogu głównego strony powinno obowiązywać AllowOverride None.
Które katalogi muszę zablokować?
Wszystkie katalogi, do których użytkownik PHP może zapisywać i które są osiągalne przez serwer WWW. Poza katalogami uploadu są to często katalogi pamięci podręcznej, tymczasowe, importu, eksportu i kopii zapasowych. Należy je ustalić wyszukiwaniem jako użytkownik środowiska uruchomieniowego.
Czy blokada zepsuje moją stronę?
Zwykle nie, ponieważ w czystych aplikacjach w katalogach uploadu i pamięci podręcznej nie leżą skrypty wywoływane bezpośrednio. Pojedyncze rozszerzenia zapisują tam jednak pliki PHP. Dlatego po przestawieniu proszę przetestować najważniejsze funkcje.
Czy blokada chroni przed PHP2Shell?
Nie. PHP2Shell to luka w interpreterze PHP, którą zamyka wyłącznie aktualizacja i restart wszystkich procesów roboczych. Blokada zapobiega jednak późniejszemu wywołaniu zapisanego pliku przez przeglądarkę i użyciu go jako trwałej tylnej furtki.