AppCache(Application Cache)是HTML5提供的应用缓存机制的一组接口,在各大浏览器(曾经)都有实现。它通过用户指定的manifest缓存特定页面从而使这些页面即使在离线状态的都可以访问,提高了访问速度且减轻了服务器的压力。
但随着技术发展,像Service Worker这种新的客户端缓存技术逐渐有了取代AppCache这种老大哥技术的趋势。AppCache由于其技术实现的缺陷,也被安全研究人员发现了一些漏洞。本文针对Chrome下的AppCache实现,回顾曾经被爆出来的安全漏洞。
#1 AppCache Poisioning
这个漏洞在[1]和[2]中被提及,主要的成因是AppCache的FALLBACK字段没有限制源URI的作用域。意思就是说即使attack.html和manifest.txt在网站的/1337/a/b/c/
下,attach.html安装的manifest.txt也可以指定根目录/index.html
的FALLBACK。这一缺陷结合Cookie Bomb等技术,只要使被劫持的页面返回500等错误就可以触发FALLBACK,使之被劫持到攻击者的恶意页面。一个例子如下:
https://example.com/1337/a/b/c/attack.html
<html manifest="manifest.txt">
<script>
for(var i = 1e2; ;i--)
document.cookie = i + '=' + Array(4e3).join(0) + '; path=/';
</script>
</html>
https://example.com/1337/a/b/c/manifest.txt
CACHE MANIFEST
manifest.txt
FALLBACK:
/ /1337/a/b/c/poison.html
当用户先访问了/1337/a/b/c/attack.html
之后,再访问根路由/
由于Cookie Bomb就会发生500错误,浏览器就会将/1337/a/b/c/poison.html
页面的内容返回给用户。
这个漏洞的利用条件需要
- 上传点,可以上传html和manifest
- (或者说,返回内容可控的路由,比如html注入,可以注入
<html manifest="">
标签即可。
真实的案例可以是一些托管用户文件的网站,比如aws bucket、dropbox等等。
#2 XSLeaks
- Leak URL是否跳转
AppCache的CACHE字段可以设置跨域的URL,当设置的URL在请求时发生了跳转,appcache无法成功cache,onerror事件被触发,而当成功cache时,oncached事件被触发,这就可以oracle出一个跨域URL是否发生了跳转。比如下面的例子:
attack.html
<html manifest="manifest.appcache">
<script>
applicationCache.onerror = () => console.log("User isn't logged since there was a redirect");
applicationCache.oncached = () => console.log("User is logged since there wasn't a redirect");
</script>
</html>
manifest.appcache
CACHE MANIFEST
https://www.facebook.com/settings
attack.html页面就可以探测出用户的facebook是否是登录状态,因为非登录状态访问https://www.facebook.com/settings
会发生跳转。
- Leak 会发生跳转的URL内容
AppCache的NETWORK字段也可以设置跨域的URL,且是以白名单的方式允许哪些URL可以访问。
名词说明:
victim URL : 会发生跳转的URL
redirect URL :跳转之后的URL
如果将victim URL以及跳转后的redirect URL设置为NETWORK字段内容,则访问victim URL会正常跳转不会被拦截。而如果将victim URL和与redirect URL不一致的URL设置为NETWORK字段,则由于跳转后的redirect URL不在白名单内,所以访问会失败。博客里的例子如下:
cache.manifest
CACHE MANIFEST
NETWORK:
https://www.facebook.com/me
https://www.facebook.com/victim
attack.html
<html manifest="cache.manifest">
<script>
applicationCache.oncached = () => {
fetch("https://www.facebook.com/me", {
mode: "no-cors",
credentials: "include"
}).then(() => {
console.log("The profile of the user is /victim");
}).catch(()= > {
console.log("The profile of the user isn't /victim");
});
}
</script>
</html>
其中受害者的facebook在登录状态,当访问https://www.facebook.com/me
时候,会自动跳转到https://www.facebook.com/<用户名>
。于是当受害者访问attack.html时,页面就会oracle出受害者的facebook用户名是否是victim
这样我们可以把用户名顺序遍历的结果写到一个manifest中,利用二分法搜索出最终的用户名,相当于爆搜来说已经是很大的提升了,但是还是不够优雅。libherrera在看了AppCache的chrome源码发现了一种非标准的feature
,叫做URL patterns
。样子长下面:
CACHE MANIFEST
NETWORK:
https://www.facebook.com/me
https://www.facebook.com/vi*tim isPattern
于是通过URL pattern,就可以逐字符oracle出完整的用户名。
当然故事没有结束,在报告了这个漏洞之后的一年,作者又发现了manifest中的prefix match
,更简洁的payload出来了,长下面的样子
CACHE MANIFEST
NETWORK:
https://facebook.com/me
https://facebook.com/v
两个bug分别是CVE-2020-6399
和CVE-2021-21168
这个漏洞的利用场景可以说是非常多了,比如:
- 跳转后的URL包含session token
- 跳转后的URL包含CSRF token
不再一一例举。
# 一道例题 Pwn2Win 2021 MessageKeeper
这道题的考点就是利用AppCache中的FALLBACK字段。
题目漏洞点就是有一个JSONP接口存在任意的html注入,但是CSP是default-src none
。
其中flag也在这个JSONP接口返回,但是需要这个JSONP接口的另一个参数token也正确才行。
所以题目基本就是需要拿到这个admin的token,然后用这个JSONP的功能就可以拿到flag。看看解法最后用到的manifest长啥样吧(来自terjanq,和官方解基本一样
CACHE MANIFEST
/?cached
FALLBACK:
/user?token=a /static/background.png
ORIGIN-TRIAL:
${trial_token}
#
其中trial_token可以去Chrome Origin Trials申请一个,值得一提的是可以申请任意origin的token,即使这个origin不属于你。
这个manifest的意思是缓存/?cache
,其实也就是首页,加个参数可以保证不影响到访问/
并且有限制FALLBACK作用域的作用。所以此时,下面的FALLBACK字段也只作用于/?cached
页面。
首页中有脚本调用了这个JSONP,所以这个script标签的src会被作用于FALLBACK字段
但如何让这个FALLBACK生效呢?解法有一步是主动调用/logout
让admin退出,就是为了让/user
接口返回401,从而让FALLBACK起作用。
所以当这个appcache manifest注册之后,第一次访问/?cached
,首页script的src向/user?token=<正确的token>
发起请求返回401,如果:
- 此时攻击者注册的manifest中的FALLBACK字段的URI
/user?token=a
匹配了这个真的/user?token=<正确的token>
,那么浏览器就会先请求/static/background.png
然后返回图片的内容。而这个/static/background.png
设置了Cache-Control: public, max-age=14400
返回头,也就是说会缓存在本地中,下次访问直接从本地缓存拿。 - 此时攻击者注册的manifest中的FALLBACK字段的URI
/user?token=a
没有匹配到真的/user?token=<正确的token>
,那么浏览器就不会去请求/static/background.png
这个文件,而是直接报401。
这样当第二次再请求/?cached
的时候,如果匹配正确,会直接从本地缓存拿/static/background.png
文件,如果匹配失败,依然会发送一个http请求。
这样,两次请求的时间差异就会因为匹配成功与否呈现出差别,以此就可以进行oracle,结合prefix match
就可以逐字节oracle出admin的token。
上面的解释应该是题目作者gist上的意思,但是他的gist代码感觉并不是这个意思,因为gist的payload只对/?cached进行了一次load,并不是两次。
所以我测试了一下FALLBACK的机制,也就是FALLBACK被触发一次之后,会不会直接cache触发FALLBACK的URI的返回结果。如果不会cache,那么作者的这种解释就显得牵强,因为两次请求触发FALLBACK的URI都会被浏览器请求一次然后再返回FALLBACK后的URI(触发FALLBACK的URI请求不会在console显示),这样能触发FALLBACK的URI反而会因为还要加载新的URI(即题目中的background.png)而花费更长的时间。
测试的结果也验证的我的猜想,FALLBACK并不会cache触发FALLBACK的URI的返回结果。图中可以看出从disk cache和401的两个结果花费的时间基本一致,因为disk cache看起来是从缓存拿的结果,实际上也进行了一次http request。
那么作者提供的payload为什么会起作用呢。在测试的时候我注意到一个有规律可以复现的现象,那就是chrome会retry被401的/user?token=xx
请求
而被FALLBACK匹配的/user?token=xx
请求则不会进行retry。而这多的一次retry的时间刚好和oracle的时间差吻合,到这里应该也就大概明白了这个oracle的逻辑了。
整理下这个题的关键点:
- FALLBACK的作用域可以被manifest限制在独立的URI里
- chrome会对失败的401请求进行retry,结合AppCache的FALLBACK会导致一个侧信道
Timeline
- Chrome 50在非安全环境下“废弃” - 2016/4
- Chrome 70在非安全环境下“移除” - 2018/10
- Chrome 79在安全环境下“废弃” - 2019/12
- Chrome 80限制AppCache作用域 - 2020/2
- Chrome 84开启“reverse origin trial” - 2020/7
- Chrome 85在安全环境下移除,但依然可以通过"reverse origin trial"开启 - 2020/8
- Chrome 93完全移除 - 大约2021/10
Reference
- [1] Exploiting the Unexploitable with Lesser Known Browser Tricks. https://speakerdeck.com/filedescriptor/exploiting-the-unexploitable-with-lesser-known-browser-tricks. AppSec EU 2017.
- [2] Attacking Modern Web Technologies. https://www.slideshare.net/fransrosen/attacking-modern-web-technologies. AppSec EU 2018.
- [3] https://blog.lbherrera.me/posts/appcache-forgotten-tales/. @lbherrera. 2021/5/31.
BY:先知论坛
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论