Krótko: Plik .env chronisz, nie umieszczając go w ogóle w katalogu dostępnym publicznie: webroot wskazuje na podkatalog taki jak public/, a konfiguracja leży poziom wyżej. Dodatkowo serwer WWW blokuje wszystkie pliki z kropką na początku, plik jest czytelny tylko dla właściciela i procesu PHP i nigdy nie trafia do repozytorium Git. Po włamaniu wszystkie zawarte w nim dane dostępowe uznaje się za znane i wymienia.
Plik z kluczami do domu
Nowoczesne projekty PHP chętnie przechowują swoje sekrety w pliku o nazwie .env: hasło do bazy danych, dostęp do serwera pocztowego, klucze API dla usług płatności i wysyłki, klucz, którym aplikacja podpisuje ciasteczka. Frameworki takie jak Laravel i Symfony stosują ten wzorzec, wiele własnych rozwiązań go przejęło.
Pomysł jest dobry: konfiguracja i kod są oddzielone, a każde środowisko dostaje własne wartości. Niebezpiecznie robi się, gdy plik trafi do niewłaściwego katalogu. Jeśli leży w webroot, a serwer WWW go nie blokuje, wystarczy jedno wywołanie w przeglądarce, aby odczytać wszystkie dostępy naraz.
Dochodzą warianty o innych nazwach: .env.local, .env.production, config.local.php lub pliki z danymi dostępowymi do usług przechowywania. To, co tu opisano dla .env, dotyczy wszystkich plików, w których znajdują się sekrety.
Takie pliki są nieustannie wyszukiwane przez zautomatyzowane skanery. To nie specjalność pojedynczych atakujących, lecz tło internetu, podobnie jak masowe skanowanie w poszukiwaniu niezałatanych wersji PHP.
Gdzie powinien leżeć plik .env
Najskuteczniejszy środek jest strukturalny: serwer WWW może wydawać tylko ten katalog, który ma być naprawdę publiczny. W Laravelu i Symfony jest to katalog public/. Wszystko inne, czyli kod, konfiguracja, logi i .env, leży wyżej i przez HTTP w ogóle nie jest adresowalne.
/srv/www/shop/
├── .env ← niedostępny
├── config/
├── src/
├── vendor/
└── public/ ← jedyny webroot
├── index.php
└── assets/
W praktyce webroot mimo to często wskazuje na sam katalog projektu, na przykład dlatego, że panel hostingu tak narzuca albo że przy przeprowadzce nie dostosowano ścieżki. Sprawdź więc w konfiguracji serwera WWW lub w panelu, na co wskazuje root albo DocumentRoot.
Ochrona pliku .env: blokada w serwerze WWW jako druga linia
Jeśli nie da się zmienić webroot albo chcesz dodatkowego zabezpieczenia, zablokuj ukryte pliki w serwerze WWW. Katalog .well-known musi przy tym pozostać dostępny, ponieważ służy między innymi do wystawiania certyfikatów.
# Zablokować wszystkie pliki i katalogi z kropką na początku, z wyjątkiem .well-known
location ~ /\.(?!well-known/) {
deny all;
return 404;
}
# Zablokować pliki z kropką na początku
<FilesMatch "^\.">
Require all denied
</FilesMatch>
Odpowiedź 404 zamiast 403 nie zdradza, że plik istnieje. To, czy blokada działa, sprawdzasz z zewnątrz:
# Oczekiwane: 403 lub 404, nigdy 200
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env.example
curl -s -o /dev/null -w "%{http_code}\n" https://www.example.de/.env.bak
Dwa ostatnie wiersze nie są przypadkowe: kopie takie jak .env.bak lub .env.old powstają przy pracach serwisowych i często są zapominane. Więcej o takich pozostałościach znajduje się w artykule Pliki kopii zapasowych w webroot.
Uprawnienia, Git i postępowanie z kopiami
Także poza webroot obowiązuje zasada najmniejszych uprawnień. Sprawdzony podział:
- Właścicielem jest użytkownik wdrożeniowy, a nie proces PHP.
- Grupą jest grupa puli PHP-FPM, która może tylko czytać; uprawnienia
640. - Brak dostępu do zapisu dla procesu PHP. Aplikacja nie musi móc zmieniać własnych danych dostępowych.
- Brak prawdziwych wartości w repozytorium: Wprowadzany jest tylko
.env.examplez wartościami zastępczymi, prawdziwy plik znajduje się w.gitignore. - Oddzielne wartości dla każdego środowiska: Programowanie, staging i produkcja używają różnych dostępów, aby wyciek w środowisku testowym nie otworzył produkcji.
chown deploy:shop-php /srv/www/shop/.env
chmod 640 /srv/www/shop/.env
# Sprawdzić, czy plik kiedykolwiek został zatwierdzony
git log --all --oneline -- .env
Zwróć też uwagę na strony błędów. Frameworki w trybie debugowania częściowo wyświetlają zmienne środowiskowe i wartości konfiguracji; w systemie produkcyjnym ten tryb musi być wyłączony. Sprawdzaj to po każdym wdrożeniu, ponieważ błędna wartość w samym .env może go ponownie włączyć.
Jeśli ostatnie polecenie zwraca trafienia, dane dostępowe znajdują się w historii Git i uchodzą za ujawnione, nawet jeśli pliku dziś nie ma. Dlaczego jest to szczególnie delikatne, gdy katalog .git leży na serwerze, opisuje artykuł Publiczny katalog .git: niedoceniany wyciek danych. Podstawy uprawnień wyjaśnia poradnik Uprawnienia do plików na serwerze WWW.
Pliki testowe z phpinfo() wyświetlają zmienne środowiskowe. Kto przekazuje sekrety jako zmienne środowiskowe, zdradza je przez zapomniany plik testowy. Tło opisano w artykule Pliki phpinfo(): dlaczego muszą zniknąć.
Po włamaniu: wymiana danych dostępowych
Żadna blokada nie chroni przed atakującym, który już wykonuje kod na serwerze. Proces PHP musi móc czytać swoją konfigurację, więc wstrzyknięty kod w tym samym procesie też może. Po PHP2Shell obowiązuje więc: jeśli nie można wykluczyć włamania, wszystkie dane dostępowe na serwerze należy traktować jako znane.
Uporządkowana wymiana obejmuje hasła do baz danych, dostępy do poczty i SMTP, klucze API usług zewnętrznych, klucze podpisujące aplikacji oraz dostępy do usług przechowywania. Kolejność i moment powinny być zaplanowane: kto najpierw wymienia, a potem czyści serwer, może od razu przekazać atakującemu nowe wartości.
Szkody wynikające z wycieku ograniczasz już wcześniej, sprawiając, że każdy sekret może tylko tyle, ile potrzeba. Użytkownik bazy danych strony nie potrzebuje uprawnień do innych baz. Klucze API usług zewnętrznych można u wielu dostawców ograniczyć do pojedynczych funkcji lub adresów nadawców, na przykład zakładanie przesyłek, ale bez zlecania wypłat. A każda aplikacja otrzymuje własne dostępy, zamiast aby kilka stron dzieliło to samo konto pocztowe lub ten sam klucz. Wtedy wyciek dotyczy jasno wyodrębnionego obszaru, a wymiana pozostaje przejrzysta.
W środowiskach kontenerowych plik .env nie należy ani do obrazu, ani do publicznie dostępnego rejestru. Wyklucz go przez .dockerignore i przekazuj sekrety dopiero w czasie działania za pomocą mechanizmów platformy.
Na dłuższą metę warto zwrócić uwagę na centralne magazyny sekretów, jakie zaleca OWASP: sekrety w jednym miejscu, z dziennikiem dostępu, regularną wymianą i możliwie krótką ważnością.
Jak konkretnie pomaga PHP SOS?
- Sprawdzamy z zewnątrz, czy pliki konfiguracyjne, kopie i ukryte pliki Państwa stron są dostępne do pobrania.
- Przestawiamy webroot, blokady serwera WWW i uprawnienia do plików tak, aby sekrety nie były już osiągalne przez HTTP.
- Sprawdzamy repozytoria i wdrożenia pod kątem zatwierdzonych danych dostępowych.
- Po incydencie planujemy i wspieramy wymianę wszystkich dotkniętych dostępów we właściwej kolejności.
Te prace są częścią naszego utwardzania serwerów PHP.
Częste pytania
Czy plik .env w webroot jest automatycznie publiczny?
Nie automatycznie, ale często. To, czy można go pobrać, zależy od konfiguracji serwera WWW. Wiele konfiguracji domyślnych chroni tylko pliki .htaccess, a nie inne pliki z kropką na początku. Test za pomocą curl wyjaśnia sprawę.
Czy zmienne środowiskowe są bezpieczniejsze niż plik .env?
Nie z zasady. OWASP zwraca uwagę, że zmienne środowiskowe są czytelne dla wszystkich procesów użytkownika i mogą pojawiać się w logach lub zrzutach pamięci. Unikają jednak pliku w webroot. Lepsze są centralne magazyny sekretów, jeśli środowisko je obsługuje.
Co zrobić, jeśli plik .env był dostępny do pobrania?
Wszystkie zawarte w nim dane dostępowe uznać za znane i wymienić: baza danych, poczta, API, usługi płatnicze, klucze aplikacji. Następnie sprawdzić w logach dostępu, czy plik został pobrany, a w przypadku danych osobowych włączyć inspektora ochrony danych.
Czy plik .env należy do repozytorium Git?
Nie. Do repozytorium należy co najwyżej szablon bez prawdziwych wartości, na przykład .env.example. Prawdziwy plik jest wykluczany przez .gitignore i udostępniany na serwerze oddzielnie.
Czy phpinfo() może zdradzić dane dostępowe?
Tak. phpinfo() wyświetla między innymi zmienne środowiskowe i zmienne serwera. Jeśli znajdują się tam sekrety, zostaną wyświetlone. Pliki testowe z phpinfo() nie mogą więc nigdy trafić na dostępny system.