Did you know that manipulating a single HTTP header can unlock high-impact security flaws hidden within a web application?
HTTP headers control how browsers and servers interact with data, so these vulnerabilities can affect the entire application stack.
In this comprehensive guide, we share techniques – both simple and advanced – for exploiting vulnerabilities in HTTP headers, ranging from abusing custom headers to leveraging cache poisoning and reverse proxy misconfigurations.
With real-world examples and easy-to-follow tips, this article will help you spot weaknesses faster and boost your Bug Bounty and ethical hacking skills.
Let's dive in and uncover how minor modifications to request headers can reveal major security issues!
Outline
- HTTP response header recon
- Custom HTTP headers
- The Vary HTTP header
- Fuzzing for HTTP headers
- Cache-Poisoned Denial-of-Service
- CPDoS via Akamai: caching error responses
- CPDoS on Cloudflare: infinite redirect loop
- Abusing Akamai's ak_bmsc cookie
- Exploiting cache collisions
- Common HTTP headers used by reverse proxies
- General HTTP headers used by reverse proxies
- Nginx HTTP headers
- Apache HTTP headers
- Envoy HTTP headers
- Mitigation & best practices for HTTP header exploitation
- Enforce strict header validation
- Optimise caching policies
- Harden reverse proxy configurations
- Conclusion
HTTP response header recon
Analysing HTTP response headers provides valuable information about the web 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.
Custom HTTP headers
Inspecting custom HTTP headers in both responses and requests is a valuable first step when scanning a target. Customised headers often reveal sensitive information – such as internal system information or framework identifiers – or inadvertently expose pieces of code that can be leveraged for further reconnaissance.
An attacker can harness custom headers as keywords for open source intelligence (OSINT) recon, potentially uncovering source code leaks and other technical information related to the application.
Pay particular attention to '400 Bad Request' responses, as they often expose headers that are not typically visible in server responses. These error responses often include default configurations as well as internal debugging information and naming conventions:
GET %2f HTTP/2
Host: redacted.com
Response:
HTTP/2 400 Bad Request
Server: Apache
X-Content-Type-Options: nosniff
Accept-Ranges: bytes
Vary: Accept-Encoding
X-REDACTED_Session: <redacted-value>
Performing Google dorking can potentially retrieve fragments of source code that handle custom headers. 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 (ie, dork) "X-Internal-Token" site:github.com”, which might then reveal hardcoded references in backend code, sample configs or .env files, or middleware or route handlers checking for the header.
The Vary HTTP header
The vary
HTTP response header specifies which parts of the request message should be considered when creating a cache key. This mitigates attacks such as cache poisoning, where an attacker manipulates the cache to serve malicious content to other users.
In some cases, the Vary
header values indicate whether a given user input is being trusted or reflected by the application. This may signal a potential attack surface for further testing.
Initial request:
GET / HTTP/1.1
Host: redacted.com
Server response:
HTTP/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: 11512
As you can see above, the response contains the Vary
header, which comprises header values X-Forwarded-Host
and Origin
. Let’s leverage this information to test if the web application trusts user input:
GET / HTTP/1.1
Host: redacted.com
X-Forwarded-Host: yeswehack.com
Server response:
HTTP/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://yeswehack.com/assets/js/vendor/jquery-ui.custom.min.js"></script>
...
</html>
Sure enough, the response confirmed that the X-Forwarded-Host
header is being reflected in the HTML and is likely being trusted by the application.
This discovery paves the way to using the X-Forwarded-Host
header to check for unexpected behaviours. 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 use X-Forwarded-Host
without including it in Vary
.
Fuzzing for HTTP headers
When fuzzing a target for custom X-Headers
, the following setup can be combined with dictionary or brute-force attacks to extract hidden headers from the target:
X-Forwarded-{FUZZ}
X-Original-{FUZZ}
X-{COMPANY_NAME}-{FUZZ}
Keep in mind that header-based fuzzing can sometimes produce false negatives. This can occur if the content delivery network (CDN) and/or backend only respond differently under specific header configurations.
For example, a basic request using a simple Forwarded header may result in a normal response:
GET / HTTP/1.1
Host: redacted.com
Forwarded: example.com
Response:
HTTP/1.1 200 OK
Content-type: text/html; charset=utf-8
Connection: close
Content-Length: 8426
However, configuring the Forwarded
header more precisely can trigger a 500 Internal Server Error:
GET / HTTP/1.1
Host: redacted.com
Forwarded: for=example.com;host=example.com;proto=https
Response:
HTTP/1.1 500 Internal Server Error
Content-type: text/html; charset=utf-8
Connection: close
Content-Length: 764
If the above Forwarded
header configuration does not work either, alternative configurations are available for testing against the application (see the Nginx documentation on forwarded headers).
In the example above, the forwarded header for=example.com;host=example.com;proto=https
successfully triggered a 500 Internal Server Error. This allowed us to further experiment with the header’s value to determine the cause of the error, and later discover a cache poisoning vulnerability affecting the application.
Cache-Poisoned Denial-of-Service
A Cache-Poisoned Denial-of-Service (CPDoS) is achieved when an attacker caches a malicious or malformed response that causes the legitimate page content to become inaccessible. Once stored in the cache, the poisoned response is served to all users attempting to access the affected resource – effectively resulting in a denial of service.
CPDoS via Akamai: caching error responses
Let’s examine a real-world scenario where the cache error responses feature is enabled. By default, Akamai edge servers cache error responses with status codes 204, 305, 404, and 405 for 10 seconds. However, this behaviour can extend to other status codes, depending on system configuration.
Consult Akamai’s cache HTTP error responses documentation for more details.
If an attacker sends an invalid header – for example :1 – the server will typically return a 400 Bad Request error:
GET / HTTP/2
Host: redacted.com
": 1
HTTP/2 400 Bad Request
Vary: Accept-Encoding, Origin
Server-Timing: cdn-cache; desc=HIT
When the cache error responses feature is enabled, this error response may be cached, resulting in CPDoS for other users regardless of whether their request is legitimate or not.
CPDoS on Cloudflare: infinite redirect loop
By default, Cloudflare caches certain HTTP response codes (200, 206, 301, 302, 303, 404, 410) with the Edge Cache TTL, unless overridden by Cache-Control or Expires headers. Cloudflare also uses the header X-Forwarded-Proto to determine the client's protocol version (HTTP vs HTTPS).
If a cached redirect accepts this header, it becomes possible to set the X-Forwarded-Proto header value to http. When the target detects this value, it attempts to redirect to the https version.
However, since our request was made using https, this manipulation causes Cloudflare to mistakenly interpret the request as http, resulting in a redirection back to https. This misconfiguration triggers an infinite redirect loop that eventually leads to a crash once the maximum number of redirects is reached. Given that Cloudflare typically caches 301, 302 and 303 responses, this attack method is frequently observed in web applications that utilise Cloudflare’s CDN.
If an attacker sets:
https://www.redacted.com:443
Then on an HTTPS request, the backend may attempt to redirect to HTTPS again, causing an infinite redirect loop:
GET /js/index.js HTTP/2
Host: redacted.com
X-Forwarded-Proto: http
Response:
HTTP/2 301 Moved Permanently
Location: https://redacted.com/js/index.js
CF-Cache-Status: HIT
Because the redirect is cached, every user hitting that route may be trapped in a loop - eventually resulting in a browser error after too many redirects.
Abusing Akamai's ak_bmsc cookie
The Akamai Bot Manager service uses the ak_bmsc cookie to differentiate between human and bot traffic. Automated tools are typically blocked quickly. However, misconfigured implementations can be vulnerable to the ak_bmsc
cookie being bypassed.
If an attacker supplies the misconfigured ak_bmsc
cookie with a random or null value, the attacker could evade rate limits and potentially gain access to authenticated endpoints. I contacted Akamai to confirm this behaviour and received the following response:
For those interested in bypassing techniques, I recommend reading our Web Application Firewall Bypass article, which details advanced methods for circumventing firewall and filter protections in web applications.
Exploiting cache collisions
Modern web architectures often implement several caching layers managed by different teams, and expose multiple cache headers in their HTTP responses. When caching is configured inconsistently, security vulnerabilities can arise through cache header collisions and inconsistent normalisation.
In the example below, a Drupal-based system uses two caching headers: X-Drupal-Cache
(application-level cache) and X-Cache
(proxy or VPN-level cache). When the page is successfully cached, these headers display a status of HIT; if not, they display MISS.
Our first request returned X-Cache: HIT
, confirming that the page had been cached, although the X-Drupal-Cache
header returned MISS:
GET / HTTP/1.1
Host: redacted.com
User-Agent: yeswehack-bugbounty
Response:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 37531
X-Drupal-Cache: MISS
X-Content-Type-Options: nosniff
Cache-Control: public, max-age=0
Vary: Cookie,Accept-Encoding
Age: 6
X-Cache: HIT
When we added the Authorization
header to our request, both headers were confirmed as cached (HIT), while the Age
header was unaffected:
GET / HTTP/1.1
Host: redacted.com
User-Agent: yeswehack-bugbounty
Authorization: yeswehack
Response:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 37531
X-Drupal-Cache: HIT
X-Content-Type-Options: nosniff
Cache-Control: public, max-age=0
Vary: Cookie,Accept-Encoding
Age: 12
X-Cache HIT
When the Authorization header was then removed from the request, the X-Drupal-Cache: HIT status persisted:
GET / HTTP/1.1
Host: redacted.com
User-Agent: yeswehack-bugbounty
Response:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 37531
X-Drupal-Cache: HIT
X-Content-Type-Options: nosniff
Cache-Control: public, max-age=0
Vary: Cookie,Accept-Encoding
Age: 5
X-Cache HIT
This indicates that two distinct caching mechanisms were triggered, allowing the responses to be stored in the cache under different conditions. It shows that, if an attacker can manipulate these responses, they can poison the cache with content intended only for authenticated users.
This raises the critical question: which cached response is being served?
The system attempted to distinguish between authorised and unauthorised users, but the caching mechanism disregarded the Authorization
header. Consequently, the CDN served the same cached content to users regardless of their Authorization
values.
The application (X-Drupal-Cache
) prioritized its cached response, even when the CDN (X-Cache
) stored a different version.
Unfortunately, I was unable to register as an authenticated user to further test this behaviour. In theory, this behaviour could allow unauthenticated users to access cached content containing sensitive information belonging to an authenticated user – in other words, a cache deception vulnerability without user interaction.
Common HTTP headers used by reverse proxies
The values of headers used by reverse proxies can be spoofed in ways that lead to improper access, server-side request forgery (SSRF), Denial of Service (DoS), stored open redirect, and stored cross-site scripting (XSS), among other vulnerabilities.
Below is a collection of the most commonly used headers by reverse proxies. This information was gathered from active tests on live systems, by analysing public configuration files and by reading documentation that details default behaviour.
General HTTP headers used by reverse proxies
X-Forwarded-For
X-Forwarded-Host
X-Forwarded-Proto
Nginx HTTP headers
X-Real-IP
Forwarded
Apache HTTP headers
X-Forwarded-Server
X-Real-IP
Max-Forwards
Envoy HTTP headers
X-Envoy-External-Address
X-Envoy-Internal
X-Envoy-Original-Dst-Host
Some of these headers have been successfully abused in real-world exploits. It’s highly recommended to experiment with testing these headers as they often yield successful results against misconfigured targets.
Mitigations and defences against HTTP header exploitation
The following strategies can harden your web application and reduce the risk of header-based attacks:
Enforce strict header validation
- Sanitise and validate inputs: Always validate header values against strict format expectations. Reject or sanitize any input that deviates from these standards.
- Limit header exposure: Remove or obscure unnecessary headers. This reduces risk of exposing sensitive information, especially via error responses.
Optimise caching policies
- Separate caches for authenticated and unauthenticated users: Configure CDN and backend servers to distinguish between users. This prevents scenarios where sensitive content might be served from a misconfigured cache.
- Restrict error caching: Disable or tightly control caching for error responses, particularly those with HTTP 400 or 500 status codes. This measure helps to prevent cache poisoning incidents that can result in denial of service.
Harden reverse proxy configurations
- Audit header trust policies: Ensure reverse proxies like Nginx, Apache or Envoy are configured to validate incoming headers and to avoid blindly trusting headers like X-Forwarded-For or X-Real-IP.
- Implement layered defences: Deploy additional security measures, such as Web Application Firewalls (WAFs), to detect and block abnormal header patterns and potential header misuse.
Conclusion
So now you’ve learned some of the varied ways that HTTP headers can be leveraged to identify and exploit weaknesses in web applications. Whether you’re abusing custom headers, the Vary
header or reverse proxy misconfigurations, it’s clear that even subtle header modifications can result in significant security vulnerabilities.
By understanding these techniques, you can broaden your exploitation skillset, while recognising the importance of robust security practices such as meticulous header validation and effective caching strategies.
As in any area of web security, attack methodologies continually evolve as defenders develop mitigations for prevailing techniques. Whether you’re new to this field or a seasoned professional, it’s therefore wise to keep up to date with the latest developments through resources such as Dojo, our CTF training platform, and the PortSwigger Web Security Academy.
So stay up to date, keep hacking ethically and happy hunting!