HTTP headers control how browsers and servers interact with data so vulnerabilities in them may affect the whole application. Headers can reveal running services, software versions, and programming languages employed by the application. Such details help in tailoring payloads and selecting appropriate attack methods for further testing or direct HTTP header exploitation.
Real-world examples are used to illustrate how header analysis contributes to vulnerability discovery.
Custom headers are the headers made exclusively for that specific application by their developer team. Usually their names start with X- but now it is more common to see regular names — we can still verify if a header is custom or standard.
Why Pay Attention?
There are multiple reasons why custom headers can be an interesting finding for VAPT. They are not standardized headers defined by RFC, so they lack strict specifications which the VAPT team should be paying attention to.
- Tech stack fingerprinting: frameworks, proxies, auth layers. Faster attack planning.
- Auth logic insight: headers tied to roles, tokens, internal trust paths.
- Session handling clues: custom session IDs, user mapping, tracking logic.
- Environment leaks: staging, dev, internal hostnames. Those are softer targets.
- Attack chaining fuel: combine header behavior with IDOR, CORS, cache poisoning, auth bypass.
- OSINT wins: find backend code, configs, or comments explaining exactly how the app thinks.
How to Spot Them?
While headers with X- at the beginning are obvious, others might be much harder. There are still tricks to do it — when we check headers of the site we must pay attention to these potential clues.
- Baseline first: know common headers. Anything outside that set demands attention.
- Diff responses: normal 200 vs 400/403/500. New headers popping up = custom.
- Trigger parser errors: malformed paths, bad encoding, weird methods. Sloppy responses talk more.
- Proxy tools: Burp, ZAP. Sort headers alphabetically. The odd names jump out.
- Repetition test: hit another site. If the header only exists here, it's custom.
- Name smell test: words like
internal,debug,env,user,role,sessionalmost certainly are app-specific headers.
After we identify a suspicious header name we must do a quick search to find information about it. The easiest way is to use Google Dorking. Let's look at the example below.
HTTP/2 400 Bad Request
Server: Apache
X-Content-Type-Options: nosniff
Accept-Ranges: bytes
Vary: Accept-Encoding
X-REDACTED_Session: <redacted-value>
For instance, source code in GitHub repos sometimes includes elements that interact with custom headers. So if you identified the header X-Internal-Token: secret-value, you could use Google to search (dork) "X-Internal-Token" site:github.com, which might reveal hardcoded references in backend code, sample configs or .env files, or middleware and route handlers checking for the header.
Now we will get more technical.
Example: ipapi.co
-
First let's curl to get the response from the server:
BASHcurl -v https://ipapi.co/json
curl verbose output — response headers are prefixed with <We are receiving a response from this request. We are interested in the headers, which start with
<in verbose mode. In this case our headers are:HTTP< HTTP/2 200 < date: Sun, 28 Dec 2025 12:26:01 GMT < content-type: application/json < content-length: 724 < nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800} < server: cloudflare < allow: POST, OPTIONS, GET, OPTIONS, HEAD < x-frame-options: DENY < vary: Host, origin < x-content-type-options: nosniff < referrer-policy: same-origin < cross-origin-opener-policy: same-origin < content-security-policy-report-only: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.stripe.com https://*.paddle.com https://www.google.com https://www.gstatic.com https://maps.gstatic.com https://maps.googleapis.com https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; style-src 'self' 'unsafe-inline' https://*.paddle.com https://fonts.gstatic.com https://fonts.googleapis.com; img-src 'self' data: https://ipapi.co https://maps.gstatic.com https://maps.googleapis.com https://*.stripe.com; font-src 'self' data: https://fonts.gstatic.com https://fonts.googleapis.com; frame-src 'self' https://www.google.com https://*.stripe.com https://*.paddle.com https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/; connect-src 'self' https://ipapi.co/ https://*.paddle.com https://*.stripe.com https://maps.googleapis.com https://www.google.com/recaptcha/; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; < cf-cache-status: DYNAMIC < report-to: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=sQ4siVO1FBc%2BTiiiF0PbdpmCfyGC9qZysZyNKy3Nybcu2b5X8q9RqDleMmDLVEu2WK5lMVEgAGgekWTbBll5LCUu7cgo"}]} < cf-ray: 9b510b2e3fc2dfb0-EVNAs we can see, besides
X--prefixed headers we also havecf-ray— which is technically a custom header since it's not standardized by RFC, however in VAPT it's still not application-defined because it's used across all infrastructures running Cloudflare.In case we don't want to see other data and see headers only, we should use
-Iinstead of-v:BASHcurl -I https://ipapi.co/json -
Now let's see what we can find about this header using Google Dorking.
If we search "cf-ray" we will find out that this is a Cloudflare HTTP header, otherwise known as Ray ID — a hashed value that encodes information about the data center and the visitor's request.
Example: pokeapi.co
Being standardized by RFC doesn't mean it's pointless for VAPT, even if the header is technically harmless.
-
Run:
BASHcurl -I https://pokeapi.co/api/v2/pokemon/pikachuWe will see our headers:
HTTPHTTP/2 200 date: Sun, 28 Dec 2025 12:55:10 GMT content-type: application/json; charset=utf-8 access-control-allow-origin: * cache-control: public, max-age=86400, s-maxage=86400 nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800} etag: W/"3fa24-wbt3/8cXlN2gHJ6CC6+3DtEKJMc" server: cloudflare strict-transport-security: max-age=31556926 traceparent: 00-56faeedd9ecb60dfdb9e51b1388e11f1-e62c36a9e5de6889-00 x-cloud-trace-context: 56faeedd9ecb60dfdb9e51b1388e11f1/16585691631032625289 x-country-code: DK x-orig-accept-language: fr-CH,fr-FR;q=0.9,fr;q=0.8,en-US;q=0.7,en;q=0.6 x-powered-by: Express x-served-by: cache-bma-essb1270021-BMA x-cache: HIT x-cache-hits: 0 x-timer: S1765974536.323588,VS0,VE1 vary: Accept-Encoding,cookie,need-authorization, x-fh-requested-host, accept-encoding alt-svc: h3=":443"; ma=86400 report-to: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=molvjxUnL%2FCIqKdhT9VYD5TeJlSmNtTBH2qW64VNUAijalKvMXuIrG7oIbq1faT%2BZEDgpTcmriHgsbsc460F8YmdmScyBaZd"}]} age: 1420 cf-cache-status: HIT cf-ray: 9b5135e07e62e540-EVNHere let's focus on the
traceparentheader. -
traceparentis a distributed tracing header. Harmless by itself, but if the dev was sloppy it can leak:- internal service structure
- correlation IDs tied to logs
- sometimes other trace headers get echoed back or logged publicly
It's not a vulnerability, more like a fingerprint / recon helper. Depending on the specific case this can be a serious flag.
Example: OpenAI API
Now we will try a harder example.
-
First let's curl our target to check its response:
BASHcurl -I -H "Authorization: Bearer fk-fakekey123" https://api.openai.com/v1/modelsHere we can find our headers:
HTTPHTTP/2 401 date: Sun, 28 Dec 2025 13:14:16 GMT content-type: application/json content-length: 242 www-authenticate: Bearer realm="OpenAI API" openai-version: 2020-10-01 x-request-id: req_5a0f57ef8f4b4d9aa9c14502d0624925 openai-processing-ms: 4 x-envoy-upstream-service-time: 6 x-openai-proxy-wasm: v0.1 cf-cache-status: DYNAMIC set-cookie: __cf_bm=p4ywGRx4xaMlaxuz4UXqaKnbm8ZY8yCmW0OqqjgvuhU-1766927656-1.0.1.1-wAy4aXPRqSQltBMGwu2W4V0sASGrOCJq84YhCvjYpMFj93RTOamHjCpveseEt06O0BuzSaN4E5U5bLpq8vhKGCiDoZBVBBw7ld0U1pyy_j4; path=/; expires=Sun, 28-Dec-25 13:44:16 GMT; domain=.api.openai.com; HttpOnly; Secure; SameSite=None strict-transport-security: max-age=31536000; includeSubDomains; preload x-content-type-options: nosniff set-cookie: _cfuvid=xes5NHC5Vd__5iekCtUB9jbdZE0ElhGSdrfIJ9arLOg-1766927656089-0.0.1.1-604800000; path=/; domain=.api.openai.com; HttpOnly; Secure; SameSite=None server: cloudflare cf-ray: 9b5151ce5905dfb0-EVN alt-svc: h3=":443"; ma=86400 -
We need to identify custom headers. Some have app-specific names for OpenAI, some outright start with
X-. Our custom headers are:openai-version: 2020-10-01x-request-id: req_5a0f57ef8f4b4d9aa9c14502d0624925openai-processing-ms: 4x-envoy-upstream-service-time: 6x-openai-proxy-wasm: v0.1
-
Finally we will perform a search to find data on these headers. Using Google Dorking we search our newly found headers.
-
openai-version: 2020-10-01
By searching:
BASH"openai-version: 2020-10-01" site:github.com -inurl:issuesWe identify that this is the version of the API we were sending the request to. So now we know which version to target when looking for vulnerabilities.
-
x-request-id
By searching:
BASH"x-request-id" site:github.com "openai"We can find how that header is used and how it's interacting with our application of interest. Here we managed to find the
openai-pythonrepository where we can see how the header is implemented — all object responses in the SDK expose a_request_idproperty sourced from this header, so you can quickly log failing requests and report them back to OpenAI.We can even find a specific code snippet by searching:
BASH"x-request-id" site:github.com inurl:openai-python filetype:pyPYTHONlog.debug("request_id: %s", response.headers.get("x-request-id")) -
openai-processing-ms
By searching:
BASHsite:github.com "openai-processing-ms"This represents server-side processing time in milliseconds. In our case it's 4ms because it was a quick auth rejection — real AI queries spike much higher.
To hunt for recent bugs, try:
BASHsite:github.com "openai-processing-ms" inurl:issues "2025" "dec"This can surface recent reported bugs that may still be exploitable.
And so on — you can try this now with the remaining two custom headers. No vulnerabilities are claimed for this specific target, but in different cases close to this example they absolutely can be found.
-
Now we see repos where those headers are used and can read the code, comments, questions, and issues around them. This will help us understand what they do and get us on the blood-trail.
Now using the knowledge we have about HTTP headers we will cover other topics related to this subject. This section covers techniques for exploiting weaknesses in HTTP headers, ranging from custom header abuse to cache poisoning and proxy misconfigurations.
Vary HTTP Header
The Vary HTTP response header tells caches which request headers must be considered when deciding whether a cached response can be reused. Without Vary, a cache will assume same URL = same response. That assumption is often wrong. Vary corrects it by saying: same URL + same listed header values = same response.
How does it do that? Caches normally key on [HTTP method] + [URL]. But when Vary exists, the cache key becomes:
[HTTP method] + [URL] + [values of headers listed in Vary]
For example:
Vary: X-Forwarded-Host, Origin
This means the cache will be reused if: Cache key = / + value of X-Forwarded-Host + value of Origin
- It does not validate headers
- It does not block headers
- It does not make things safe by itself
- It does not prevent the server from trusting bad input
Red Team Perspective
If a header appears in the Vary response header, it means the application uses that header when generating the response. That directly implies the server backend is reading that header and it is part of the application logic — which is a signal for the Red Team to keep an eye on it.
Example: X-Forwarded-Host Reflection
-
First we look at the server's response:
HTTPHTTP/1.1 200 OK Server: Apache/2.4.37 Vary: X-Forwarded-Host,Origin Content-type: text/html; charset=utf-8 Connection: close Content-Length: 11512The response contains the
Varyheader listingX-Forwarded-HostandOrigin. We will now leverage this to test if the web application trusts user input from those headers. -
We will send:
BASHcurl -i https://example.com/ -H "X-Forwarded-Host: testing.com"We receive this response from the server:
HTTPHTTP/1.1 200 OK Server: Apache/2.4.37 Vary: X-Forwarded-Host,Origin Content-type: text/html; charset=utf-8 Connection: close Content-Length: 11509 <!DOCTYPE html> <html> ... <script src="https://testing/assets/js/vendor/jquery-ui.custom.min.js"></script> ... </html>Sure enough, the response confirmed that the
X-Forwarded-Hostheader is being reflected in the HTML and is likely being trusted by the application.This discovery paves the way to using
X-Forwarded-Hostto check for unexpected behaviors. Techniques such as spoofing, server-side request forgery (SSRF), and browser-powered desync attacks can potentially lead to cache poisoning. It might be possible to poison the cache if some responses useX-Forwarded-Hostwithout including it inVary. Cache Poisoning will be discussed in the next chapter of Security Headers.