Keywords
cache miss, cache hit, CDN, cache key, cache rules
Cache
- Web cache is a system that sits between the origin server and the user. When a client requests a static resource, the request is the first directed to the cache. If the cache doesn’t contain a copy of the resource (cache miss), the request is forwarded to the origin server, which processes and responds to the request. The response is then sent to the cache before being sent to the user. When a request for the same static resource is made in the future, the cache serves the stored copy of the response directly to the user (known as a cache hit).
- Cache key is used by cache to determine whether to respond to the client or forward the request to the origin server.
- Cache rules determine what can be cached and for how long. Cache rules are often set up to store static resources, which generally don’t change frequantly and are reused accross multiple pages.
Types of cache rules:
- Static file extension rules
- Static directory rules
- File name rules
- Custom rules
Web Cache Deception Attack
Web Cache Deception Attacks exploit how cache rules are applied.
Detecting cached responses
The X-Cache header provides information about the cache.
X-Cache: hitresponse was served from the cacheX-Cache: missresponse was from the origin server*X-Cache: dynamicdynamic content generatedX-Cache: refreshcache refreshed, was outdated
The Cache-Control header may include a directive taht indicates caching, like public with a max-age higher than 0.
If the response is slower than usual, this may indicate not cached response, vice versa.
Exploiting static extension cache rules
Default behavior for CDN cache rules match by commoen file extensions like .css or .js etc. If there are discrepancies in how the cache and the origin server map the URL path to resources or use delimeters, an attacker may be able to craft a request for a dynamic resource with a static extension that is ignored by the origin server but viewed by the cache.
Path mapping discrepancies
Disrepancies in how the cacehe and origin server map the URL path to resources can result in web cache deception vulnerabilities.
Consider the following example:
http://example.com/user/123/profile/wcd.css
- An origin server using REST-style URL mapping may interpret this as a request for the
/user/123/profileendpoint and returns the profile information for user123, ignoringwcd.cssas a non-significant parameter. - A cache that uses traditional URL mapping may view this as a request for a file named
wcd.csslocated in the/profiledirectory under/user/123. It interprets the URL path as/user/123/profile/wcd.css. If the cache is configured to store responses for requests where the path ends in.css, it would cache and serve the profile information as if it were a CSS file.
Exploiting path mapping discrepancies
To test how the origin server maps the URL path to resources, add an arbitrary path segment to the URL of your target endpoint. If the response still contains the same sensitive data as the base response, it indicates that the origin server abstracts the URL path and ignores the added segment. For example, this is the case if modifying /api/orders/123 to /api/orders/123/foo still returns order information.
To test how the cache maps the URL path to resources, you’ll need to modify the path to attempt to match a cache rule by adding a static extension. For example, update /api/orders/123/foo to /api/orders/123/foo.js. If the response is cached, this indicates:
- That the cache interprets the full URL path with the static extension.
- That there is a cache rule to store responses for requests ending in
.js.
Caches may have rules based on specific static extensions. Try a range of extensions, including .css, .ico, and .exe.
You can then craft a URL that returns a dynamic response that is stored in the cache. Note that this attack is limited to the specific endpoint that you tested, as the origin server often has different abstraction rules for different endpoints.
Delimiter discrepancies
Delimiters specify boundaries between different elements in URLs. The use of characters and strings as delimiters is generally standardized. For example, ? is generally used to separate the URL path from the query string. However, as the URI RFC is quite permissive, variations still occur between different frameworks or technologies.
Discrepancies in how the cache and origin server use characters and strings as delimiters can result in web cache deception vulnerabilities. Consider the example /profile;foo.css:
- The Java Spring framework uses the
;character to add parameters known as matrix variables. An origin server that uses Java Spring would therefore interpret;as a delimiter. It truncates the path after/profileand returns profile information. - Most other frameworks don’t use
;as a delimiter. Therefore, a cache that doesn’t use Java Spring is likely to interpret;and everything after it as part of the path. If the cache has a rule to store responses for requests ending in.css, it might cache and serve the profile information as if it were a CSS file.
The same is true for other characters that are used inconsistently between frameworks or technologies. Consider these requests to an origin server running the Ruby on Rails framework, which uses . as a delimiter to specify the response format:
/profile- This request is processed by the default HTML formatter, which returns the user profile information./profile.css- This request is recognized as a CSS extension. There isn’t a CSS formatter, so the request isn’t accepted and an error is returned./profile.ico- This request uses the.icoextension, which isn’t recognized by Ruby on Rails. The default HTML formatter handles the request and returns the user profile information. In this situation, if the cache is configured to store responses for requests ending in.ico, it would cache and serve the profile information as if it were a static file
Encoded characters may also sometimes be used as delimiters. For example, consider the request /profile%00foo.js:
- The OpenLiteSpeed server uses the encoded null
%00character as a delimiter. An origin server that uses OpenLiteSpeed would therefore interpret the path as/profile. - Most other frameworks respond with an error if
%00is in the URL. However, if the cache uses Akamai or Fastly, it would interpret%00and everything after it as the path.
Exploiting delimiter discrepancies
You may be able to use a delimiter discrepancy to add a static extension to the path that is viewed by the cache, but not the origin server. To do this, you’ll need to identify a character that is used as a delimiter by the origin server but not the cache.
Firstly, find characters that are used as delimiters by the origin server. Start this process by adding an arbitrary string to the URL of your target endpoint. For example, modify /settings/users/list to /settings/users/listaaa. You’ll use this response as a reference when you start testing delimiter characters.
! If the response is identical to the original response, this indicates that the request is being redirected. You’ll need to choose a different endpoint to test.
Next, add a possible delimiter character between the original path and the arbitrary string, for example /settings/users/list;aaa:
- If the response is identical to the base response, this indicates that the
;character is used as a delimiter and the origin server interprets the path as/settings/users/list. - If it matches the response to the path with the arbitrary string, this indicates that the
;character isn’t used as a delimiter and the origin server interprets the path as/settings/users/list;aaa.
Once you’ve identified delimiters that are used by the origin server, test whether they’re also used by the cache. To do this, add a static extension to the end of the path. If the response is cached, this indicates:
- That the cache doesn’t use the delimiter and interprets the full URL path with the static extension.
- That there is a cache rule to store responses for requests ending in
.js.
Make sure to test all ASCII characters and a range of common extensions, including .css, .ico, and .exe.
You can then construct an exploit that triggers the static extension cache rule. For example, consider the payload /settings/users/list;aaa.js. The origin server uses ; as a delimiter:
- The cache interprets the path as:
/settings/users/list;aaa.js - The origin server interprets the path as:
/settings/users/list
The origin server returns the dynamic profile information, which is stored in the cache.
Because delimiters are generally used consistently within each server, you can often use this attack on many different endpoints.
! Some delimiter characters may be processed by the victim’s browser before it forwards the request to the cache. This means that some delimiters can’t be used in an exploit. For example, browsers URL-encode characters like {, }, <, and >, and use # to truncate the path.
If the cache or origin server decodes these characters, it may be possible to use an encoded version in an exploit.
Delimiter decoding discrepancies
Websites sometimes need to send data in the URL that contains characters that have a special meaning within URLs, such as delimiters. To ensure these characters are interpreted as data, they are usually encoded. However, some parsers decode certain characters before processing the URL. If a delimiter character is decoded, it may then be treated as a delimiter, truncating the URL path.
Differences in which delimiter characters are decoded by the cache and origin server can result in discrepancies in how they interpret the URL path, even if they both use the same characters as delimiters. Consider the example /profile%23wcd.css, which uses the URL-encoded # character:
- The origin server decodes
%23to#. It uses#as a delimiter, so it interprets the path as/profileand returns profile information. - The cache also uses the
#character as a delimiter, but doesn’t decode%23. It interprets the path as/profile%23wcd.css. If there is a cache rule for the.cssextension it will store the response.
In addition, some cache servers may decode the URL and then forward the request with the decoded characters. Others first apply cache rules based on the encoded URL, then decode the URL and forward it to the next server. These behaviors can also result in discrepancies in the way cache and origin server interpret the URL path. Consider the example /myaccount%3fwcd.css.
- The cache server applies the cache rules based on the encoded path
/myaccount%3fwcd.cssand decides to store the response as there is a cache rule for the.cssextension. It then decodes%3fto?and forwards the rewritten request to the origin server. - The origin server receives the request
/myaccount?wcd.css. It uses the?character as a delimiter, so it interprets the path as/myaccount.
You may be able to exploit a decoding discrepancy by using an encoded delimiter to add a static extension to the path that is viewed by the cache, but not the origin server.
Use the same testing methodology you used to identify and exploit delimiter discrepancies, but use a range of encoded characters. Make sure that you also test encoded non-printable characters, particularly %00, %0A and %09. If these characters are decoded they can also truncate the URL path.
Exploiting static directory cache rules
It’s common practice for web servers to store static resources in specific directories. Cache rules often target these directories by matching specific URL path prefixes, like /static, /assets, /scripts, or /images. These rules can also be vulnerable to web cache deception.To exploit static directory cache rules, you’ll need to use traversal attacks.
Normalization discrepancies
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.
Discrepancies in how the cache and origin server normalize the URL can enable an attacker to construct a path traversal payload that is interpreted differently by each parser. Consider the example /static/..%2fprofile:
- An origin server that decodes slash characters and resolves dot-segments would normalize the path to
/profileand return profile information. - A cache that doesn’t resolve dot-segments or decode slashes would interpret the path as
/static/..%2fprofile. If the cache stores responses for requests with the/staticprefix, it would cache and serve the profile information.
As shown in the above example, each dot-segment in the path traversal sequence needs to be encoded. Otherwise, the victim’s browser will resolve it before forwarding the request to the cache. Therefore, an exploitable normalization discrepancy requires that either the cache or origin server decodes characters in the path traversal sequence as well as resolving dot-segments.
Detecting normalization by the origin server
To test how the origin server normalizes the URL path, send a request to a non-cacheable resource with a path traversal sequence and an arbitrary directory at the start of the path. To choose a non-cacheable resource, look for a non-idempotent method like POST. For example, modify /profile to /aaa/..%2fprofile:
- If the response matches the base response and returns the profile information, this indicates that the path has been interpreted as
/profile. The origin server decodes the slash and resolves the dot-segment. - If the response doesn’t match the base response, for example returning a
404error message, this indicates that the path has been interpreted as/aaa/..%2fprofile. The origin server either doesn’t decode the slash or resolve the dot-segment.
! When testing for normalization, start by encoding only the second slash in the dot-segment. This is important because some CDNs match the slash following the static directory prefix.
You can 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.