Ekspozycja .env
ważność krytycznamierzonysposób exposure
Czy plik .env jest dostępny publicznie. Zawiera klucze API i hasła do bazy — krytyczny wyciek.
WIKI
artykuł z 16.09.2026 · źródeł 10Ekspozycja .env to parametr monitoringu stron internetowych, który sprawdza, czy pod adresem /.env w katalogu głównym witryny serwer oddaje zawartość pliku, zamiast odmówić dostępu albo zgłosić jego brak. Pomiar kończy się jednym z trzech wyników: plik dostępny publicznie, plik niedostępny albo nie udało się uzyskać odpowiedzi. Plik .env jest plikiem tekstowym ze zmiennymi środowiskowymi, które biblioteka dotenv wczytuje do środowiska procesu aplikacji [2].
Jak działa
Metodyka Twelve-Factor App nazywa konfiguracją wszystko, co zmienia się między wdrożeniami aplikacji, w tym dane dostępowe do usług zewnętrznych, i zaleca trzymanie jej w zmiennych środowiskowych [1]. Jej test poprawności brzmi: kod źródłowy dałoby się w każdej chwili upublicznić bez ujawnienia żadnych danych dostępowych [1]. Plik .env przenosi tę zasadę na dysk. Dokumentacja dotenv każe utworzyć go w katalogu głównym projektu i zapisywać w nim wiersze w postaci NAZWA="wartość", na przykład klucz API [2]. Na pytanie, czy plik trafia do repozytorium, odpowiada „nie”, chyba że jest zaszyfrowany [2].
Problem zaczyna się wtedy, gdy katalog główny projektu jest jednocześnie katalogiem, z którego serwer WWW podaje pliki. W nginx ścieżka pliku powstaje przez proste dopisanie adresu z żądania do wartości dyrektywy root [7], więc żądanie /.env wskazuje wprost na plik w tym katalogu. Plik, którego rozszerzenia nie obsługuje żaden interpreter, serwer zwykle zwraca jako tekst, zamiast go wykonać. Przewodnik testowania OWASP pokazuje to na kopii login.asp.old, która oddaje kod źródłowy strony [4].
Domyślny plik konfiguracyjny Apache HTTP Server zawiera blok <Files ".ht*"> z dyrektywą Require all denied, opisany jako ochrona plików .htaccess i .htpasswd przed odczytem przez klientów [6]. Wzorzec obejmuje nazwy zaczynające się od .ht, więc .env do niego nie pasuje. W nginx reguły z wyrażeniem regularnym oznacza się modyfikatorem ~, sprawdza w kolejności wystąpienia w konfiguracji, a pierwsze dopasowanie kończy wyszukiwanie [7]. Blok odmowy dla ścieżek z kropką, np. location ~ /\., działa więc tylko wtedy, gdy stoi przed innymi blokami wyrażeń regularnych, które mogłyby przechwycić to samo żądanie [7].
Sam kod 200 nie przesądza o wycieku. Według OWASP odpowiedź 200 zwykle oznacza, że zasób istnieje, o ile serwer nie podaje własnej strony „nie znaleziono” z kodem 200 [4]. Rozstrzyga treść: plik .env składa się z wierszy zmiennych, a nie z dokumentu HTML.
Standardy i specyfikacje
- The Twelve-Factor App, rozdział „Config” oddziela konfigurację od kodu i zauważa, że pliki konfiguracyjne trzymane poza repozytorium łatwo przez pomyłkę do niego dodać [1].
- OWASP Secrets Management Cheat Sheet stwierdza, że wiele organizacji trzyma sekrety otwartym tekstem w kodzie i plikach konfiguracyjnych [3], a zmienne środowiskowe są zwykle dostępne dla wszystkich procesów i mogą trafić do logów [3]. Po wycieku zaleca kolejno: natychmiastowe unieważnienie ujawnionych kluczy, szybkie wygenerowanie nowych, usunięcie sekretów z narażonego systemu oraz dostęp zespołu reagowania do historii sekretu [3].
- OWASP WSTG-CONF-04 opisuje pliki konfiguracyjne, logi i kopie zapasowe pozostawione w katalogach dostępnych dla serwera WWW jako źródło wycieku, w tym danych dostępowych do bazy [4].
- CWE-538 („Insertion of Sensitive Information into Externally-Accessible File or Directory”) to słabość polegająca na umieszczeniu informacji wrażliwych w pliku lub katalogu dostępnym dla podmiotów uprawnionych do tego pliku, ale nie do zapisanych w nim informacji [5]. W widoku badawczym MITRE jest potomkiem CWE-200, „Exposure of Sensitive Information to an Unauthorized Actor” [5].
Zagrożenia i skutki
Masowe skanowanie. W sierpniu 2024 roku Unit 42 opisał kampanię wymuszeń, której infrastruktura skanowała ponad 230 milionów unikalnych celów w poszukiwaniu odsłoniętych plików środowiskowych [8]. Funkcja uruchamiana w chmurze przechodziła po liście domen i dla każdej wysyłała żądanie o plik /.env [8]. Atakujący zebrali pliki .env z co najmniej 110 000 domen i ponad 90 000 unikalnych zmiennych, z czego 7 000 należało do usług chmurowych, a 1 500 do kont w mediach społecznościowych [8].
Nadużycie kluczy. Z przejętymi kluczami chmury atakujący kopiowali zawartość magazynów danych, kasowali ją i zostawiali w pustym magazynie żądanie okupu, bez szyfrowania danych [8]. Unit 42 podkreśla, że przyczyną była błędna konfiguracja po stronie ofiar, a nie podatność dostawców chmury [8].
Usunięcie pliku nie kończy incydentu. Klucz, który był choć raz dostępny publicznie, OWASP każe unieważnić i zastąpić nowym [3]. Skasowanie pliku zamyka dostęp do niego, ale nie odbiera kopii temu, kto już ją pobrał.
Fałszywe trafienia. Strona błędu podawana z kodem 200 wygląda w samym kodzie odpowiedzi jak istniejący plik [4]. Pomiar oparty wyłącznie na kodzie statusu myli wtedy zwykłą stronę HTML z wyciekiem.
Przykłady
Klasyczna instalacja WordPressa trzyma dane połączenia z bazą w pliku wp-config.php w katalogu głównym instalacji [10]. Plik .env pojawia się przy nakładkach i szkieletach projektów, takich jak Bedrock, który zarządza ustawieniami przez pliki .env i zmienne środowiskowe, a jako katalog publiczny wskazuje podkatalog web, żeby pliki wrażliwe zostały poza drzewem serwera [9].
| Sytuacja | Odpowiedź na GET /.env | Znaczenie |
|---|---|---|
.env w katalogu głównym projektu, który jest też katalogiem serwera [2] [7] | 200 z wierszami zmiennych | wyciek: klucze do unieważnienia [3] |
Bedrock: katalog publiczny web, konfiguracja poza nim [9] | brak pliku pod tym adresem | plik poza drzewem serwera |
Apache z domyślną regułą dla .ht* [6] | zależy od położenia pliku | reguła nie obejmuje .env |
| nginx z blokiem odmowy dla ścieżek z kropką przed innymi wyrażeniami [7] | odmowa dostępu | plik istnieje, ale nie jest podawany |
| Serwer z własną stroną „nie znaleziono” podawaną z kodem 200 [4] | 200 z dokumentem HTML | fałszywe trafienie, treść nie jest plikiem .env |
| Masowy skaner z listą domen [8] | jedno żądanie o /.env na domenę | odsłonięty plik zostaje zebrany automatycznie |
- [1] The Twelve-Factor App — III. Config · Adam Wiggins / 12factor.net
- [2] dotenv — README · motdotla/dotenv
- [3] Secrets Management Cheat Sheet · OWASP
- [4] WSTG-CONF-04 — Review Old Backup and Unreferenced Files for Sensitive Information · OWASP Web Security Testing Guide
- [5] CWE-538: Insertion of Sensitive Information into Externally-Accessible File or Directory · MITRE
- [6] httpd.conf.in — domyślna konfiguracja Apache HTTP Server · Apache Software Foundation
- [7] Module ngx_http_core_module — dyrektywy location i root · nginx
- [8] Leaked Environment Variables Allow Large-Scale Extortion Operation in Cloud Environments · Palo Alto Networks Unit 42
- [9] Bedrock — WordPress boilerplate · Roots
- [10] wp-config.php — Advanced Administration Handbook · WordPress.org
STATS
wyniki z systemu · tylko liczby zbiorczeTen parametr nie zapisuje odczytu per strona — przebieg zostawia tylko ustalenia, więc liczby sprawdzonych stron i wyników „ok” system nie ma. Pojawią się po wdrożeniu zapisu przebiegów per strona per parametr.
Ten parametr nie otworzył jeszcze żadnego ustalenia w bazie LAB247. Pusto nie znaczy „czysto” — znaczy, że nic nie przekroczyło reguły.
- Przebiegów
- 33sposób exposure
- W 30 dni
- 33przebiegów
- Ostatni
- 19.09.2026start przebiegu
- Stron w przebiegach
- —33 przebiegów bez liczby stron
CARD
karta z kodu CREATO_PING, odczyt 16.09.2026| Obszar i ważność | Bezpieczeństwo · krytyczna (B05) |
|---|---|
| Workflow i częstotliwość | creato_ping_security_v2 · P3 działa na LAB247cron co 24 h, T1–T3; T4 raz po anomalii · Dokładny skan. dns_integrity porównuje NS, MX, SPF i DMARC z wzorcem z poprzedniego przebiegu. |
| Sonda i helper | sposób exposure · CREATO_PING/helpers/exposure_check.py |
| Testy |
|
| Typ wyniku | 0 albo 1 ustalenie exposure_env; zamiast wszystkich sond ekspozycji jedno exposure_scan_unreliable, gdy serwer odpowiada 200 na losową ścieżkę |
| Jednostka | brak — wynik jakościowy; w dowodzie ścieżka, kod HTTP i długość treści, bez samej treści pliku |
| Próg i reguła | Timeout odpowiedzi 15 s, połączenia 5 s. Reguła: kod 200 + niepusta treść = exposure_env. 403, 404, przekierowanie, błąd sieci i timeout = brak ustalenia. |
| Gdzie leżą pokrętła | CREATO_PING/helpers/exposure_check.py → PROBES (/.env, reguła 200+nonempty), _EXPOSURE_CAP, _PROBE_CONCURRENCY, _soft_200CREATO_PING/helpers/config.py → TIMEOUTS["exposure"], TIMEOUTS["security_connect"]CREATO_PING/helpers/security_runner.py → _TASK_SEVERITIESCREATO_PING/config/incident_policy.toml → [finding] exposure_env |
| Retencja odczytów | Odczytu per strona nie zapisujemy — zostaje tylko ustalenie w tabeli findings (otwarcie, zamknięcie, dowód). Replay po surowych odczytach dziś niemożliwy. |
| Znane rozbieżności |
|
| Karta przepisana z | CREATO_PING/helpers/exposure_check.py · CREATO_PING/helpers/config.py · CREATO_PING/config/incident_policy.toml · CREATO_PING/helpers/security_runner.py · CREATO_PING/helpers/clickup_writer_v2.py · CREATO_PING/workflows/_prod/creato_ping_security_v2.json · odczyt 16.09.2026 |
Powaga pilna w helperze i w polityce, pewność ewidentna, skutek PODATNA, gate=true — subtask z priorytetem pilnym, status strony przeliczony na PODATNA. Kolejne przebiegi odświeżają opis i datę. Helper nie wysyła sygnału zamknięcia: subtask zamyka człowiek albo audyt z werdyktem, nie kolejny czysty pomiar.
| Ustalenie | Powaga | Pewność | Skutek | Subtask / alarm | Co zrobić |
|---|---|---|---|---|---|
| Publiczny plik .envexposure_env | Pilne | Ewidentne | PODATNA | tak / tak | Zablokuj dostęp i zmień wszystkie ujawnione sekrety. |
| Nie dało się sprawdzić ekspozycjiexposure_scan_unreliable | Niskie | Prawdopodobnie fałszywe | bez wpływu | nie / nie | Nic po stronie strony; liczone osobno. |
LOOP
pętli: 2Trafności jeszcze nie liczymy: zamknięcie subtaska nie niesie dziś werdyktu (prawdziwy / fałszywy / nie dało się sprawdzić). Cztery liczby pojawią się, gdy każde zamknięcie dostanie werdykt — to warunek startu pętli 1.
Sygnał: Tarcze antybotowe hostingu, strony parkingowe i serwery catch-all odpowiadają 200 na każdą ścieżkę, więc sondy oparte na kodzie 200 dawały „wystawiony” wszędzie. Równocześnie sondy na wolnych hostingach odpowiadały po około 15 s, a limit 5 s ucinał je bez śladu. · dotyczy też: Ekspozycja plików wrażliwych, Ekspozycja .htaccess.bak, Dostępność xmlrpc.php, REST API: enumeracja użytkowników, Ekspozycja .git/HEAD, Dostępność readme.html, Dostępność wp-cron.php, Ekspozycja kopii zapasowych, Ekspozycja zrzutu bazy, Ekspozycja manifestów dev, Ekspozycja .DS_Store
- 1. Zbiórkaręcznieodczyt z nocnego skanu, bez zapisu per strona
- 2. Werdyktręcznieprzegląd skanera: 200 na wszystko i ciche timeouty to fałszywe wyniki
- 3. Miarabrakliczników trafności wtedy nie było
- 4. Kartabrakkarta parametru powstała 2026-09-16
- 5. Zmianazrobionestrażnik ścieżki, 15 s, logowanie błędów
- 6. Replaybrakbrak zapisanych odczytów do replayu
| Data | Zmiana | Powód | Wpływ na system | Commit · replay |
|---|---|---|---|---|
| 2026-09-05 | Przed sondami GET losowej, nieistniejącej ścieżki; odpowiedź 200 z treścią = zero sond i jedno ustalenie exposure_scan_unreliable (niska powaga, bez subtaska).helpers/exposure_check.py · config/incident_policy.toml | Odpowiedź 200 na losową ścieżkę dowodzi, że kod 200 na tym serwerze nic nie znaczy; dalsze sondowanie tarczy grozi banem IP monitoringu. | cały system Obejmuje wszystkie sondy ekspozycji naraz i wprowadza typ „nie dało się sprawdzić”; tę samą konwencję dostało wykrywanie wtyczek. Ustalenie ma niską powagę, więc filtr przebiegu je odrzuca i w produkcji nie zostawia śladu. | b0fc77cbez replayubez zapisu zatwierdzenia |
| 2026-09-05 | Limit odpowiedzi z 5 s na 15 s i połączenia z 2 s na 5 s, przeniesione do config.py; błąd sieci sondy logowany zamiast cichego pustego wyniku; do 4 sond naraz na stronę.helpers/config.py · helpers/exposure_check.py · helpers/html_fetch.py · helpers/wp_fingerprint.py | Wolne hostingi odpowiadały po około 15 s, a pusty wynik po timeoucie był nieodróżnialny od „sprawdzone, czysto”. | cały system Limit połączenia 5 s przejęło wspólne pobieranie HTML całego skanu bezpieczeństwa i wykrywanie wersji — strony z wolnym TLS przestały wypadać z całego skanu. | b0fc77cbez replayubez zapisu zatwierdzenia |
Sygnał: Brak pamięci podczas skanu: publiczny debug.log jednej ze stron był bardzo duży, a klient HTTP buforował całą odpowiedź. · dotyczy też: Sitemap.xml, Ekspozycja plików wrażliwych, Ekspozycja .htaccess.bak, Dostępność xmlrpc.php, REST API: enumeracja użytkowników, Ekspozycja .git/HEAD, Dostępność readme.html, Dostępność wp-cron.php, Ekspozycja kopii zapasowych, Ekspozycja zrzutu bazy, Ekspozycja manifestów dev, Ekspozycja .DS_Store
- 1. Zbiórkaręcznieodczyt z nocnego skanu, bez zapisu per strona
- 2. Werdyktręcznieawaria procesu przypisana ścieżce debug.log
- 3. Miarabrakliczników trafności wtedy nie było
- 4. Kartabrakkarta parametru powstała 2026-09-16
- 5. Zmianazrobionestrumień z limitem 64 KB
- 6. Replaybrakbrak zapisanych odczytów do replayu
| Data | Zmiana | Powód | Wpływ na system | Commit · replay |
|---|---|---|---|---|
| 2026-07-19 | Odpowiedź sondy czytana strumieniem do 64 KB; do oceny idzie pierwsze 5000 znaków.helpers/exposure_check.py · helpers/html_fetch.py · helpers/sitemap_check.py | Do rozpoznania ekspozycji wystarcza początek pliku; zmiana dotyczy sposobu czytania, nie werdyktu. | cały system Brak pamięci w helperze zatrzymywał cały proces skanu bezpieczeństwa partii stron; ten sam limit dostało sprawdzenie sitemap. | 343b699bez replayubez zapisu zatwierdzenia |