Information disclosure is often dismissed as a low-severity issue — "it's just version numbers, attackers already know what software you're running." This framing misunderstands how targeted attacks actually work. Attackers do not know in advance what software you are running. Version headers, error messages, and debug endpoints are exactly how they find out — turning a generic scan into a targeted attack against your specific configuration.

Server Version Headers

By default, most web servers announce themselves in detail:

Server: Apache/2.4.51 (Ubuntu)
X-Powered-By: PHP/8.0.13
X-AspNet-Version: 4.0.30319

An attacker with this information goes directly to CVE databases and searches for vulnerabilities in Apache 2.4.51, PHP 8.0.13, and ASP.NET 4.0.30319. If any of these versions have known unpatched CVEs, the attack path is immediately clear. Without this information, the attacker must probe blindly — which takes longer and generates more detectable noise.

Fix (Nginx):

server_tokens off;

Fix (Apache):

ServerTokens Prod
ServerSignature Off

Fix (PHP):

; php.ini
expose_php = Off

Stack Traces in Error Responses

Development frameworks often display detailed error information when exceptions occur. This is invaluable for debugging but catastrophic in production:

sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: user_profiles
[SQL: SELECT user_profiles.id AS user_profiles_id FROM user_profiles WHERE user_profiles.user_id = ?]
[parameters: (42,)]
File "/app/routes/profile.py", line 83, in get_profile
  result = db.execute(query).fetchone()

This single error message reveals the database engine (SQLite), table names, column names, query structure, and exact file paths and line numbers in the source code. A SQL injection attempt just became dramatically easier.

Fix: Ensure production environments never display stack traces to users. Log them server-side, show users a generic error page.

# Flask
app.config['PROPAGATE_EXCEPTIONS'] = False
app.config['TESTING'] = False

@app.errorhandler(500)
def internal_error(e):
    app.logger.exception("Unhandled exception")
    return render_template('500.html'), 500

Debug Endpoints and Admin Panels

Development frameworks often include debug interfaces that are accidentally left enabled in production:

The Flask debugger deserves special mention: when enabled in production, it provides an interactive Python shell in the browser, reachable without authentication. This is effectively unauthenticated remote code execution. Always set FLASK_DEBUG=0 and FLASK_ENV=production in production.

Backup Files and Source Code Exposure

Web servers sometimes accidentally serve files that should not be accessible:

/.git/config           → Git configuration, remote URL, sometimes credentials
/config.php.bak        → PHP configuration backup with database credentials
/wp-config.php         → WordPress database credentials
/.env                  → Environment file with API keys and secrets
/web.config.bak        → IIS configuration backup
/database.sql          → Database dump

Automated scanners probe for hundreds of these paths. An accidentally exposed .env file can expose database credentials, API keys, and application secrets in a single request.

Fix: Explicitly deny access to sensitive file extensions in your web server configuration:

# Nginx — block common sensitive files
location ~* [.](env|git|sql|bak|config|log|ini)$ {
    deny all;
    return 404;
}

location ~ /[.] {
    deny all;  # Block all dotfiles
}

Directory Listing

When no index.html or index.php exists in a directory and directory listing is enabled, the web server renders the full file tree. This exposes upload directories, log files, and any other files that happen to be in the directory.

# Nginx
autoindex off;

# Apache
Options -Indexes

Verbose HTTP Response Headers

Beyond server version, other response headers can reveal infrastructure details:

X-Generator: Drupal 9
X-Drupal-Cache: MISS
X-Varnish: 12345 67890
Via: 1.1 varnish (Varnish/6.2)

Each of these narrows down the attack surface. Remove or sanitize headers that reveal CMS versions, caching infrastructure details, or internal system names.

The Principle

The goal is not perfect secrecy about your technology stack — a determined attacker can often fingerprint your stack through behavioral analysis regardless. The goal is to slow down and complicate reconnaissance, to avoid making it trivially easy, and to eliminate the cases where a single error message hands an attacker all the information they need to exploit you. Defense in depth starts with not handing attackers a roadmap.