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

Field Value
Product prometheus/client_java
Affected Version 1.8.0 (main branch); all prior 1.x releases using prometheus-metrics-exporter-httpserver are likely affected
Component prometheus-metrics-exporter-httpserver
Vulnerable File prometheus-metrics-exporter-httpserver/src/main/java/io/prometheus/metrics/exporter/httpserver/HttpExchangeAdapter.java
Vulnerable Lines 103-141
CWE CWE-209: Generation of Error Message Containing Sensitive Information
CVSS 3.1 Score 5.3 (Medium)
CVSS 3.1 Vector AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Commit (HEAD) a017f809
Discoverer Security 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

private void sendErrorResponseWithStackTrace(Exception requestHandlerException) {
    if (!responseSent) {
      responseSent = true;
      try {
        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        printWriter.write("An Exception occurred while scraping metrics: ");
        requestHandlerException.printStackTrace(new PrintWriter(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 (IOException errorWriterException) {
        // ...
      }
    }
}

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):

@Override
public void handleException(IOException e) throws IOException {
    sendErrorResponseWithStackTrace(e);
}

@Override
public void handleException(RuntimeException e) {
    sendErrorResponseWithStackTrace(e);
}

4.3 Trigger Point

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

public void handleRequest(PrometheusHttpExchange exchange) throws IOException {
    try {
      // ... scrape metrics
    } catch (IOException e) {
      exchange.handleException(e);
    } catch (RuntimeException e) {
      exchange.handleException(e);
    } finally {
      exchange.close();
    }
}

5. CVSS 3.1 Detailed Breakdown

Metric Value Justification
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:

Category Example
Library version Package naming conventions and class structures identify the exact client_java version
Internal class names io.prometheus.metrics.config.Util, io.prometheus.metrics.config.HistogramProperties, etc.
Internal method names parseDoubleList, collect, scrape
Source file names and line numbers Util.java:90, HistogramProperties.java:45, Histogram.java:198
Configuration values Exception messages from PrometheusPropertiesException embed the invalid configuration value
JVM internals Thread stack frames expose JVM version, internal classes, and runtime behavior
Application structure Custom collector class names, application framework stack frames
File system paths Absolute paths may appear in exception messages (e.g., properties file locations)
Package structure Full 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:

import io.prometheus.metrics.core.metrics.Histogram;
import io.prometheus.metrics.exporter.httpserver.HTTPServer;
import io.prometheus.metrics.model.registry.PrometheusRegistry;

public class VulnerableApp {
    public static void main(String[] args) throws Exception {
        Histogram histogram = Histogram.builder()
            .name("example_histogram")
            .help("An example histogram")
            .register();

        HTTPServer server = 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

Module Affected Reason
prometheus-metrics-exporter-httpserver Yes HttpExchangeAdapter.sendErrorResponseWithStackTrace() writes full stack traces to the HTTP response body

Unaffected Modules

Module Affected Reason
prometheus-metrics-exporter-servlet-jakarta No handleException() re-throws the exception to the servlet container (throw e;)
prometheus-metrics-exporter-servlet-javax No handleException() 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.

private void sendErrorResponseWithStackTrace(Exception requestHandlerException) {
    if (!responseSent) {
      responseSent = true;
      try {
        // Log the full stack trace server-side only
        Logger.getLogger(this.getClass().getName())
            .log(Level.SEVERE,
                "An Exception occurred while scraping metrics.",
                requestHandlerException);

        // Send a generic error message to the client
        byte[] 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 (IOException errorWriterException) {
        // ... 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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions