Back to scan results
Check 15 of 24

Error Page Disclosure

We send a small set of crafted requests designed to trigger application errors, then look at the response for stack traces, full file paths, framework banners, and other internal detail that a normal user should never see.

What this check probes

We send a few benign-but-malformed requests that are likely to trigger an exception path in common stacks:

  • A request to a non-existent path with characters that often cause type-coercion errors (/notfound.php?a[]=1).
  • A request with a single quote in the path or query (no SQL payload, just enough to provoke a parser error in unsafe code).
  • Standard 404 / 405 / 500 paths to read your default error page.

We then look at the response body for telltale signatures:

  • Stack traces — at java.lang., Traceback (most recent call last):, System.NullReferenceException, #0 /var/www/....
  • Framework banners — Symfony Whoops, ASP.NET YSOD ("Server Error in Application"), Rails dev mode, Django debug page, Laravel Ignition, Flask debug toolbar.
  • File path leakage — absolute filesystem paths like /var/www/html/app/Controllers/... or C:\inetpub\wwwroot\....
  • SQL fragments — error messages containing query text or schema names.

Why this matters for PCI DSS

A verbose error page is a free reconnaissance brief. From a single stack trace an attacker learns: framework + version (which CVEs apply), filesystem layout (which paths to probe), database schema (which tables exist), and often credentials embedded in connection strings.

SQL error messages are particularly serious — they confirm the presence of an injection point and reveal the schema the attacker needs to exploit it.

PCI DSS 4.0 Requirement 6.2.4 requires defenses against information disclosure. Requirement 6.5 covers application coding standards including error handling.

How to fix it

The principle is the same in every stack: log the full detail to a server-side log; show the user a generic page.

PHP (php.ini, production):

display_errors = Off
display_startup_errors = Off
log_errors = On
error_log = /var/log/php/error.log

ASP.NET / IIS (web.config):

<system.web>
  <customErrors mode="On" defaultRedirect="~/error.aspx" />
  <compilation debug="false" />
</system.web>
<system.webServer>
  <httpErrors errorMode="Custom" existingResponse="Replace" />
</system.webServer>

Rails (config/environments/production.rb):

config.consider_all_requests_local = false
config.action_dispatch.show_exceptions = true

Django (settings.py):

DEBUG = False
ALLOWED_HOSTS = ['example.com']

Don't forget custom 404 and 500 templates.

Express — register a final error-handling middleware that hides the stack:

app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).send('Sorry, something went wrong.');
});

Web server defaults — also configure custom error pages at the web server level so a crashed application doesn't fall back to nginx/Apache/IIS default error pages, which themselves leak version info.

Verify by deliberately requesting a non-existent path and an invalid one: curl https://example.com/this-does-not-exist — should show a clean error page, not a stack trace.

Fixed it? Re-run the scan to confirm.

Run scan again