PortSwigger web cache deception labs

做 Lab 之前,建议先看看 PortSwigger 研究员 Martin 在今年 BlackHat 上公布的最新研究成果:Gotta cache ‘em all: bending the rules of web cache exploitation

PS: 今年 BlackHat 大会,PortSwigger 杀疯了!

1 Lab: Exploiting path mapping for web cache deception

第一步先定位存在敏感数据的页面 (endpoint)。

登录成功后,用户页展示 username 和 API key.

敏感信息的 endpoint 为 GET /my-account.

静态文件会被缓存,通过 X-Cache 判断哪些响应被缓存。

缓存规则为静态文件扩展名,.css 的文件都被缓存了。

第二步,判断源服务器和缓存服务器处理 URL 路径的差异。修改路径为 /my-account/.css 发现源服务器仍可返回对应的用户主页,且报文被缓存 (X-Cache 头部)。

右键 –> Engagement tools –> Generate CSRF poc,发送到 exploit server –> Deliver exploit to victim

1
2
3
4
5
6
7
8
9
10
11
12
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0a9d00360386549b810634b70095002c.web-security-academy.net/resources/css/.css">
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>

回到 Repeater 再次发送报文,发现命中了缓存,这里注意缓存的有效时间。

找到敏感信息,提交即可。

2 Lab: Exploiting path delimiters for web cache deception

  1. 找下 origin server 的路径分隔符

为了避免 cache server 的影响,我选择 POST /login 进行测试。先随便加个字符看看响应。

正常发包拿到的响应应该是 400,因为登录用的 CSRF token 已经过期了。

用 Intruder 暴一下分隔符。其实都不用暴,我随便输了个 ? 就是分隔符,emmm。

但爆破结果却都是 404,点开后发现字典里的分隔符(payload)被编码了。

去掉 “Payload encoding” 选项再次爆破

源服务器路径分隔符为 ? 和 ;

  1. 判断缓存规则

感觉应该是缓存的特定拓展名,修改文件名为 caishao.js,响应被缓存了。

当然也可以测试其它情况,/caishao.js 看看 /resources 是否会影响缓存规则,还有 /resources/caishao 等等。

  1. 判断 origin server 的分隔符在 cache server 上是否生效

由于 POST 请求不会被缓存,所以这里我换了一个请求,/my-account。在末尾添加 .js 拓展名,响应 200 但未缓存。说明 ? 对缓存服务器来说也是分隔符,/my-account?caishao.js 被解释成了 /my-account

请求 /my-account;caishao.js,响应 200 且被缓存。说明 ; 不是缓存服务器的路径分隔符,缓存服务器视角为 /my-account;caishao.js,命中扩展名 js,所以缓存。而已知 ; 是源服务器的分隔符,源服务器视角为 /my-account,返回正确响应。

右键请求报文生成 CSRF POC,复制到 exploit server,再发送给受害人。然后回到 Burp Repeater 重新请求 /my-account;caishao.js,命中受害人缓存,拿到敏感信息。

`记得把 Param Miner 插件自动添加 cache buster 关了!

3 Lab: Exploiting origin server normalization for web cache deception

包含敏感信息的 endpoint 仍然是 GET /my-account

  1. Detecting normalization by the origin server

测试源服务器是否会标准化解析 URL 路径。找一个响应不会被缓存的 endpoint(排除缓存服务器的影响),POST /login,POST 方法请求的响应报文一般不会被缓存。

因为 csrf token 一定用过了,返回一个 400。

修改请求报文,测试目录穿越,响应仍然是 400,说明源服务器将请求解析成了 /login,反之,如解析成 /caishao/..%2flogin 本身,应该会返回一个 404.

  1. 判断缓存规则

浏览下 Proxy History 发现被缓存的基本都是静态文件,且都是 /resources 打头的,猜测缓存规则可能是匹配的静态目录(其实是题目上说的,哈哈)。构造请求报文如下图,响应虽然是个 404,但提示被缓存了,说明缓存是匹配的静态目录 /resources

貌似确实得 /resources 打头,目录里面包含都不能缓存。

顺带测一下缓存是否会匹配扩展名,均得到 404 且未缓存。

  1. Detecting normalization by the cache server

测试缓存服务器是否会标准化解析 URL 路径。找一个响应被缓存的静态文件请求,GET /resources/labheader/css/academyLabHeader.css。路径长一点,能多测一会儿。

修改请求路径为:/caishao/..%2fresources/labheader/css/academyLabHeader.css,响应没被缓存,说明没有解析 ..%2f。反之,如果 cache server 规范化路径为 /resources/labheader/css/academyLabHeader.css,则命中缓存规则。

仅添加 ..%2f 得到 404 但响应被缓存了,说明缓存规则为匹配 /resources 前缀且缓存服务器不会对路径进行规范化。

/caishao/..%2f 挪到中间即可获取到 200 响应且响应被缓存。但 cache key 与直接请求 /resources/labheader/css/academyLabHeader.css 不同,两者命中的是不同的缓存。这也更说明了路径规范化是在源服务器上进行了,当然也有一种可能是缓存服务器先匹配规则再规范化路径再发送到源服务器。So,如何判断第二种可能性?

结合上述所有点:

  1. 源服务器进行路径规范化
  2. 缓存服务器不会规范化路径
  3. 缓存规则为匹配 /resources 前缀
  4. 敏感信息 endpoint 为 GET /my-account

套公式:

1
/<static-directory-prefix>/..%2f<dynamic-path>

构造请求报文,将敏感信息缓存下来。因为缓存服务器基本都没有认证措施,直接访问 url https://example.com/resources/..%2fmy-account,即可获取到缓存中的敏感信息。

妈的利用不了啊!!!发送给 victim 的 url,我自己访问死活命中不了缓存啊?!

应该是 Param Miner 的问题,它自动给我加了 cache buster,关掉之后就好了。

4 Exploiting origin server normalization for web cache deception

  1. 判断缓存规则。

大概率是 /resources 打头的都缓存。

缓存规则不是匹配扩展名。

  1. 找源服务器的路径分隔符

选一个不会被缓存的报文开始爆破。

爆破之后发现路径分隔符为 #, ? 及其 URL 编码 %23, %3F

  1. 测试源服务器是否会规范化路径(路径穿越

并不会。

  1. 测试缓存服务器路径分隔符,找在源服务器有效而在缓存服务器上无效的。

WTF? 分隔符跟源服务器一样一样的?(–>看下面

  1. 缓存服务器是否存在路径穿越

响应被缓存,说明缓存服务器对路径进行了规范化,将其解析成了 /resources/js/tracking.js.

/resources/js/tracking.js 命中了同样的规则。在有效期内请求 /resources/js/tracking.js 响应 404,命中缓存。

再说回上文中缓存服务器路径分隔符的问题。爆破的时候,响应码为 200 的报文并不能说明缓存服务器也有这个路径分隔符,很大概率是源服务器解析后返回的。如下图所示,如果 # 为缓存服务器的分隔符,响应报文是不会被缓存的,/caishao#/..%2f/resources/caishao 命中缓存规则,说明它被解析成了 /resources/caishao

综上,构造 POC,成功缓存敏感信息。

右键生成 CSRF poc,复制到 exploit server,再发送给受害人。然后回到 Burp Repeater 再发送一下报文即可。

注意下自动生成的 csrf poc 中的 url 不是很对,记得修改。

# 和其 URL 编码 %23 命中的是同样的缓存规则,但我用 # 的时候死活命中不了受害人的缓存,查看了下 Burp Logger 发现所有 /my-account#/..%2fresources/caishao 的请求路径都是 /my-account,难怪前面生成的 poc 不对。但是响应也确实是缓存了,或许只是 Burp 显示的问题?(又试了好几遍直接用 #,都不行,命中不了受害人的缓存)

原文好像建议把路径穿越序列都编码,写成这种 %2f%2e%2e%2f,记得改一下。

5 Lab: Exploiting exact-match cache rules for web cache deception

  1. 测试缓存规则

看了半天能缓存的好像就一个图标,缓存规则好像是精准匹配文件名 /favicon.ico。我测试了 /caishao.ico, /resources/favicon.ico 等,响应都没有被缓存,说明既不是扩展名也不是静态目录名称。

  1. 测试源服务器目录穿越。返回 404 说明源服务器不会规范化路径。

  1. 测试源服务器路径分隔符。还是原来的方法,爆就完事了。分隔符为 ;?

  1. 测试缓存服务器是否存在路径穿越。发现如下报文,响应被缓存,说明匹配到缓存规则,/caishao%2F%2E%2E%2Ffavicon.ico 被解析成了 /favicon.ico

  1. 结合 delimiter 和 normalization 利用缓存规则。

请求 /my-account;%2F%2E%2E%2F/favicon.ico 只能被缓存,但拿不到 /my-account 的数据。为什么?

/my-account;%2F%2E%2E%2F/favicon.ico/my-account%2F%2E%2E%2F/favicon.ico 命中相同的规则,都是 /favicon.ico。连续交替发送这两个请求,看看缓存是 hit 还是 miss 不难得出这个结论。

/my-account;%2F%2E%2E%2F/favicon.ico 的响应报文怎么又变成 200 了????

检查一下 Logger 发现,我每次在请求 /my-account;%2F%2E%2E%2F/favicon.ico 之前都先请求了 /my-account%2F%2E%2E%2F/favicon.ico,这两者命中的是相同的缓存,所以 /my-account;%2F%2E%2E%2F/favicon.ico 总是返回 404。此外,这些响应报文中的 X-Cache 头部均为 hit,说明响应被缓存影响了。

截至目前,我们已经可以利用缓存规则把敏感信息存入缓存了。However,题目要求是借助 exploit server 修改 administrator 用户的邮箱地址。

先来看一下修改邮箱 POST /my-account/change-email 的消息体。除了新的邮箱地址还有一个 CSRF token。此外修改邮箱不需要邮箱验证码,借助简单的 CSRF 即可攻击成功。

1
email=caishao%40portswigger.net&csrf=xoOJ3JcF0RMizdEcPveJbzC8mPvfYWTF

思路:使用 web cache deception 拿到管理员的 csrf token,然后将其放到 CSRF PoC 中,发送给 exploit server,当管理员访问恶意页面时,会自动发送 POST 请求修改邮箱地址。

拿到管理员 csrf token 7AkFUISuKLCBTBaLrFrXYB5Lq2eN06pb

修改 POC 中的 csrf token 并发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<!-- CSRF PoC - generated by Burp Suite Professional -->
<body>
<form action="https://0aee00ef045ba8b5806c21c600020035.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="caishao&#64;portswigger&#46;net" />
<input type="hidden" name="csrf" value="[修改这里]" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>

然鹅发送之后并没有显示 lab resolved。

?????我把相同的 poc 重新发送了一遍就好了????难道说是之前发送之后管理员没点到我的 poc 页面?=> 具体原因留待后面学 csrf 再研究

expert 难度也不是很难嘛,风停了雨静了我觉得我又行了。不过,说实话这个 lab 没加任何 csrf 防御(除了 csrf token)实际环境应该很少遇到这种。


有点没看懂这两行是干嘛的。

validateSession.js 源码如下,貌似是每次访问 /my-account 页面时验证 Session 有效性的。PS:我真是憨憨,这玩意一眼都没看出来。我一度以为修改邮箱的时候会发两个报文,一个验证 csrf token,一个修改邮箱,emmm。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function validateSession(loginPath) {
fetch('/validateSession', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
csrf: document.getElementsByName("csrf")[0]?.value
}).toString()
}).then(response => {
if (!response.ok) {
window.location = loginPath;
}
}
).catch( () => window.location = loginPath);
}

PortSwigger web 缓存欺骗的 lab 就全部做完啦!!完结撒花!做题的时候我全程没加 cache buster,实际环境测试别忘了加,我不想进局子(当然,得先能挖到洞 emmm)