Exposed sensitive files (.env, .git, backups): how it works, how to test it, how to fix it

Files like .env, .git/config or backup.sql left in the web root can hand out credentials and source code to anyone who requests the right URL.

Typical severity: Critical · Checked by NovaPentest module: Sensitive File Discovery

What it is

An exposed sensitive file is a file that was never meant to be public but is served by the web server to anyone who asks for its path. It is almost always an accident of deployment rather than a bug in application code.

The usual causes are ordinary. A site is deployed by cloning the repository straight into the document root, so the .git directory travels with it. A .env file holding database passwords and API keys sits next to index.php because the framework reads it from there. Someone takes a quick backup before an upgrade and leaves backup.zip or dump.sql in the public folder. A phpinfo.php page created to debug a server is never deleted. An editor or operating system leaves metadata files such as .DS_Store behind.

Web servers serve whatever is inside the document root unless told otherwise. Hidden files are hidden only in directory listings on your own machine; the leading dot means nothing to nginx or Apache. And since these paths are well known, nobody needs a link to find them: they can be requested directly, and automated tools across the internet routinely do exactly that.

The category is broad. Some files are dangerous on their own (a .env with live credentials, a full database dump). Others give away information that makes other attacks easier, such as server status pages, PHP configuration details or Java deployment descriptors. Some are low impact but still signal that the deploy process copies more than it should.

The fix is rarely complicated, but it has two parts that both matter: stop the file from being served, and treat anything it contained as already disclosed.

How it is exploited

What an attacker gains depends on the file, and the most common ones are also the most damaging.

A readable .git directory lets someone download the repository's objects and reconstruct your source code, often including its history. That history frequently contains secrets that were committed once and later removed: the current files look clean, but the old commit still holds the key. Source code also reveals internal endpoints, admin routes and logic flaws that are much easier to find by reading than by probing from outside.

A .env or config file typically contains database credentials, cloud access keys, payment provider secrets, SMTP passwords and signing keys for sessions or tokens. With those, an attacker may not need to exploit the application at all: they can connect to the database or call the third-party API directly, or forge session tokens that the application accepts as valid.

SQL dumps and site backup archives are a copy of your data, including user records, password hashes and personal information, handed over in one download. That is a data breach in itself, with the reporting obligations that come with it.

Diagnostic pages such as phpinfo expose server paths, loaded modules, configuration values and sometimes environment variables. Apache server-status can show the URLs other visitors are requesting in real time, which may include tokens in query strings. These are reconnaissance aids rather than direct compromise, but they shorten the road to one.

The important point for owners is timing. These files are requested by automated scanners constantly, so if one was reachable for any length of time, assume it has already been read. Blocking access afterwards stops future reads; it does not undo the disclosure.

How to detect it

The Sensitive File Discovery module runs only in full scans, not in the free passive scanner. It takes the scheme, host and port of your target, ignores any path, and requests a fixed list of 32 paths at the site root with redirects disabled, so a 301 to the home page cannot pass for a real file. Paths include /.git/config, /.git/HEAD, /.svn/entries, /.hg/hgrc, /.env and its .local, .prod, .production and .backup variants, /wp-config.php.bak, /backup.sql, /dump.sql, /database.sql and /db.sql, all reported as Critical. /config.php, /config.bak, /config.yml, /config.json, /backup.tar.gz, /backup.zip, /phpinfo.php, /info.php, /php-info.php, /web.xml and /WEB-INF/web.xml are High. /test.php, /.htaccess, /server-status and /server-info are Medium, and /.DS_Store, /crossdomain.xml and /clientaccesspolicy.xml are Low.

A path is flagged only if it returns HTTP 200 with a body of at least 10 characters that does not look like a generic not-found page and does not match the site's catch-all response, which the module fingerprints first by requesting a random nonexistent path. This keeps single-page apps that answer 200 to everything from producing false alarms.

The limits are real. The module does not inspect what the file contains, so a legitimate public config.json served by your app is still reported. A real file whose text includes phrases like page not found can be skipped. Only the root is checked, not subfolders. If you submit an http:// URL that redirects to https, every probe receives a redirect and nothing is reported, so scan the https address. Other names, such as .env.dev or .git/index, are not requested.

To verify by hand, run curl -s -o /dev/null -w "%{http_code}\n" https://example.com/.env and repeat for each path, including subfolders you deploy to. A 200 is worth opening; for .git, curl -s https://example.com/.git/HEAD returning a line starting with ref: confirms exposure. A 403 or 404 is what you want.

How to fix it

Work in this order: contain, rotate, then prevent recurrence.

1. Block access immediately at the web server. In nginx, deny dotfiles while keeping ACME challenges working: location ~ /\.(?!well-known) { deny all; return 404; }. Add explicit denials for backup and dump extensions, for example location ~* \.(sql|bak|zip|tar\.gz)$ { deny all; }. In Apache 2.4, use <FilesMatch "^\."> Require all denied </FilesMatch> plus RedirectMatch 404 /\.git in the site configuration.

2. Delete the files from the web root. Backups, dumps, phpinfo pages and test scripts have no reason to be on the server at all; move backups to storage that is not web-served.

3. Rotate every secret the file contained, even if logs show no access. Change database passwords, revoke and reissue API and cloud keys, rotate payment and email provider credentials, and replace session or token signing keys, which will log users out. If a .git directory was exposed, review the full history for secrets, not just the current files, and rotate those too.

4. Check access logs for requests to the exposed path to judge what was downloaded and since when. If a database dump or backup was reachable, handle it as a possible data breach.

5. Move secrets out of the web root. Point the document root at a public subfolder (for example public/) so configuration lives one level above it, or inject secrets as environment variables from your hosting platform or a secret manager instead of files.

6. Fix the deploy. Build an artifact containing only what the site needs instead of cloning the repository onto the server, keep .env in .gitignore, and disable diagnostic pages such as server-status or restrict them to internal addresses.

7. Rescan after the change and add the check to your release process.

Check your own site

Start with the free passive scan: no account needed, and you get an A to F grade covering headers, TLS, cookies, DNS and other configuration a visitor can observe. It does not send attack payloads, so it will not confirm every class of flaw. The full scan, available with a free account, runs the active checks, including the one described on this page.

Scan your site free