Web cache deception enables an attacker to trick a web cache into storing sensitive, dynamic content. The attacker persuades a victim to visit a malicious URL, inducing the victim’s browser to make an ambiguous request for sensitive content. The cache misinterprets this as a request for a static resource and stores the response. The attacker can then request the same URL to access the cached response, gaining unauthorized access to private information.
- Web cache deception (PortSwigger)
- Web cache deception lab delimiter list (PortSwigger)
- Cache Deception (HackTricks)
Not the same as Web Cache Poisoning.
Web cache poisoning manipulates cache keys to inject malicious content into a cached response, which is then served to other users.
Web cache deception exploits cache rules to trick the cache into storing sensitive or private content, which the attacker can then access.
Add this Cache Deception BCheck (GitHub) in Burp Suite.
Web Cache
User <—> Cache <—> Website
Cache keys
Used by the cache to decide if it will serve the cache response or forward the original request to the web server. Typically, this includes the URL path and query parameters, but it can also include a variety of other elements like headers and content type.
Cache rules
Determine what can be cached and for how long. Cache rules are often set up to store static resources.
Examples
- Static file extension rules, like “.css”, “js”
- Static directory rules, like a prefix to target specific directories that contain only static resources, for example /static or /assets.
- File name rules, like robots.txt and favicon.ico
- Any custom rules based on other criteria, such as URL parameters or dynamic analysis.
Testing
Web cache deception attacks exploit how cache rules are applied.
Use a cache buster! Each request you send must have a different cache key. Use Param miner (Settings -> Add dynamic cachebuster).
Burp Scanner automatically detects web cache deception vulnerabilities that are caused by path mapping discrepancies during audits. You can also use the Web Cache Deception Scanner BApp to detect misconfigured web caches.
Add a custom column in the Proxy’s HTTP history tab to display the cache (adapt to the HTTP header name):
return requestResponse.response().headerValue(“X-Cache”);
Identify endpoint with sensitive information
Review dynamic responses in Burp Suite to identify sensitive information that is not cached. The goal will be to put this information in the cache. Focus on HTTP methods GET, HEAD and OPTIONS.
Identify a discrepancy in how the cache and origin server parse the URL path
Find a discrepancy in how the cache and origin server parse the URL path.
Path mapping
Returns the same information
GET /sensitive-api
GET /sensitive-api/whatever?cachebuster=1 HTTP/2
Path delimiter
Use two requests that return different responses.
GET /sensitive-api // Returns HTTP 200
GET /sensitive-apiwhatever // Returns HTTP 404
Send the request to the Intruder module. Use this list: Web cache deception lab delimiter list (PortSwigger). Also test non-printable characters: %00, %0A, %09.Look for responses that return the original response (HTTP 200).
Do NOT select “URL-encode these characters” in the Payloads tab.
GET /sensitive-api§delimiter§whatever HTTP/2
Normalize path
Normalization involves converting various representations of URL paths into a standardized format. This sometimes includes decoding encoded characters and resolving dot-segments, but this varies significantly from parser to parser.
/static/..%2fprofile
/profile // Origin server decodes the slash + resolves dot-segment
/static/..%2fprofile // Cache does NOT decode slash + NOT resolves dot-segments
If the cache stores responses for requests with the /static prefix, it would cache and serve the profile information.
Detect normalization by the origin server: Send a request to a non-cacheable resource with a path traversal sequence and an arbitrary directory at the start of the path. If the response matches /profile, the origin server decodes the slash and resolves the dot-segment.
Start by encoding only the second slash in the dot-segment. Some CDNs match the slash following the static directory prefix.
Also try encoding the full path traversal sequence, or encoding a dot instead of the slash. This can sometimes impact whether the parser decodes the sequence.
POST /idontexist/..%2fprofile HTTP/2
[...]
Detect normalization by the cache server: Identify potential static directories with common prefixes and that are cached (scripts, images, CSS). Resend a cached request with a path traversal sequence and arbitrary directory at the start. If the response is still cached, this may indicate that the cache has normalized the path to /assets/js/somescript.js.
GET /idontexist/...%2assets/js/somescript.js HTTP/2
To confirm that the cache rule is based on the static directory (vs another cache rule like extensions), replace the path after the directory prefix with an arbitrary string. If the response is still cached, this confirms the cache rule is based on the /assets prefix.
GET /assets/idontexist HTTP/2
Identify cache rules
Static file extension rules
Check for cache rules on file extension, like “.css”, “js”
Static directory rules
Like a prefix to target specific directories that contain only static resources.
/static
/assets
/scripts
/images
File name rules
Like robots.txt and favicon.ico
Custom rules
Based on other criteria, such as URL parameters or dynamic analysis.
Craft a malicious URL that uses the discrepancy
Trick the cache into storing a dynamic response. When the victim accesses the URL, their response is stored in the cache.
Using Burp, you can then send a request to the same URL to fetch the cached response containing the victim’s data. The payload can be a web page or email like this:
Hello there! ...
<img src="https://vulnerable-app/sensitive-api;whatever.js?cachebuster=victim" />
Path mapping discrepancy + Static extension cache rule
When these 2 URL paths return the sensitive information:
GET /sensitive-api/123
GET /sensitive-api/123/whatever
Cache rule “.js” are cached:
GET /sensitive-api/123/whatever.js
Path delimiter discrepancy + Static extension cache rule
Responses must be different:
GET /sensitive-api
GET /sensitive-apiaaaaaaaa
Cache rule “.js” are cached:
GET /sensitive-api;whatever.js
GET /sensitive-api.whatever.js
GET /sensitive-api%00whatever.js
Normalization by origin server + Static directory cache rule
The origin server resolves encoded dot-segments, but the cache does not:
/<static-directory-prefix>/..%2f<dynamic-path>
/assets/..%2fprofile
Normalization by the cache server + Static directory cache rule
The cache server resolves encoded dot-segments but the origin server does not. You also need to identify a path delimiter (like “;”) and add it to the payload after the dynamic path. NOTE: “%2f%2e%2e%2f” = “/../”.
URL-encode all characters in the path traversal sequence.
Ignore the #
character as delimiter. It can’t be used for an exploit as the victim’s browser will use it as a delimiter before forwarding the request to the cache. Use %23 instead.
/<dynamic-path><path-delimiter>%2f%2e%2e%2f<static-directory-prefix>
/profile;%2f%2e%2e%2fstatic
/my-account%23victim%2f%2e%2e%2fresources
/my-account%23victim%2f%2e%2e%2fresources?cachebuster