Back to scan results
Check 15 of 45

Error Page Disclosure

We independently send controlled, non-state-changing error requests over HTTP port 80 and HTTPS port 443, then inspect each response for stack traces, full file paths, framework banners, unfiltered ASP.NET remoting exceptions, and other internal detail that a normal user should never see.

What this check probes

We send a benign-but-malformed request that is likely to trigger an exception path in common stacks:

  • A unique, non-existent ASP.NET-style path with malformed query characters, sufficient to exercise error handling without changing application data.
  • A unique, nonexistent .rem path requested with plain GET and no query string or body. This safely asks ASP.NET's remoting handler to reject an unpublished URI before any object dispatch, deserialization, application method, or login can occur.
  • A matching nonexistent .aspx path is used as a control. The check fails only when the .rem response is HTTP 500, plain text, begins with the concrete System.Runtime.Remoting.RemotingException type, and the control does not return that remoting signature.
  • The probe is sent independently to plaintext HTTP port 80 and HTTPS port 443.
  • Redirects are not followed for these requests, so an HTTP redirect cannot hide an error body returned directly by port 80.

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/....
  • Detailed framework error pages — Symfony Whoops, ASP.NET YSOD pages that expose exception/source/stack/version details, Rails dev mode, Django debug pages, Laravel Ignition, and Flask debug output. A generic ASP.NET application-error banner without internal detail is reported as a warning rather than a failure.
  • ASP.NET remoting custom errors — an unfiltered concrete remoting exception on the .rem trigger proves that the legacy handler is returning server exception information to a remote caller. A filtered message, ordinary 404, redirect, access denial, or generic page does not fail.
  • File path leakage — absolute filesystem paths like /var/www/html/app/Controllers/..., files with any plausible extension such as C:\inetpub\site\handler.cgi, and directory-only paths such as C:\inetpub\site.
  • 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.runtime.remoting>
  <customErrors mode="on" />
</system.runtime.remoting>
<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.');
});

Legacy ASP.NET remoting — if the application does not use .NET Remoting over HTTP, remove the IIS mappings for *.rem and *.soap. If it is genuinely required, keep both ASP.NET and remoting custom errors enabled for remote callers.

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 both services with non-existent paths: curl -i http://example.com/this-does-not-exist and curl -i https://example.com/this-does-not-exist — each response should redirect cleanly or show a sanitised error page, never a stack trace. A bodyless curl -i https://example.com/pciscan-remoting-test.rem must not return the concrete System.Runtime.Remoting.RemotingException type to a remote client.

Fixed it? Re-run the scan to confirm.

Run scan again