9 min read

Information Disclosure

Information disclosure vulnerabilities expose sensitive data that helps attackers plan targeted attacks. This includes version numbers, internal paths, stack traces, API keys, database details, and debugging information. While sometimes considered low-severity individually, information disclosure is a critical enabler for further exploitation.

Information

Information disclosure is often the first step in attack chain construction. Document all findings — even seemingly minor leaks can be combined for significant impact.

Frameworks: WSTG-INFO-05, WSTG-CONF-04, WSTG-ERRH-01 · ASVS 5.0 V13.4.1, V13.4.2, V13.4.3 · CWE-200 · T1592

What Makes a Leak a Finding

Information disclosure is a category of outcomes, not one bug: a debug flag, a document root one level too high, a bundler that ships source maps. The defect is always the same — the application tells a visitor something it has no need to know.

Three questions separate a finding from noise: is it reachable unauthenticated, is it not public by design, and is it useful? That last test is where a banner stops short of disclosure. Server: nginx is guessable; nginx/1.18.0 matters only if you tie it to a vulnerability and show the deployment matches.

Error Page Analysis

Make the application fail in a way its authors did not rehearse: a path that cannot exist, an array where a scalar is expected, truncated JSON. A true positive names something concrete — a framework version, an absolute path, a line number. A branded 404 returning 200 is not disclosure; a JSON body from /actuator is.

error-page-probe.sh
# Trigger error pages to reveal information:

# 404 errors - check for server version in headers/body:
curl -v https://target.com/nonexistent-page-xyz

# 500 errors - trigger with malformed input:
curl -v 'https://target.com/api/user?id=\x00'
curl -v 'https://target.com/api/user?id[]=1'
curl -v -X POST https://target.com/api/user -d '{invalid json}' -H 'Content-Type: application/json'

# Stack traces may reveal:
# - Programming language and framework version
# - Internal file paths (/var/www/app/controllers/UserController.py)
# - Database connection strings
# - Third-party library versions
# - Internal IP addresses

# Framework-specific error pages:
curl https://target.com/wp-admin/               # WordPress
curl https://target.com/elmah.axd                # ASP.NET error logging
curl https://target.com/server-status             # Apache status
curl https://target.com/server-info               # Apache server info
curl https://target.com/__debug__/                # Django debug toolbar
curl https://target.com/_debugbar/open            # Laravel debug bar
curl https://target.com/actuator                  # Spring Boot actuator
curl https://target.com/actuator/env              # Spring env variables
curl https://target.com/actuator/health           # Spring health check
curl https://target.com/trace                     # Spring trace
curl https://target.com/console                   # H2 database console

Response Header Analysis

This is the class's most over-reported evidence. What matters is a patch level such as Apache/2.4.51, a runtime that dates the deployment (X-Runtime), or an artefact showing debug is still on (X-Debug-Token). curl -sI sends HEAD, which a few applications answer differently from GET: confirm with GET, and check a static asset.

header-check.sh
# Check response headers for version disclosure:
curl -sI https://target.com | grep -iE 'server|x-powered|x-aspnet|x-runtime|x-version'

# Common headers revealing information:
# Server: Apache/2.4.51 (Ubuntu)
# X-Powered-By: PHP/8.1.2
# X-Powered-By: Express
# X-Powered-By: ASP.NET
# X-AspNet-Version: 4.0.30319
# X-Runtime: 0.012345 (Ruby on Rails)
# X-Generator: Drupal 10
# X-Debug-Token: abc123 (Symfony)

# Check for internal IP leakage:
curl -sI https://target.com | grep -iE 'x-forwarded|x-real-ip|x-backend|via'

# Check all headers:
curl -sI https://target.com -H 'Host: target.com' | sort

Debug Endpoints & Hidden Files

The list works because of how deployments are assembled. Config files, dumps and VCS metadata sit in the same tree as the application, and a document root one level too high serves them as static content. What comes back decides it: /.env returning the homepage is a soft 404, APP_KEY=base64:… is a credential disclosure, and /.git/HEAD returning a ref confirms a browsable repository.

debug-endpoint-probe.sh
# Common debug/admin endpoints:
curl -s https://target.com/phpinfo.php          # PHP configuration
curl -s https://target.com/info.php
curl -s https://target.com/.env                  # Environment variables!
curl -s https://target.com/.git/HEAD             # Git repository
curl -s https://target.com/.svn/entries          # SVN repository
curl -s https://target.com/.DS_Store             # macOS directory listing
curl -s https://target.com/web.config             # IIS configuration
curl -s https://target.com/crossdomain.xml       # Flash cross-domain
curl -s https://target.com/clientaccesspolicy.xml # Silverlight policy
curl -s https://target.com/robots.txt            # Disallowed paths
curl -s https://target.com/sitemap.xml           # Full URL listing
curl -s https://target.com/.well-known/security.txt

# Backup files:
curl -s https://target.com/config.php.bak
curl -s https://target.com/config.php~
curl -s https://target.com/config.php.old
curl -s https://target.com/config.php.swp
curl -s https://target.com/database.sql
curl -s https://target.com/dump.sql
curl -s https://target.com/backup.zip

# API documentation:
curl -s https://target.com/swagger.json
curl -s https://target.com/api-docs
curl -s https://target.com/openapi.json
curl -s https://target.com/graphql?query={__schema{types{name}}}

Source Code Exposure

An exposed repository is the strongest form because history comes with it. Once /.git/HEAD resolves to a ref the objects are usually fetchable and git-dumper reconstructs the tree; git log -p then reaches commits that were later "cleaned" — the key moved to an environment variable, the account that was deleted. Source maps are the quieter half: a map with sourcesContent reconstructs the original modules; a 404 is not exposure.

source-code-exposure.sh
# Git repository exposure:
# If /.git/ is accessible, you can reconstruct the entire source code

# Check if git is exposed:
curl -s https://target.com/.git/HEAD
# Output: ref: refs/heads/main → Git exposed!

# Use git-dumper to download the repository:
pip install git-dumper
git-dumper https://target.com/.git/ ./target-source

# Then search for secrets:
cd target-source
git log --all --oneline  # Check commit history
git log -p | grep -iE 'password|secret|key|token|api_key'

# Check for source maps in JavaScript:
curl -s https://target.com/js/app.js | tail -1
# //# sourceMappingURL=app.js.map
curl -s https://target.com/js/app.js.map
# Source maps contain the original source code!

Directory Listing

Listing turns a guessed path into an inventory: the server hands you the filename you would otherwise brute force. Apache emits <title>Index of /</title>, IIS uses [To Parent Directory], nginx can emit JSON. A true positive lists files linked from nowhere; an empty index or styled 403 is not one. Keep 403 in the fuzz match set — it proves the path exists.

directory-listing-check.sh
# Check for directory listing:
curl -s https://target.com/uploads/
curl -s https://target.com/images/
curl -s https://target.com/static/
curl -s https://target.com/assets/
curl -s https://target.com/backup/
curl -s https://target.com/tmp/
curl -s https://target.com/logs/

# Look for:
# - <title>Index of /</title> (Apache)
# - Directory listing enabled
# - Backup files, configuration files, log files
# - Database dumps, credential files

# Automate with ffuf:
ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt \
  -u https://target.com/FUZZ/ \
  -mc 200,403 \
  -fc 404

Confirming a Leak Before You Report It

Almost every false positive here comes from trusting a status code: SPA and CDN fallbacks answer 200 for paths that do not exist. Baseline first — request a path that cannot exist, record status, content type and byte size, then compare. A leak is real when the body differs in a way that carries information, without your session cookie. Never exercise a discovered credential beyond proving it exists; record its format and let the client rotate it.

confirm-disclosure.sh
# 1. Unauthenticated fetch: status, content type, byte size
curl -s -o /dev/null -w '%{http_code} %{content_type} %{size_download}\n' \
  https://target.com/.git/HEAD

# 2. Baseline: the same measurements for a path that cannot exist
curl -s -o /dev/null -w '%{http_code} %{content_type} %{size_download}\n' \
  https://target.com/this-path-cannot-exist-9f3a

# 3. Read the first bytes of the body, not just the status line
curl -s --max-time 10 https://target.com/actuator/env | head -c 400

# 4. A source map is only a finding if it parses as JSON and carries sources
curl -s https://target.com/js/app.js.map | head -c 200

Re-run the same request on the last day, so the report describes the state the client is fixing.

Testing Checklist

  • ☐ 1. Check response headers for server version, framework, and internal IPs
  • ☐ 2. Trigger error pages (404, 500) and check for stack traces
  • ☐ 3. Scan for debug endpoints (phpinfo, actuator, debug toolbar)
  • ☐ 4. Check for exposed .env, .git, backup files, and source maps
  • ☐ 5. Check for directory listing on common paths
  • ☐ 6. Review robots.txt and sitemap.xml for hidden paths
  • ☐ 7. Check API documentation endpoints (swagger, openapi)
  • ☐ 8. Search HTML source code for comments, credentials, internal URLs
  • ☐ 9. Compare every candidate response against a soft-404 baseline

Evidence Collection

Screenshots: Error pages showing stack traces, version numbers, internal paths

Header Dump: Full response headers showing version disclosure

Files Found: .env contents (redact actual secrets), .git HEAD output

Soft-404 Baseline: Status, content type and byte size for a path that cannot exist, so the client can see the leak is not the SPA fallback or CDN error page

Reproduction Command: The exact curl invocation that produced the evidence, with a timestamp — enough for the client to re-run it after the fix ships

CVSS Range: Version disclosure: 2.7–4.3 | Credentials in source: 7.5–9.1 | .env exposed: 9.1

Remediation

  • • Custom error pages: Replace default error pages with custom ones that reveal no technical details.
  • • Remove version headers: Configure web server to suppress Server, X-Powered-By headers.
  • • Disable debug mode: Ensure DEBUG=False in production for all frameworks.
  • • Block sensitive files: Configure web server to deny access to .env, .git, .svn, backup files.
  • • Disable directory listing: Set Options -Indexes (Apache) or autoindex off (Nginx).
  • • Remove source maps: Don't deploy .map files to production.
  • • Fix the document root: Point the web server at the build output directory, not the project directory — that single change removes .env, .git, and most backup files from the reachable tree.
  • • Deny dotfiles at the edge: Block paths under dot-directories and common archive extensions (.bak, .old, .sql, .zip) in the CDN or reverse proxy, so a future deployment mistake is not immediately reachable.
  • • Verify by request, not by config: After the fix, re-run the exact curl that found the leak and attach the response. A configuration screenshot does not prove the file is unreachable.

False Positive Identification

  • Public information in error pages: Generic server version strings (e.g., "Apache") may not be sensitive enough to report — focus on exact patch versions, internal paths, and database connection strings.
  • Intentional debug endpoints: Some applications expose /health or /status by design — verify with the client whether these are intended to be public.
  • Stack traces in development: If testing on a dev/staging environment, confirm the same behavior occurs in production before reporting.
  • SPA soft 404: A 200 with the application shell is the most common false positive in this class. Compare byte size and content type against a path that cannot exist before calling anything exposed.
  • Edge-added banners: A CDN or WAF often adds Server, Via or X-Served-By at the edge. Those describe the proxy path, not the origin build, and usually carry no patch-level information.
  • Denied but present: A 403 on /.git/HEAD confirms the file exists but is not readable. That is a hardening note, not a source-code disclosure — do not escalate it to the impact of an exposed repository.

Information Disclosure Practice

Hunt leaked data in errors, debug pages, and backup artefacts.

PortSwigger — Information Disclosure Labs PortSwigger easy
Error message leakageDebug endpointsBackup file discovery