返回

Web Cache的那些事

0x01 简介

Web Cache

web缓存主要是为了解决网页延迟,提高带宽利用而出现的技术。 web缓存可以分为浏览器缓存、代理服务器缓存、CDN缓存、数据库缓存、DNS缓存等。

浏览器缓存:也即客户端缓存,把请求过的文件保存在本地,再次访问时直接从本地取出。保存的技术有HTTP缓存Web StorageApp CacheIndexedDBFile SystemAPi。判断头部有ExpiresCache-control: max-ageLast-ModifiedEtag。 代理服务器/CDN缓存:共享缓存,多用户可同时访问。 数据库缓存:为数据库的增删改查做了缓存,主要代表为memecacheredis。 DNS缓存:缓存域名记录信息。

Web Cahce poisoning

web缓存投毒,主要的场景是在代理服务器/CDN。 下面用一张图来展示CDN场景下的网站访问。

从图中我们可以看出在CDN模式下,多用户访问站点的情况。 那么CDN是依据什么来给用户提供缓存的呢? 最简单的一个例子就是针对于不同地域的用户,需要提供的缓存是不同的。 由此引出一个叫做Cache Key(缓存键)的概念,CDN依此来判断是否返回缓存。

  • Cache Key Vary是HTTP响应头部中的一个字段,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。 例如:
Vary: User-Agent

针对于不同的类型客户端的访问,pc和移动端的ua不同,就会返回不同的缓存。 如下图(当Accept-Encoding不同时,返回不同的缓存)

当然CDN的缓存方式不会这么简单,Cache key不会由单个头部特征构成,通常由多个特征共同构成,其中可能由CDN自己的特征,并且一般不会用vary返回。(缓存投毒原因之一) 用一个cloudflare的官方示例说明下

GET /logo.jpg HTTP/1.1
Host: www.cloudflare.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
Accept: image/jpg

默认的cache keycloudfare Zone ID、scheme、hostname、path进行组合而得到。 那么该请求的cache key为 1234:https://www.cloudflare.com/logo.jpg ,组合方式也由CDN决定,也可以自己进行配置。


再看一个请求

图中的橙色部分被作为缓存键进行匹配,那么对于两个地域的用户就会出现页面显示问题
使用en语言的用户,就会返回pl语言响应情况。 这里的language就是非缓存键,顾名思义,非缓存键就是缓存键之外的参数,这样的话就会引入安全问题,如果非缓存键能够被攻击利用的话,那么被投毒的响应就会返回给正常用户(关键)。

那么你就会想,这不科学啊?为啥CDN没有按照Vary的进行缓存,这其实也是各个厂商对于标准实现差异导致的,总是想夹带私货(功能扩展之类)。就像Smuggle一样。😂


那么如何去发现一个页面是否存在缓存投毒的可能呢? 方法论:

1. 寻找非缓存键
2. 分析非缓存键带来的风险,着重注意是否页面的缓存中存在一些风险参数,例如:文件后缀,Content-type、route(路由)、响应状态码、响应头。
3. 随后注入缓存,观察效果,找到目标页面

手动进行缓存投毒测试时,由于缓存响应可能会忽略非缓存键,这样会导致测试无法进行。因此cache-buster(缓存破坏)就很重要了,通过找到一个缓存键,每次请求都改变缓存键,从而使得每次请求都不会被缓存。

手动测试还存在一个风险,就是在测试过程中会影响正常用户访问,所以每次请求最好使用cache-buster来解决这个问题。

发现缓存投毒可以使用param-miner,PortSwigger官方出品。

危害

  • XSS存储,投毒每个会浏览页面的人
  • 投毒资源文件,比如js、css

Case

接下来使用一系列的案例来对缓存投毒场景进行说明

HTTP头部非缓存键

简单HTTP头部

先看到一个请求

响应包出现了x-cache: hit代表命中了缓存,真实场景下只能依据不同请求包的响应内容来推测。随后就可以使用param-mine进行测试,发现X-Fowarded-Host就是他的非缓存键。 携带X-Fowarded-Host发送请求包
从返回包中可以看出,X-Fowarded-Host会修改tracking.js的引用地址,那么就引用任意地址的脚本了。
这里要问题要说明下,从结果上来看X-Fowarded-Host像是缓存键,因为每次改变都会产生新的缓存。其实由于X-Fowarded-Host会直接改变响应内容导致,一定要区分清楚。


看一个请求

从响应包中可以发现Cookie的值可以出现再缓存中,那么这里的利用方式就可以和上面相同


多头部

看到下面这个请求

不像前2个响应一样,响应包中没有出现明显的可以篡改的特征,都使用了相对路径。 是可篡改主要看在请求或响应中存在相同值。 这里使用Param-mine进行探测,发现当同时出现X-Forwarded-HostX-Forwarded-Scheme的时候,页面会进行重定向
不过这里跳转的协议是https,可能是和服务器是https连接有关。 所以可以构造第三方恶意地址,使其跳转。这里选择对js文件进行跳转。


未知头部

看到下面这个请求

可以发现存在Vary: User-Agent头部,内容中存在携带host引用的js资源 先使用param miner进行头部测试,发现x-host字段是可以替换host的非缓存键
头部添加x-host进行测试
这里还有个问题要解决,就是如果要针对特定的用户(例如管理员)进行投毒攻击的话,需要知道他的User-Agent才行。这个时候需要让受害者去访问下攻击者的资源就行了,可以找类似于评论这种功能,看看是不是可以提交富文本内容即可。
这样就获取到了他的ua,之后按照之前的构造链构造即可

HTTP请求行

请求参数未被缓存

看到下面的请求

测试参数看看,是不是会被缓存
从不同的参数值都返回新缓存来看,参数是会被缓存的,然后实际上会存在隐藏参数缓存的情况。使用param miner进行测试。
发现utm_content是被缓存的,验证方式很简单,只要修改utm_content的值,如果缓存命中的话,那就是缓存参数了。 随后可以发现参数会在响应包中出现,那么就可以进行投毒了。
这里使用的origin属于cache破坏,避免测试是直接投毒了首页缓存,避免业务故障。


看到下面这个请求

同样也存在这utm_conten参数未被缓存的情况。
但是请求参数会被编码,直接通过响应的缓存思路就行不通,这里看到一个js调用。
发现callback参数会被当作方法名传入响应中。那么怎么投毒/js/geolocate.js?callback=setCountryCookie呢。在Ruby on Rails中有一个参数解析漏洞,以下两个请求效果一样。

/?param1=test&param2=foo
/?param1=test;param2=foo

这样解析会导致很有很多问题,其中有一个参数覆盖的比较特别,结合utm_content这种隐藏的非缓存参数。
对于Rails来说他会把第1个请求中的第2个callback作为值,但是缓存服务器会使用第1个callback作为缓存键。 所以构造如下请求即可完成对缓存投毒

Fat Get

varnish cache中有一种情况,允许GET请求携带请求体,对于参数相同的请求,后端服务器可能会采用请求体中而非请求行中的。但是缓存使用的是请求行中的进行缓存。

可以看到此时的缓存键是X-Cache-Key: /js/geolocate.js?callback=setCountryCookie$$


URL normalization

有一种URL规范化的情况,由于缓存服务器把URL中的特殊符号进行规范化后缓存,导致正常的请求会返回异常请求的响应。 例如火狐的更新请求:

在第一个请求之后,缓存服务器把URL规范化之后记录了正常请求的URL作为缓存键,这样就会导致原有正常的更新请求变成了301。

直接用浏览器访问是无法进行xss反射的,但是可以利用缓存。


缓存键注入

缓存键构成一般都会由多个元素构成,如果存在可控元素的话,可以结合跨域origin进行CRLF注入。 看到下面这个案例

访问首页,经过2次302跳转来到真正的页面。
还有一个请求/js/localize.js?lang=en&cors=0
修改cors=1,并且添加origin头
可以进行CRLF注入。
需要解决的问题有几个

1、正常访问的cors=0,如何让受害者访问时,cors=1
2、缓存键是什么

关键是要知道缓存键的组成,这里使用Pragma: x-get-cache-key(案例设置的,实际情况可以看缓存服务器的文档或者手动测试)。

X-Cache-Key: /js/localize.js?lang=en&cors=1$$origin=http://123.com结构是url_path$$origin=origon_value

接下来就是解决问题1了,在/login/?lang=en内容中,对/js/localize.js?lang=en&cors=0进行了引用,并且这里的lang=en是来自于请求中的。那么可以想到如果在/login?lang=en的跳转可以修改cors值并且缓存键不修改的情况,这个投毒链就可行了。 首先来看看直接修改cors值,缓存键会不会被修改。

根据缓存键的构成就能够猜测到会被修改。那么如何在url中修改参数,但是不影响缓存键呢? 这个时候就要寻找非缓存参数了,utm_content当然需要试试,实际上在真正测试时,可能存在其它的参数。
Bingo.这里的跳转地址变了,但是缓存键没有变。

构造payload的时候要注意,$的数量还有编码以及缓存时间
GET /js/localize.js?lang=en?utm_content=x&cors=1
Origin: x%0d%0aContent-Length:%208%0d%0a%0d%0aalert(1)$$$$

GET /login?lang=en?utm_content=x%26cors=1$$Origin=x%250d%250aContent-Length:%208%250d%250a%250d%250aalert(1)$$%23

DOM漏洞&缓存投毒

看到下面的请求

响应脚本中存在一个对data.host数据引用,如果data可控就可以造成一个dom型的漏洞。 使用param miner进行测试,看看是否头部存在可改变的非缓存键。 发现了x-forwarded-hostorigin和缓存存在关系。 通过手动测试可以发现,x-forwarded-host为非缓存键,origin为缓存键。
接着就是分析initGeoLocate函数和/resources/json/geolocate.json文件的内容了 initGeoLocate函数在/resources/js/geolocate.js可以找到定义。
/resources/json/geolocate.json文件内容
这个功能就是一个送货功能,国家是可控的。
接下来只要篡改data.host,并且修改geolocate.json即可。(缓存投毒时,要注意需要携带Cookie的session,如果响应包中存在set-cookie字段,则该响应不可缓存)

混合投毒(DOM漏洞+重定向)

看到下面这个请求

和之前的DOM投毒相同,可以看到data.host依然被引用。 直接分析initTranslations/resources/json/translations.json文件,同时使用param miner进行非缓存键的测试。
这里是类似一个网页翻译的功能。默认是使用英语。目标是要对会选择英语页面的用户进行投毒,执行alert(document.cookie)。根据代码来看,可以选择一个语言进行投毒,所以就要想办法把用户访问的页面切换到其它被投毒语言页面中。语言文件的codename都没法执行xss,一个是setAttribute,一个是innerText

经过param miner测试,x-original-url、x-forwarded-host、origin都会触发缓存变化。 x-forwarded-host、origin的作用类似。x-original-url可以覆盖目标的URL值,X-Rewrite-URL也有类似的效果。

要达到投毒用户的目的,可以分成首页被设置为其它语言/用户切换语言、其它语言被投毒2个思路。 在进行语言切换的时候,会产生302重定向的请求,这里如果可以投毒,可以让用户在进行语言切换时,切换到其它被投毒的语言。

这个时候使用x-original-url即可
但是有个Set-Cookie,说明响应无法缓存。 观察主页的时候发现,资源的引用有时候使用了\。服务器会对重定向的地址中的\变为/
这里可以把x-original-url: /setlang/es?修改为x-original-url: /setlang\es?
可以看到没有Set-Cookie。接下来就是把首页设置为其它语言,思路也类似,使用x-original-url,把首页投毒成其它语言。
最后投毒其它语言页面。
这样一个攻击链就完成了。

One’s Storm

  1. Web Cache Deception Web缓存欺骗,类似于RPO攻击,利用缓存服务器和后端服务器处理差异导致。

    主要在于欺骗受害者访问一个不存在的静态资源,例如/home.do/logo.png,对于后端服务器来说,这个文件不存在,请求会返回/home.do的响应内容,但是对于缓存服务器来说这个扩展是png,是静态资源,可以缓存,那么就会创建home.do目录,然后缓存一个logo.png,内容是/home.do的内容。 这种情况可以用来窃取CSRF Token、用户个人信息之类,Paypal就存在过相关问题。(有时间再详细写下吧看😂)

  2. Tips

  • 缓存判断的三种方法:头部Hit、动态内容、响应计时

参考:

(ง •_•)ง 2021-01-24 23:34:45 星期日

Licensed under CC BY-NC-SA 4.0