Přejít na obsah

Kontrola citlivých souborů

Zjistěte, jestli váš web veřejně nevydává soubory, které na něj nepatří: repozitář .git, konfiguraci .env, zálohy databáze nebo logy. Vysvětlíme, jak nález ověřit a co s ním dělat.

Načítáme ověření…

Co kontrola ověřuje

Při nasazování webu se na server snadno dostanou soubory, které tam nepatří: celý adresář .git, soubor .env s heslem k databázi, zapomenutá záloha nebo log. Webový server je pak vydá každému, kdo zná jejich název. Roboti tyto adresy plošně zkoušejí na všech webech, takže spoléhat na to, že o souboru nikdo neví, nejde.

Nástroj se vašeho serveru zeptá na pevný seznam více než 30 běžných cest a u každé zjistí, jestli ji server vydá. Z každé odpovědi přečte nejvýše první 4 kB, aby poznal, jestli jde o skutečný soubor, nebo jen o běžnou stránku webu. Obsah neukládáme ani nezobrazujeme.

  • Verzovací systémy – /.git/HEAD, /.git/config a /.svn/entries. Z dostupného repozitáře lze stáhnout zdrojový kód i celou historii změn, často včetně hesel, která v ní kdysi byla.
  • Konfigurace a tajné klíče – /.env, /.env.local, /.env.production, /config.php, /config.json a záložní kopie wp-config.php s příponou .bak nebo .old.
  • Zálohy a exporty – /backup.sql, /database.sql, /db.sql, /backup.zip, /backup.tar.gz a /.htaccess.bak.
  • Ladicí a provozní soubory – /phpinfo.php, /info.php, /test.php, /debug.log, /error.log, stránka /server-status a soubory /.htpasswd a /web.config.
  • Další stopy po vývoji – /package.json, /package-lock.json, /composer.json, adresáře /node_modules/, /vendor/ a /.vscode/, systémové soubory /.DS_Store a /Thumbs.db a u WordPressu /xmlrpc.php a /wp-admin/.
  • Standardní soubory – /robots.txt, /sitemap.xml a /.well-known/security.txt. Ty naopak na webu mít dává smysl.

Jak číst výsledek

Kontrolujeme adresu, na které web skutečně běží. Když zadaná adresa přesměruje třeba z HTTP na HTTPS nebo na variantu s www, otestujeme cílový web. Všechny cesty zkoušíme od kořene domény.

Mnoho webů odpoví na jakoukoli neexistující adresu stavem 200 a úvodní stránkou. Proto se nespokojíme se stavovým kódem. Odpověď ve formátu HTML (text/html) nikdy nepovažujeme za hledaný soubor .env nebo zálohu. U každého typu cesty si navíc vyžádáme náhodnou adresu stejného tvaru (stejný adresář, přípona, tečka na začátku). Odpověď počítáme, jen když server náhodnou adresu odmítne (404, 410 nebo přesměrováním mimo web), nebo když na ni odpoví jinak než na hledanou cestu. Samotnou adresu, kterou některé weby v odpovědi zopakují, přitom nebereme v úvahu. Když server na náhodnou adresu odpoví chybou nebo omezením počtu požadavků, nález potvrdit nelze. Stránky, které server generuje, jako phpinfo(), /server-status nebo výpis adresáře, poznáme podle jejich typického obsahu.

  • Veřejně dostupné – server cestu vydal a odpověď vypadá jako skutečný soubor nebo vygenerovaná stránka. Nález ověřte podle postupu níže.
  • Nedostupné – server vrátil 404 nebo 410, přesměroval mimo váš web, nebo poslal jen běžnou stránku webu. U config.php to platí i tehdy, když skript existuje, ale server ho spustí a jeho zdrojový kód nevydá.
  • Přístup odepřen – server odpověděl 401 nebo 403. Obvykle jde o vaše pravidlo, které soubor chrání, ale stejně odpovídá i firewall, který blokuje naše požadavky.
  • Neověřeno – server odpověděl chybou, omezením počtu požadavků (například 429) nebo přesměrováním, které nikam nevedlo, neodpověděl do 5 sekund, místo webu odpověděla ochrana proti robotům, nebo z odpovědi nešlo spolehlivě poznat, jestli soubor existuje. Neověřené zůstanou i cesty, na které už nezbyl čas, protože celá kontrola má časový limit. Takový výsledek neříká nic o tom, jestli je soubor na serveru. Když se to stane u většiny cest, upozorníme, že výsledek je neúplný.
  • Dostupné, zvažte omezení – administrace WordPressu (/wp-admin/) nebo rozhraní /xmlrpc.php. Jsou veřejná záměrně, nejde o únik souboru, ale roboti na nich zkoušejí hádat hesla.
  • Standardní soubory – u robots.txt, sitemap.xml a security.txt je nález naopak žádoucí. Přesměrování na jiný web, které RFC 9116 u security.txt dovoluje a které je běžné u map stránek na CDN, počítáme jako nález. Absence těchto souborů zabezpečení neoslabuje, security.txt ale doporučujeme doplnit.
Cesta                         Odpověď serveru                       Výsledek
/.tunasec-nahodna-cesta       404, text/html                        (srovnání)
/.env                         200, text/plain: DB_PASSWORD=…        Veřejně dostupné
/sitemap.xml                  200, text/html: úvodní stránka        Nenalezeno
/.git/HEAD                    403                                   Přístup odepřen (HTTP 403)
/backup.sql                   429                                   Neověřeno (HTTP 429)
/wp-admin/                    302 → /wp-login.php, 200              Dostupné, zvažte omezení
  • Náhodná cesta ukazuje, jak server odpovídá na neexistující soubory stejného tvaru jako /.env, tedy s tečkou na začátku a bez přípony. Tady vrátil 404.
  • /.env vrátil 200 a obsah, který není stránkou HTML, zatímco náhodná adresa stejného tvaru vrátila 404. Nástroj ho proto označí jako veřejně dostupný.
  • /sitemap.xml vrátil 200, ale jen úvodní stránku webu. Mapa stránek tedy na této adrese není, i když stavový kód říká opak.
  • /.git/HEAD vrátil 403: server přístup odepřel. Odpověď 404 je o něco lepší, protože neprozradí, jestli adresář na serveru je.
  • /backup.sql vrátil 429, server nás dočasně omezil. O záloze proto nevíme nic a je potřeba ji ověřit jinak, například příkazem z postupu níže.

Co dělat, když nástroj něco najde

  1. Ověřte, co server opravdu vydává

    Otevřete adresu v prohlížeči nebo spusťte curl -s https://vase-firma.cz/.env | head -n 5. Pokud uvidíte hesla, klíče, příkazy SQL nebo u .git/HEAD řádek ref: refs/heads/main, soubor je opravdu veřejný. Stejným příkazem ověříte i cesty, které nástroj označil jako neověřené.

  2. Zablokujte přístup

    Soubor z webového adresáře odstraňte, nebo přístup zakažte v konfiguraci serveru podle příkladu níže. Zálohy, exporty databáze a logy patří mimo adresář, který server vydává. Ladicí skripty jako phpinfo.php smažte. Potom kontrolu spusťte znovu.

  3. Vyměňte všechna tajemství, která byla dostupná

    Smazání souboru nestačí, kopii už mohl někdo stáhnout. Změňte hesla k databázi, API klíče, přístupy k SMTP a cloudu i aplikační klíče jako APP_KEY nebo SECRET_KEY. U dostupného .git počítejte s tím, že útočník má zdrojový kód i všechno, co kdy bylo v historii.

  4. Zjistěte, jestli si soubor někdo stáhl

    V přístupových logech hledejte požadavky na danou cestu s odpovědí 200, například na /.env nebo /.git/. Pokud logy sahají jen pár dní zpět, to, že v nich nic není, nic nedokazuje. Pokud mohlo dojít k úniku osobních údajů, posuďte, jestli nemusíte incident do 72 hodin od zjištění ohlásit Úřadu pro ochranu osobních údajů.

  5. Upravte nasazování, aby se chyba neopakovala

    Na server nahrávejte jen výsledek sestavení, ne pracovní kopii repozitáře: artefakt z CI nebo rsync s vyloučením .git a .env. Kořen webu nastavte na podadresář typu public/, aby konfigurace a závislosti ležely mimo něj. Tajemství, které se dostalo do commitu, odstraňte z historie nástrojem git filter-repo, ale až po výměně klíčů, ne místo ní.

# nginx – uvnitř bloku server { }, před ostatními regulárními location
# skryté soubory a adresáře (.git, .env, .htpasswd) kromě .well-known
location ~ /\.(?!well-known/) {
    return 404;
}
# zálohy, exporty databáze a logy
location ~* \.(sql|bak|old|log)$ {
    return 404;
}

# Apache 2.4 – konfigurace webu nebo .htaccess
RedirectMatch 404 "/\.(?!well-known/)"
<FilesMatch "\.(sql|bak|old|log)$">
    Require all denied
</FilesMatch>
  • Regulární bloky location vyhodnocuje nginx v pořadí, v jakém jsou zapsané, a použije první, který cestě odpovídá. Pravidla proto dejte před ostatní regulární bloky, například před zpracování PHP.
  • Výjimka pro .well-known je nutná, jinak zablokujete i security.txt a ověřování certifikátů Let’s Encrypt.
  • return 404 neprozradí, že soubor existuje. deny all v nginx nebo Require all denied v Apache vrací 403, což chrání stejně, jen existenci souboru potvrdí.
  • Stránku /server-status v Apache omezte direktivou Require local nebo Require ip s adresami správců. Blokování v konfiguraci je pojistka: zálohy a logy na webový server vůbec nepatří.

Co kontrola neříká

  • Zkoušíme jen pevný seznam cest. Soubory s jinými názvy, například zaloha-2025.zip nebo .env.bak, nenajdeme. Web neprocházíme ani neodhadujeme další názvy.
  • Testujeme jen kořen jedné domény. Pokud zadáte adresu s cestou, cestu ignorujeme. Jiné subdomény, například admin.vase-firma.cz, zkontrolujte zvlášť.
  • Nález je založený na prvních 4 kB odpovědi. Nevíme proto, jaké tajné údaje soubor obsahuje, a soubor, který server posílá jako HTML (text/html), za nález nepovažujeme.
  • Skript bez rozpoznatelného obsahu, jako je test.php, umíme spolehlivě posoudit jen na webech, které na neexistující adresy odpovídají 404 nebo 410. Na ostatních webech ho označíme jako neověřený, pokud jeho odpověď není stejná jako odpověď na náhodnou adresu.
  • Když server odpoví chybou, nestihne odpovědět do 5 sekund nebo naše požadavky zablokuje firewall či ochrana proti robotům (například od Vercelu nebo Cloudflaru), cestu označíme jako neověřenou nebo s odepřeným přístupem. Celá kontrola má navíc limit zhruba 25 sekund a cesty, na které nezbude čas, označíme také jako neověřené. Neznamená to, že soubor na serveru není.
  • Žádný nález neznamená bezpečný web. Zranitelnosti v aplikaci, přihlašování nebo API odhalí až penetrační test.
  • Připojujeme se jen k veřejným adresám, a to i po přesměrování. Výsledek ani obsah odpovědí neukládáme a z jedné IP adresy lze spustit nejvýše pět kontrol za minutu.

Časté otázky

Proč nástroj u /wp-admin/ doporučuje omezení, když web funguje normálně?

Na WordPressu tato adresa přesměruje na přihlašovací stránku, takže je dostupná záměrně a nejde o únik souboru. Roboti na ní ale zkoušejí hádat hesla. Zapněte vícefaktorové ověření, omezte počet pokusů o přihlášení, a pokud administraci používá jen pár lidí, povolte ji jen z vybraných IP adres. Rozhraní /xmlrpc.php vypněte, pokud ho nepoužívá mobilní aplikace nebo jiná služba.

Stačí dostupný soubor smazat?

Ne. Smazáním zabráníte dalšímu stažení, ale kopie, které už někdo má, tím nezmizí. Hesla a klíče, které soubor obsahoval, považujte za vyzrazené a vyměňte je. Teprve potom má smysl čistit historii repozitáře.

Kontrola nic nenašla. Je web v pořádku?

Znamená to jen, že server žádnou z testovaných cest nevydal. Zkontrolujte, jestli některé cesty nejsou neověřené nebo s odepřeným přístupem kvůli firewallu. Soubory s jinými názvy, aplikace v podadresářích ani zranitelnosti v samotné aplikaci tato kontrola nepokrývá.

Uvidí kontrolu správce serveru?

Ano. Do přístupového logu se zapíše zhruba 60 požadavků GET s identifikací (User-Agent) TunaSec-Security-Scanner/1.0, včetně náhodných adres obsahujících tunasec-. Najednou jich posíláme nejvýše deset a z každé odpovědi čteme jen začátek, takže běžný web kontrola nezatíží. Kontrolujte jen weby, které spravujete, nebo k tomu máte svolení.

Nevadí, že robots.txt ukazuje, kde je administrace?

robots.txt je jen doporučení pro vyhledávače, ne ochrana. Je veřejný a útočníci ho čtou mezi prvními. Nevypisujte do něj adresy, které mají zůstat skryté. Co nemá být veřejné, chraňte přihlášením, nebo to na server vůbec nedávejte.

K čemu je security.txt?

Soubor /.well-known/security.txt podle standardu RFC 9116 říká, kam hlásit nalezené zranitelnosti. Povinná jsou pole Contact a Expires. Díky němu se upozornění od bezpečnostního výzkumníka dostane ke správnému člověku a neztratí se v obecné schránce.

Chcete vědět, co dalšího útočník na webu najde?

Kontrola souborů pokrývá jen nejčastější chyby při nasazení. Napište nám, o jaký web jde, a probereme, jak ho prověřit důkladněji.

Napsat nám

Při penetračním testu hledáme i soubory s jinými názvy a slabá místa v přihlašování, API a infrastruktuře a ověřujeme, kam by se útočník reálně dostal.

Penetrační testování