array(1) { [0]=> object(WP_Post)#2062 (24) { ["ID"]=> int(12691) ["post_author"]=> string(2) "23" ["post_date"]=> string(19) "2022-11-16 08:58:14" ["post_date_gmt"]=> string(19) "2022-11-16 08:58:14" ["post_content"]=> string(27380) "

In this section, let's discuss how HTTP headers can be chained with different vulnerabilities. We will see behaviours from reflected values, response errors, misconfigurations and/or configuration conflicts that will be used for a successful header exploitation.


Response header recon

Analysing HTTP response headers can be used to gather information related to the web application. Headers can expose running services, versions, programming languages etc, that are being used within the application. This information provides a good basic structure to adjust payloads and attack methods for future testing or direct HTTP header exploitation.

/ Custom headers

Custom HTTP headers in either the response or request are a great place to start when scanning for a target. If a header is fully customised, in most cases you are more likely to find sensitive information or even leaked pieces of code related to the target using that header.

An attacker would be able to take the custom header used in the system and use it as a keyword for OSINT recon with a possibility of finding source code leaks that point to the application.

In 400 Bad request responses, you can sometimes reveal headers that were not visible in other responses from the server. It's common that the 400 Bad request response contains the default configuration which is not as strict.

GET %2f HTTP/2
Host: redacted.com
HTTP/2 400 Bad Request
Server: Apache
X-Content-Type-Options: nosniff
Accept-Ranges: bytes
Vary: Accept-Encoding
X-REDACTED_Session: <redacted-value>

With some Google Dorking, it was possible to gather a part of the source code used by the system to handle this header. The code was exposed inside a Github repository.

The attacker extracts the custom response header and uses OSINT recon to find its source code

/ Vary header

The Vary header HTTP response header describes the parts of the request message and is most often used to create a cache key. This mitigates attacks such as cache poisoning, where an attacker is able to store malicious content within the cache.

In some cases, the Vary header value(s) indicate that a potential input might be trusted by user input.

GET / HTTP/1.1
Host: redacted.com
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

By simply requesting the web application, we got a response containing the Vary header. The header has the value of the headers X-Forwarded-Host and Origin, let's see if the web application trusts user input.

GET / HTTP/1.1
Host: redacted.com
X-Forwarded-Host: yeswehack.com
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>

The Vary header made it possible for us to discover that the X-Forwarded-Host header is being used and that it trusts user input.

The next step would be to use the X-Forwarded-Host header to check for strange behaviours and responses content. Spoofing, Server-side request forgery (SSRF), browser-powered desync attack and/or finding endpoints that have been forgotten to set the X-Forwarded-Host header within the Vary header could lead to a possible cache poisoning. If any of these problems are detected, it would result in a successful HTTP header exploitation.

/ Fuzzing

When fuzzing for custom X-Headers on a target, a setup example as below can be combined with a dictionary/bruteforce attack. This makes it possible to extract hidden headers that the target uses.

X-Forwarded-{FUZZ}
X-Original-{FUZZ}
X-{COMPANY_NAME}-{FUZZ}

When fuzzing, headers sometimes give false negatives. This can occur if the content delivery network (CDN) and/or backend only response differently when the header is in a particular configuration state.

GET / HTTP/1.1
Host: redacted.com
Forwarded: example.com
HTTP/1.1 200 OK
Content-type: text/html; charset=utf-8
Connection: close
Content-Length: 8426

If we instead configure the Forwarded header to its proper state: Forwarded: for=example.com;host=example.com;proto=https, for=example.com, we get a 500 Internal Server Error response this time.

GET / HTTP/1.1
Host: redacted.com
Forwarded: for=example.com;host=example.com;proto=https, for=23.45.67.89
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, there are other possible configurations that could be used to test against the application.

In this case, the Forwarded: for=example.com;host=example.com;proto=https, for=example.com worked and we can now start to play around with its value to detect why the application gives a 500 Internal Server Error and tries to perform a header exploitation.

(The application was later discovered to be vulnerable to a cache poisoning)

Cache-Poisoned Denial-of-Service

Cache-Poisoned Denial-of-Service (CPDoS) appears when an attacker is able to cache a response that makes the original content of the page unable to view. The poisoned response that is stored in the cache server is then served to all users attempting to render the page, resulting in CPDoS.

/ Akamai error response caching

Let's see how it goes when the feature "Cache Error Responses" is enabled. By default, Akamai edge servers cache error responses with status codes 204, 305, 404, and 405 for 10 seconds according to the documentation. However, in many systems, this behaviour affects almost all error responses with the error status code (Shown in the image below).

This is explained in the documentation "Cache HTTP Error Responses" by Akamai.

Documentation text snippet from "Cache HTTP Error Responses" by Akamai

If an attacker sends an invalid header like ":1 where " is the header, this will in most cases throw a 400 error status code. And if the application has the Cache error response feature enabled, this will be cache and lead to a CPDoS.

GET / HTTP/2
Host: redacted.com
": 1
HTTP/2 400 Bad Request
Vary: Accept-Encoding, Origin
Server-Timing: cdn-cache; desc=HIT
The attacker triggers a 400 status code. Akamai stores the response in its cache and serves it to all users.

/ Cloudflare redirect loop

By default, Cloudflare caches certain HTTP response codes (200, 206, 301, 302, 303, 404, 410) with the Edge Cache TTL when a cache-control directive or expires response header are not present.

Cloudflare uses the header X-Forwarded-Proto to determine the protocol version used by the client. If a cached redirect accepts this header, it's possible to set the X-Forwarded-Proto header value to standard http. When the target sees this value, it will try to redirect to the https version. However, because our request in fact used the https and we just changed the X-Forwarded-Proto to http, we tricked Cloudflare to think it was http. It will therefore redirect https requests to https.

This X-Forwarded-Proto header exploitation results in a cache poisoning that causes the application to execute an infinite loop of redirects until the browser/server reaches the maximum number of redirects and crashes.

Since Cloudflare tends to cache 301, 302, 303, this attack technique becomes more common to see in web applications that use the CDN provided by Cloudflare.

https://www.redacted.com:443
GET /js/index.js HTTP/2
Host: redacted.com
X-Forwarded-Proto: http
HTTP/2 301 Moved Permanently
Location: https://redacted.com/js/index.js
CF-Cache-Status: HIT
The attacker performs a header exploitation by setting the X-Fowarded-Proto to http. This poisoned response is cached and causes an infinity loop.

Akamai - ak_bmsc

The Akamai bot manager service includes the cookie ak_bmsc that is used to differentiate between traffic from humans and bots. When using tools against systems that use this feature, it is common to be blocked very quickly.

However, in some systems, the ak_bmsc is misconfigured and can be manipulated. If an attacker includes the ak_bmsc cookie with a random/none value, it's possible to bypass this protection. This allows an attacker to bypass rate limit and may lead to the authorized/authenticated endpoint being reached by the attacker.

I contacted Akamai personally to verify this behaviour and received the following response:

If you are into bypassing techniques in general, I highly recommend reading the Web application firewall bypass article that explains advanced techniques to bypass the firewall and filter protection for web applications.

Cache collisions

Sometimes, systems use multiple cache headers that are being exposed in the HTTP response. If there are cache configurations from multiple setting departments, it's possible to detect collisions or strange normalization.

In this case, the systems run on Drupal and use two different headers for caching: the X-Drupal-Cache and X-Cache. If the page is cached, it will set its value as HIT, either in both or one of them; otherwise a MISS, if the page is not cached.

Navigating to the root path of the domain, we got a X-Cache: MISS. After the first request we send one more to the same endpoint and we now received a X-Cache: HIT. We now confirmed that the page is cached and it did effect the X-Cache but not the X-Drupal-Cache header.

GET / HTTP/1.1
Host: redacted.com
User-Agent: yeswehack-bugbounty
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, we instead got X-Drupal-Cache HIT without the headers X-Cache and Age being affected.

GET / HTTP/1.1
Host: redacted.com
User-Agent: yeswehack-bugbounty
Authorization: yeswehack
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

If we remove the Authorization header from our request, the X-Drupal-Cache: HIT remains. This indicates that we now triggered two cache services in the application and were able to store our responses in the cache. If we're able to control the response(s), we can perform a successful cache poisoning.

The question is, which one is shown...?

The system tried to isolate the cached content from authorized and unauthorized users, but the cache didn't care about the value given within the Authorization header. The content delivery network (CDN) gave the same cache content to users that had different Authorization values.

GET / HTTP/1.1
Host: redacted.com
User-Agent: yeswehack-bugbounty
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

As long as the application had a stored cached response from the X-Drupal-Cache, it prioritised it.

Unfortunately, I wasn't able to register as an authenticated user to test this further. In theory, this would allow unauthorized users to see cached content that might contain sensitive data that belong to an authorized user. This would result in a similar impact as a Cache deception vulnerability exploitation without user interaction.

The hacker hijacks the stored data from the cache that belongs to an authorized user

Reverse proxy common headers

It's possible to perform a header exploitation by spoofing header value(s) within a reverse proxy, if the setup is misconfigured. This can lead to Improper Access, Server-Side Request Forgery, Denial of service, Stored Open Redirect, Stored Cross-Site Scripting etc.

Below, you will find a collection of the most commonly used headers by reverse proxies. This was discovered by active tests on live systems, analysing public configuration files and by reading the documentation that explains the default configuration for each reverse proxy.

General

X-Forwarded-For
X-Forwarded-Host
X-Forwarded-Proto

Nginx

X-Real-IP
Forwarded

Apache

X-Forwarded-Server
X-Real-IP
Max-Forwards

Envoy

X-Envoy-external-adress
X-Envoy-internal
X-Envoy-Original-Dst-Host

Some of these headers may be recognisable from the previous sections of the article where we used some of them to exploit targets. It is highly recommended that you play with these headers first, as they have a good chance of working against a target.

Remember to adapt your fuzzing and attacks to the services that the target is running. This will most likely improve the exploitation and give a higher success rate!

" ["post_title"]=> string(24) "HTTP Header Exploitation" ["post_excerpt"]=> string(136) "Discover different techniques to achieve HTTP header exploitation by taking advantage of common misconfigurations and caching behaviors." ["post_status"]=> string(7) "publish" ["comment_status"]=> string(6) "closed" ["ping_status"]=> string(6) "closed" ["post_password"]=> string(0) "" ["post_name"]=> string(24) "http-header-exploitation" ["to_ping"]=> string(0) "" ["pinged"]=> string(0) "" ["post_modified"]=> string(19) "2022-11-16 09:15:39" ["post_modified_gmt"]=> string(19) "2022-11-16 09:15:39" ["post_content_filtered"]=> string(0) "" ["post_parent"]=> int(0) ["guid"]=> string(34) "https://blog.yeswehack.com/?p=12691" ["menu_order"]=> int(0) ["post_type"]=> string(4) "post" ["post_mime_type"]=> string(0) "" ["comment_count"]=> string(1) "0" ["filter"]=> string(3) "raw" } }