4.21 Review Error Page Disclosure
4.21 - Review Error Page Disclosure
Error handling can leak implementation details when stack traces, framework versions, or internal paths reach the HTTP response. Review catch blocks, global exception handlers, API error serializers, and deployment settings for debug mode. Confirm production returns generic messages while detailed diagnostics stay in server-side logs.
What This Vulnerability Is
Information disclosure through errors happens when the application answers a failure with more detail than the user should see. Default servlet containers, reverse proxies, and frameworks often ship verbose error pages that name server software and version. Application code may print exceptions directly to the response writer or include SQL fragments, file paths, and library versions in JSON error bodies.
The unsafe assumption is that only legitimate users will trigger errors. Attackers probe inputs to force exceptions and harvest clues for targeted exploits. This behavior relates to CWE-209 (Generation of Error Message Containing Sensitive Information) and CWE-497 (Exposure of Sensitive System Information to an Unauthorized Control Sphere).
Vulnerability Characteristics (Where to Identify Them)
| Signal | Where to look |
|---|---|
| Feature type | API error JSON, catch blocks, global exception handlers, health endpoints on failure |
| Response sinks | printStackTrace, traceback.format_exc(), returning ex.Message or ex.StackTrace |
| Debug flags | DEBUG=True, app.debug, developer exception page enabled in production deploy paths |
| Missing error pages | No catch-all /error route; container defaults expose version banners |
| Partial handlers | 404/500 pages configured without general fallback hiding stack traces |
| Version banners | Server, X-Powered-By, framework version strings in production configs |
Attack Payloads
Use these in authorized tests to trigger failures and inspect responses. Goal is to see whether stack traces, paths, or versions leak—not to exploit injection.
Pattern 1: Type and format errors
GET /api/users/not-a-number HTTP/1.1
POST /api/order {"quantity": "abc"} HTTP/1.1
Content-Type: application/json
{"id": null}
Pattern 2: Missing resources and path probes
GET /api/users/999999999 HTTP/1.1
GET /../../../etc/passwd HTTP/1.1
GET /%00/report HTTP/1.1
Pattern 3: Database and query failures
GET /search?q=' HTTP/1.1
GET /report?sort=invalid_column HTTP/1.1
May return SQL syntax fragments, table names, or ORM query text in the body.
Pattern 4: Unhandled exceptions in business logic
POST /transfer {"amount": -1, "to": ""} HTTP/1.1
GET /export?format=__invalid__ HTTP/1.1
Divide-by-zero, null dereference, or assertion failures if not caught by a generic handler.
Pattern 5: Debug and health endpoints
GET /error?debug=1 HTTP/1.1
GET /__debug__/ HTTP/1.1
TRACE / HTTP/1.1
Language-Specific Sinks and Dangerous APIs
Find code paths that write exception details, stack traces, or framework diagnostics into HTTP responses.
Python
import traceback
return {"trace": traceback.format_exc()}, 500
app.run(debug=True)
# Flask/Werkzeug debugger, Django DEBUG=True in prod settings
Django: DEBUG template with stack trace. FastAPI: unhandled exception returns default detail with path.
Java
e.printStackTrace(response.getWriter());
return ResponseEntity.status(500).body(e.toString());
server.error.include-stacktrace=always
Spring Boot: server.error.include-message, include-binding-errors. Servlet container default error pages.
C
catch (Exception ex) {
return Content(ex.ToString());
}
app.UseDeveloperExceptionPage(); // enabled in Production
ASP.NET Core: DeveloperExceptionPageMiddleware, IncludeErrorDetail=true on APIs.
JavaScript (Node.js)
res.status(500).json({ error: err.message, stack: err.stack });
app.use((err, req, res, next) => res.send(err.stack));
process.env.NODE_ENV = 'development';
Express error handler returning err.stack; Next.js dev overlay config in production build.
Go
http.Error(w, err.Error(), 500)
fmt.Fprintf(w, "%+v\n", debug.Stack())
panic without recover middleware; log.Printf then echoing err to client.
PHP
catch (Exception $e) { echo $e->getTraceAsString(); }
ini_set('display_errors', '1');
Laravel APP_DEBUG=true in deployed .env.
Reverse proxy / server config
# Default nginx/Apache 502 pages with version
proxy_intercept_errors off; # upstream stack body passed through
Sample Vulnerable Code in Python
import traceback
import logging
import uuid
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
logger = logging.getLogger("reports")
@app.get("/reports/run")
async def run_report(request: Request):
try:
return await generate_report()
except Exception as exc:
# Stack trace returned to client — reveals paths, ORM queries, and library versions
return JSONResponse(
{"error": str(exc), "trace": traceback.format_exc()},
status_code=500,
)
Step-by-Step Review Walkthrough
- Locate every catch block and exception handler that writes to HTTP responses or API error payloads.
- Trace the FastAPI report route. In the sample,
traceback.format_exc()in JSON responses exposes internal paths and ORM details. - Check framework and server configuration.
web.xmlerror pages, Springserver.error.*, DjangoDEBUG, FastAPI exception handlers, and Express error middleware. - Trace whether stack traces reach clients in any environment via
printStackTrace,e.message, or full exception serialization. - Review API layers that serialize exceptions into JSON (
detail,stack, embedded file paths). - Confirm a catch-all error page exists so undefined HTTP error codes do not fall back to container defaults.
- Inspect health and diagnostic endpoints that echo environment variables or build metadata on failure.
- Verify logging sends stack traces to secure server logs, not to the user agent.
Risk Impact Analysis
Targeted exploitation. Stack traces reveal framework versions, file paths, and SQL fragments attackers use to craft precise exploits.
Architecture mapping. Error details expose internal module names, dependency versions, and deployment layout.
Credential and query leakage. Database exceptions may include connection hints, table names, or parameter values.
Compliance exposure. Verbose errors conflict with policies requiring minimal client-facing diagnostic data.
Support confusion. Raw exceptions shown to end users erode trust and complicate incident triage.
Vulnerable Examples in Other Languages
Java
try {
processOrder(orderId);
} catch (Exception e) {
e.printStackTrace(response.getWriter());
}
C
catch (Exception ex)
{
return StatusCode(500, new { message = ex.Message, stack = ex.StackTrace });
}
Go
func handler(w http.ResponseWriter, r *http.Request) {
if err := runJob(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "%+v", err)
return
}
}
Fix: Safer Patterns and Libraries to Use
Python
Register error handlers that return generic messages. Log full exceptions server-side with a correlation ID.
import traceback
import logging
import uuid
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
logger = logging.getLogger("reports")
@app.exception_handler(Exception)
async def handle_error(request, exc):
request_id = str(uuid.uuid4())
logger.exception("unhandled error request_id=%s", request_id)
return JSONResponse(
{"error": "An internal error occurred.", "request_id": request_id},
status_code=500,
)
@app.get("/reports/run")
async def run_report():
return await generate_report()
Important: Keep Django DEBUG=False in production. Use handler500 and logging settings for details. Never return traceback.format_exc() to users.
Java
Map errors to static pages in web.xml. Configure Spring Boot to exclude stack traces in production.
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/error/generic.html</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/error/500.html</location>
</error-page>
# application-prod.properties
server.error.include-stacktrace=never
server.error.include-message=never
server.error.include-exception=false
@ControllerAdvice
public class GlobalErrors {
private static final Logger log = LoggerFactory.getLogger(GlobalErrors.class);
@ExceptionHandler(Exception.class)
public ResponseEntity<ProblemDetail> handle(Exception ex) {
String id = UUID.randomUUID().toString();
log.error("request_id={}", id, ex);
ProblemDetail body = ProblemDetail.forStatus(HttpStatus.INTERNAL_SERVER_ERROR);
body.setTitle("Internal error");
body.setProperty("request_id", id);
return ResponseEntity.status(500).body(body);
}
}
Important: Log full exceptions with SLF4J. Return RFC 7807 Problem Details without internal paths in public APIs.
C
Use exception handler middleware in production. Restrict developer exception page to Development environment.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
app.UseHsts();
}
app.Map("/error", () => Results.Problem(
title: "An error occurred.",
statusCode: StatusCodes.Status500InternalServerError));
catch (Exception ex)
{
var requestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
_logger.LogError(ex, "Unhandled error {RequestId}", requestId);
return StatusCode(500, new { error = "An internal error occurred.", requestId });
}
Important: Set DetailedErrors=false in production. Map to ProblemDetails without stack traces.
Go
Return generic HTTP errors. Log errors with structured logging and recover from panics in middleware.
func handler(w http.ResponseWriter, r *http.Request) {
if err := runJob(); err != nil {
requestID := middleware.RequestIDFromContext(r.Context())
slog.Error("job failed", "request_id", requestID, "err", err)
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
slog.Error("panic", "recover", rec)
http.Error(w, "internal error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
Important: Disable Gin/Echo debug mode in production. Use nginx or Envoy custom error pages as defense in depth.
Verify During Review
- No stack traces, SQL errors, or file paths appear in HTTP responses in production configurations.
- Catch-all and per-status error pages are configured so container defaults never leak versions.
- Framework debug modes and detailed error middleware are disabled outside local development.
- APIs return stable, minimal error shapes; support staff use correlation IDs tied to server logs.
- Exception logging is complete server-side but excludes secrets already covered in logging review.
- Security headers and custom error content are defense in depth, not the only control.
Reference
- CWE-209: Generation of Error Message Containing Sensitive Information
- CWE-497: Exposure of Sensitive System Information
- OWASP Error Handling Cheat Sheet
- RFC 7807: Problem Details for HTTP APIs
- Django — Error reporting
- Flask — Error handling
- Spring Boot — Error handling properties
- ASP.NET Core — Handle errors
- Go log/slog package