Skip to content

Security Report: Full Stack Trace Disclosure in HTTP Error Responses #2283

Description

@manqingzhou

Security Report: Full Stack Trace Disclosure in HTTP Error Responses

1. Vulnerability Summary

FieldValue
Productprometheus/client_java
Affected Version1.8.0 (main branch); all prior 1.x releases using prometheus-metrics-exporter-httpserver are likely affected
Componentprometheus-metrics-exporter-httpserver
Vulnerable Fileprometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java
Vulnerable Lines103-141
CWECWE-209: Generation of Error Message Containing Sensitive Information
CVSS 3.1 Score5.3 (Medium)
CVSS 3.1 VectorAV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Commit (HEAD)a017f809
DiscovererSecurity Research

2. Vulnerability Description

The HttpExchangeAdapter class in the prometheus-metrics-exporter-httpserver module exposes full Java stack traces to remote, unauthenticated HTTP clients when any exception occurs during a Prometheus metrics scrape. The method sendErrorResponseWithStackTrace() serializes the complete exception stack trace -- including exception type, message, chained causes, class names, method names, file names, and line numbers -- into the HTTP 500 response body and sends it directly to the requesting client.

The /metrics endpoint served by the built-in HTTPServer is accessible without authentication by default. No configuration option exists to suppress stack trace output in error responses. Any exception raised during the scrape cycle (configuration errors, metric collection failures, runtime exceptions from custom collectors) triggers the disclosure.

This is a classic information disclosure vulnerability. Stack traces are considered sensitive information because they reveal internal implementation details that assist attackers in reconnaissance and targeted exploitation.


3. Affected Component Architecture

The vulnerability sits at the intersection of three components:

PrometheusScrapeHandler.handleRequest()
|
| catches IOException / RuntimeException
v
PrometheusHttpExchange.handleException() (interface)
|
| implemented by
v
HttpExchangeAdapter.sendErrorResponseWithStackTrace() <-- VULNERABILITY
|
v
HTTP 500 Response with full stack trace sent to client

Key observation: The servlet-based adapters (prometheus-metrics-exporter-servlet-jakarta and prometheus-metrics-exporter-servlet-javax) are not affected. Their handleException() implementations re-throw the exception to the servlet container rather than serializing stack traces into the response. This vulnerability is specific to the standalone HTTPServer exporter module.


4. Vulnerable Code

4.1 Primary Vulnerable Method

File:prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java
Lines: 103-141

privatevoidsendErrorResponseWithStackTrace(ExceptionrequestHandlerException) {
if (!responseSent) {
responseSent = true;
try {
StringWriterstringWriter = newStringWriter();
PrintWriterprintWriter = newPrintWriter(stringWriter);
printWriter.write("An Exception occurred while scraping metrics: ");
requestHandlerException.printStackTrace(newPrintWriter(printWriter));
byte[] stackTrace = stringWriter.toString().getBytes(StandardCharsets.UTF_8);
httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
httpExchange.sendResponseHeaders(500, stackTrace.length);
httpExchange.getResponseBody().write(stackTrace);
} catch (IOExceptionerrorWriterException) {
// ...
}
}
}

The critical issue is the call to requestHandlerException.printStackTrace(new PrintWriter(printWriter)) on line 110, which dumps the entire stack trace into a StringWriter whose contents are then written verbatim to the HTTP response body on line 114.

4.2 Call Sites

The vulnerable method is invoked from the two handleException() overloads (lines 93-101):

@OverridepublicvoidhandleException(IOExceptione) throwsIOException {
sendErrorResponseWithStackTrace(e);
}
@OverridepublicvoidhandleException(RuntimeExceptione) {
sendErrorResponseWithStackTrace(e);
}

4.3 Trigger Point

PrometheusScrapeHandler.handleRequest() (lines 99-102) routes all caught exceptions through the exchange's handleException():

publicvoidhandleRequest(PrometheusHttpExchangeexchange) throwsIOException {
try {
// ... scrape metrics
} catch (IOExceptione) {
exchange.handleException(e);
} catch (RuntimeExceptione) {
exchange.handleException(e);
} finally {
exchange.close();
}
}

5. CVSS 3.1 Detailed Breakdown

MetricValueJustification
Attack Vector (AV)Network (N)The /metrics HTTP endpoint is network-accessible. The HTTPServer binds to a network socket (default: all interfaces).
Attack Complexity (AC)Low (L)No special conditions required. The attacker only needs network access to the metrics port. An exception must occur during scraping, which can be triggered via environment variable misconfiguration or may happen naturally.
Privileges Required (PR)None (N)The HTTPServer does not require authentication by default. The Authenticator is an optional builder parameter that is null unless explicitly configured.
User Interaction (UI)None (N)No user interaction is needed. The attacker sends an HTTP request directly to the endpoint.
Scope (S)Unchanged (U)The vulnerability affects only the confidentiality of the vulnerable component; it does not cross trust boundaries to impact other components.
Confidentiality (C)Low (L)The disclosed information (stack traces, class names, library versions, configuration values, file paths) provides valuable reconnaissance data but does not directly expose credentials, secrets, or user data.
Integrity (I)None (N)No data modification occurs.
Availability (A)None (N)No denial-of-service impact.

CVSS 3.1 Vector String:CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CVSS 3.1 Base Score:5.3 (Medium)


6. Information Disclosed in Stack Traces

A stack trace exposed via this vulnerability can reveal the following categories of sensitive information:

CategoryExample
Library versionPackage naming conventions and class structures identify the exact client_java version
Internal class namesio.prometheus.metrics.config.Util, io.prometheus.metrics.config.HistogramProperties, etc.
Internal method namesparseDoubleList, collect, scrape
Source file names and line numbersUtil.java:90, HistogramProperties.java:45, Histogram.java:198
Configuration valuesException messages from PrometheusPropertiesException embed the invalid configuration value
JVM internalsThread stack frames expose JVM version, internal classes, and runtime behavior
Application structureCustom collector class names, application framework stack frames
File system pathsAbsolute paths may appear in exception messages (e.g., properties file locations)
Package structureFull package hierarchy of the application and its dependencies

7. Proof of Concept

7.1 Prerequisites

  • A Java application using prometheus-metrics-exporter-httpserver with the built-in HTTPServer
  • Network access to the metrics HTTP port

7.2 Reproduction Steps

Step 1: Configure an invalid property to guarantee an exception during scrape.

export IO_PROMETHEUS_METRICS_HISTOGRAM_CLASSIC_UPPER_BOUNDS=invalid

This environment variable sets an invalid value for the histogram upper bounds configuration. When a Histogram metric is collected, HistogramProperties attempts to parse this value, throwing a PrometheusPropertiesException.

Step 2: Start the application with a Prometheus HTTP server.

Any Java application using the HTTPServer builder pattern:

importio.prometheus.metrics.core.metrics.Histogram;
importio.prometheus.metrics.exporter.httpserver.HTTPServer;
importio.prometheus.metrics.model.registry.PrometheusRegistry;
publicclassVulnerableApp {
publicstaticvoidmain(String[] args) throwsException {
Histogramhistogram = Histogram.builder()
.name("example_histogram")
.help("An example histogram")
.register();
HTTPServerserver = HTTPServer.builder()
.port(9400)
.buildAndStart();
System.out.println("Server started on port " + server.getPort());
Thread.currentThread().join();
}
}

Step 3: Request the metrics endpoint.

curl -s http://localhost:9400/metrics

Step 4: Observe the full stack trace in the response.

Expected HTTP 500 response body:

An Exception occurred while scraping metrics: io.prometheus.metrics.config.PrometheusPropertiesException:
io.prometheus.metrics.histogram.classic_upper_bounds=invalid: Expecting comma separated list of double values
at io.prometheus.metrics.config.Util.parseDoubleList(Util.java:90)
at io.prometheus.metrics.config.HistogramProperties.<init>(HistogramProperties.java:45)
at io.prometheus.metrics.core.metrics.Histogram.collect(Histogram.java:198)
at io.prometheus.metrics.model.registry.PrometheusRegistry.scrape(PrometheusRegistry.java:67)
at io.prometheus.metrics.exporter.common.PrometheusScrapeHandler.scrape(PrometheusScrapeHandler.java:143)
at io.prometheus.metrics.exporter.common.PrometheusScrapeHandler.handleRequest(PrometheusScrapeHandler.java:63)
at io.prometheus.metrics.exporter.httpserver.MetricsHandler.handle(MetricsHandler.java:25)
at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:77)
at sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:82)
at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:80)
at sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:692)
at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:77)
...

7.3 Attack Scenario Without Misconfiguration

Even without attacker-controlled misconfiguration, stack traces are disclosed when:

  1. A custom Collector registered in the PrometheusRegistry throws a RuntimeException during collect() (e.g., NullPointerException, IllegalStateException).
  2. A transient I/O error occurs during metric serialization.
  3. An internal consistency error is triggered by concurrent metric updates.

In production environments, these conditions are not uncommon, making the vulnerability exploitable through passive observation as well as active triggering.


8. Impact Analysis

8.1 Direct Impact

  • Information Disclosure: Every error during metrics scraping sends implementation details to the network client. In environments where the metrics port is exposed (common in Kubernetes, cloud deployments, and monitoring infrastructure), any network-adjacent attacker can collect this information.

8.2 Reconnaissance Value

The disclosed stack traces provide an attacker with:

  1. Dependency mapping: Exact library names and versions enable identification of known CVEs in dependencies.
  2. Code path analysis: Method names and line numbers reveal execution flow, helping identify injection points or logic flaws.
  3. Configuration inference: Exception messages from PrometheusPropertiesException and similar classes expose configuration parameters and their values.
  4. Technology fingerprinting: JVM version, application server type, and framework stack frames identify the technology stack.

8.3 Deployment Context

The HTTPServer component is commonly used in:

  • Standalone Java applications without a servlet container
  • Java agents for JVM instrumentation
  • Microservices exposing metrics alongside application traffic

In these deployments, the metrics port is frequently accessible to broader network segments than intended, amplifying the exposure risk.


9. Scope Clarification

Affected Module

ModuleAffectedReason
prometheus-metrics-exporter-httpserverYesHttpExchangeAdapter.sendErrorResponseWithStackTrace() writes full stack traces to the HTTP response body

Unaffected Modules

ModuleAffectedReason
prometheus-metrics-exporter-servlet-jakartaNohandleException() re-throws the exception to the servlet container (throw e;)
prometheus-metrics-exporter-servlet-javaxNohandleException() re-throws the exception to the servlet container (throw e;)

10. Recommended Remediation

10.1 Immediate Fix

Replace the stack trace serialization in sendErrorResponseWithStackTrace() with a generic error message. Log the full stack trace server-side for debugging purposes.

privatevoidsendErrorResponseWithStackTrace(ExceptionrequestHandlerException) {
if (!responseSent) {
responseSent = true;
try {
// Log the full stack trace server-side onlyLogger.getLogger(this.getClass().getName())
.log(Level.SEVERE,
"An Exception occurred while scraping metrics.",
requestHandlerException);
// Send a generic error message to the clientbyte[] message = "An internal error occurred while scraping metrics.\n"
.getBytes(StandardCharsets.UTF_8);
httpExchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
httpExchange.sendResponseHeaders(500, message.length);
httpExchange.getResponseBody().write(message);
} catch (IOExceptionerrorWriterException) {
// ... existing error handling
}
}
}

10.2 Configurable Error Verbosity

Introduce a configuration property (e.g., io.prometheus.metrics.exporter.httpserver.debug) that controls whether detailed error information is included in HTTP responses. Default to false (generic messages only).

10.3 Defense in Depth

  • Document that the metrics HTTP port should be protected by network-level access controls (firewall rules, network policies).
  • Encourage use of the Authenticator builder option when the metrics port is exposed to untrusted networks.
  • Consider implementing the same error-handling pattern used by the servlet adapters, which delegate exception handling to the container rather than serializing stack traces.

11. References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions