Dokumentacja

Zablokuj publiczny dostęp do .git i .env

Zablokuj publiczny dostęp do .git i .env

Gdy https://example.com/.git/HEAD albo https://example.com/.env otwiera się w przeglądarce, strona oddaje coś, co nigdy nie powinno opuścić serwera. Plik .env zawiera hasło do bazy danych, klucze do poczty i płatności oraz klucz szyfrujący aplikacji. Folder .git zawiera cały kod źródłowy z historią, łącznie z każdym hasłem, które kiedykolwiek trafiło do commita i zostało później "usunięte". Darmowe narzędzia pobierają publiczny folder .git w kilka minut, a boty codziennie przeszukują cały internet pod kątem obu ścieżek.

To jedyna pozycja na liście kontrolnej strony, która nie może czekać do jutra. Jeśli jest czerwona, załóż, że ktoś już ma te pliki.

Traktuj to jak wyciek, a nie ustawienie

Blokada zatrzymuje kolejne pobranie, a nie te, które już się odbyły. Działaj w tej kolejności:

  1. Natychmiast zablokuj dostęp. Najszybciej regułą na serwerze WWW albo w Cloudflare (zobacz zakładki poniżej). To zajmuje minutę.
  2. Zmień każdy sekret, który był w .env: hasło do bazy danych, klucze SMTP i API poczty, klucze dostawców płatności (Stripe, PayPal), klucze do przechowywania w chmurze, sekrety klientów OAuth, tokeny API. U każdego dostawcy utwórz nowe i unieważnij stare.
  3. To samo zrób z sekretami w historii gita, jeśli publiczny był .git. Liczy się wszystko, co kiedykolwiek było w commicie, nawet jeśli późniejszy commit to usunął.
  4. Sprawdź logi, aby zobaczyć, czy ktoś pobrał pliki i od kiedy.

Ostrzeżenie: W Laravelu APP_KEY szyfruje sesje, ciasteczka i dane zapisane przez Crypt lub szyfrowane casty. Nowy klucz wyloguje wszystkich, a zaszyfrowanych danych w bazie nie da się odczytać samym nowym kluczem. Laravel 11 i nowsze potrafią czytać stare dane podczas przejścia na nowy klucz: wpisz stary klucz do APP_PREVIOUS_KEYS.

Kto pobrał pliki, sprawdzisz w logach dostępu (także archiwalnych):

sudo zgrep -hE '"GET /\.(env|git)' /var/log/nginx/access.log* /var/log/apache2/access.log* 2>/dev/null | grep '" 200 '

Każda linia ze statusem 200 to udane pobranie. Na hostingu współdzielonym szukaj tych samych ścieżek w surowych logach dostępu w panelu.

Sprawdź sam

Zamiast example.com wpisz swoją stronę:

curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.git/HEAD
curl -s -o /dev/null -w '%{http_code}\n' https://example.com/.env

403 albo 404 oznacza blokadę. 200 oznacza, że plik jest publiczny; aby zobaczyć to, co widzi odwiedzający, uruchom to samo polecenie bez -o /dev/null -w …. .git/HEAD zaczynający się od ref: refs/heads/ to prawdziwe repozytorium.

Sprawdź też http:// i wariant adresu z www, na wypadek gdyby obsługiwała je inna konfiguracja.

Jak to naprawić

Są dwa poziomy. Reguła blokująca ścieżki zaczynające się od kropki jest szybka i zatrzymuje wyciek jeszcze dziś. Trwałe rozwiązanie to trzymanie obu poza katalogiem głównym strony: serwer WWW powinien widzieć tylko folder przeznaczony dla publiczności (public/, public_html/), a wdrożenie w ogóle nie powinno tam kopiować .git.

nginx

Dodaj to do każdego bloku server { … } strony, nad innymi blokami location: na Debianie i Ubuntu w /etc/nginx/sites-available/example.com, na Rocky, Alma i CentOS w /etc/nginx/conf.d/example.com.conf:

# Każda ścieżka zaczynająca się od kropki, poza /.well-known/.
location ~ /\.(?!well-known/) {
    deny all;
}

nginx używa pierwszego pasującego location z wyrażeniem regularnym, więc ten musi być przed regułami takimi jak location ~ \.php$.

sudo nginx -t && sudo systemctl reload nginx

Apache i LiteSpeed

Dodaj to na początku .htaccess w głównym folderze strony albo w <VirtualHost> strony, jeśli zarządzasz serwerem:

# Każda ścieżka zaczynająca się od kropki, poza /.well-known/.
RedirectMatch 404 /\.(?!well-known/)

Odpowiada 404, jakby plików nie było. Działa na Apache i na LiteSpeed. <FilesMatch "^\.env"> nie wystarczy: dopasowuje tylko nazwy plików, a pliki wewnątrz .git nazywają się HEAD, config i tak dalej.

Z dostępem do serwera przeładuj konfigurację po zmianie:

sudo apachectl configtest && sudo systemctl reload apache2

(Na Rocky, Alma i CentOS httpd zamiast apache2.)

Laravel

Katalog główny serwera WWW (document root) musi być folderem public/, nigdy folderem projektu. Wtedy .env, .git, vendor/ i storage/ leżą poza tym, co serwer może serwować, i dodatkowa reguła nie jest potrzebna. Laravel Forge, Ploi i Laravel Cloud ustawiają to domyślnie.

Na hostingu współdzielonym, gdzie katalog główny to na stałe public_html, nie wgrywaj całego projektu do public_html. Wgraj go obok (na przykład do ~/example-app) i w panelu skieruj katalog główny na jego folder public (zobacz zakładki cPanel i Plesk). Jeśli panel na to nie pozwala, zastąp public_html dowiązaniem przez SSH:

mv ~/public_html ~/public_html.old
ln -s ~/example-app/public ~/public_html

Jeśli nie masz pewności, czy dowiązania są dozwolone, najpierw zapytaj hosting. Dopóki przeniesienie nie jest gotowe, tymczasowym rozwiązaniem jest reguła .htaccess z zakładki Apache.

cPanel

Szybka blokada: dodaj linię RedirectMatch z zakładki Apache do .htaccess w public_html (File Manager, z włączonym Show Hidden Files).

Przeniesienie katalogu głównego dla domeny dodatkowej (addon) lub subdomeny: Domains → Domains, Manage przy domenie, zmień New Document Root na folder z plikami publicznymi (na przykład example-app/public) i kliknij Update. Katalog główny domeny głównej to na stałe public_html; dla niej użyj dowiązania z zakładki Laravel albo zapytaj hosting.

Wdrażanie przez Git Version Control: sklonuj repozytorium poza public_html i kopiuj do niego tylko pliki publiczne za pomocą pliku wdrożenia .cpanel.yml. Repozytorium sklonowane bezpośrednio do public_html publikuje swój folder .git.

Plesk

Szybka blokada: Websites & Domains → example.com → Apache & nginx Settings, w Additional nginx directives dodaj:

location ~ /\.(?!well-known/) {
    deny all;
}

Przeniesienie katalogu głównego: w Hosting Settings domeny (w nowszych wersjach Pleska Hosting & DNS → Hosting) ustaw Document root na folder publiczny, na przykład httpdocs/public dla Laravela lub Bedrock, i zapisz.

Jeśli wdrażasz przez rozszerzenie Git w Plesku, ustaw jako ścieżkę wdrożenia katalog główny, a samo repozytorium trzymaj poza nim.

Cloudflare

Jeśli strona działa za Cloudflare (pomarańczowa chmurka), reguła WAF zablokuje obie ścieżki w minutę, także na darmowym planie:

  1. Otwórz domenę, przejdź do Security → WAF → Custom rules (w nowszym panelu Security → Security rules) i utwórz regułę.
  2. Nazwij ją Block .git and .env, kliknij Edit expression i wklej:
(http.request.uri.path contains "/.git" or http.request.uri.path contains "/.env")
  1. Wybierz akcję Block i kliknij Deploy.

To chroni tylko ruch przechodzący przez Cloudflare. Każdy, kto zna adres IP serwera, nadal może zapytać go bezpośrednio, więc popraw też serwer.

IIS i ASP.NET

IIS potrafi ukryć całe segmenty ścieżki. W pliku web.config strony:

<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <hiddenSegments>
          <add segment=".git" />
          <add segment=".env" />
        </hiddenSegments>
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>

Żądania tych ścieżek dostają 404. Domyślnie IIS odmawia też plików bez znanego typu MIME, więc publiczny .env zwykle oznacza, że ktoś dodał ogólny mimeMap; usuń go.

Node.js i Next.js

Next.js serwuje tylko folder public/, więc .env i .git wyciekają tylko wtedy, gdy coś przed nim (nginx, Apache) wskazuje na folder projektu. Popraw katalog główny tamtego serwera albo dodaj regułę z jego zakładki.

W Expressie serwuj dedykowany folder, nigdy katalog projektu:

app.use(express.static(path.join(__dirname, 'public')));

express.static(__dirname) publikuje wszystko obok kodu, łącznie z .env i .git.

Wdrażanie bez .git

Nawet z regułą najbezpieczniejszy .git to taki, którego na serwerze WWW w ogóle nie ma. Zamiast git pull w katalogu głównym strony wyeksportuj pliki:

# Tylko pliki z ostatniego commita, bez .git:
git archive --format=tar HEAD | ssh deploy@example.com 'tar -x -C /var/www/example.com'

# Albo rsync, bez .git i lokalnego .env:
rsync -az --delete --exclude='.git' --exclude='.env' ./ deploy@example.com:/var/www/example.com/

--delete usuwa na serwerze pliki, których nie ma w Twoim folderze, więc sprawdź ścieżkę docelową dwa razy. Trzymaj .env serwera poza synchronizowanym folderem albo wyklucz go jak wyżej.

Nigdy nie blokuj /.well-known

Reguły powyżej celowo zostawiają /.well-known/ otwarte. Let's Encrypt sprawdza tę ścieżkę przy wydawaniu i odnawianiu certyfikatów, a znajdują się tam też pliki takie jak security.txt i powiązania aplikacji na iOS i Androida. Reguła blokująca każdą ścieżkę z kropką zatrzyma też odnawianie certyfikatu, a strona przestanie działać, gdy certyfikat wygaśnie.

Potwierdź poprawkę

Uruchom ponownie oba polecenia curl z sekcji Sprawdź sam, następnie otwórz cel w PulseKeeper i kliknij Sprawdź teraz na zakładce Lista kontrolna. Pozycja zmieni się na OK w ciągu kilku minut, a nie dopiero przy następnym codziennym sprawdzeniu. Blokada nie cofa wycieku: dokończ wymianę sekretów, nawet gdy pozycja jest zielona.