XSS DOM破坏实战案例

news/2024/10/20 11:49:49/

目录

案例一

思考

源码分析

查找问题

实现

案例二

查看源码

问题查找

实现


实验环境:DOM clobbering | Web Security Academy (portswigger.net)

案例一

里面是一篇篇的博客,点击进去里面是一些评论

思考

尝试一些常规的xss

没什么效果...

他将我后面的给过滤掉了

我们需要分析他的他的源码

源码分析

又是这个框架

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 = avatarImgHTMLif (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 + " | " + dateStrfirstPElement.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);}}
};

这是第二个里面的源代码,里面有三个方法

分别是载入,过滤和展示,第一个是将请求的数据进行一个提交然后使用displaycomments方法进行展示,widow.location使我们请求的地址栏而后面的search是我们请求的参数

提交完之后他会先提取我们的头像然后又创建了一个div将我们的标签放进去,这些东西它都会放进一个p标签里面去

查找问题

这里头像有两个,当我们头像不存在的时候他有一个默认的值

他这里会写进一个img后面是如果我们有头像就用我们的如果没有他就用默认的然后会走进defaultAvatar.avatar,如果我们能够走到window.defaultAvatar,就可能闭合我们的src,那么我们就得想如何创造它

只要我们原来没有头像就 可以⽤⼀个构造⼀个defaultAvatar.avatar进⾏XSS了。

我们可以⽤HTMLCollection来操作

<a id=defaultAvatar><a id=defaultAvatarname=avatar href="1:&quot;onerror=alert(1)//"

注意"需要进⾏HTML实体编码,⽤URL编码的话浏览器会报错1:%22οnerrοr=alert(1)//

实现

用我们之前的payload会被编码导致失败,我们需要将1:&quot;换成一个不存再的协议,否侧会被当作url地址栏会将它进行编码成%22导致我们无法进入到后面的语句,这里我换成的是cid

案例二

他的一个界面还是跟我们之前的一个相似

查看源码

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();let janitor = new HTMLJanitor({tags: {input:{name:true,type:true,value:true},form:{id:true},i:{},b:{},p:{}}});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 avatarImgElement = document.createElement("img");avatarImgElement.setAttribute("class", "avatar");avatarImgElement.setAttribute("src", comment.avatar ? comment.avatar : "/resources/images/avatarDefault.svg");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 + janitor.clean(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 + " | " + dateStrfirstPElement.innerHTML = newInnerHtml}firstPElement.appendChild(avatarImgElement);commentSection.appendChild(firstPElement);if (comment.body) {let commentBodyPElement = document.createElement("p");commentBodyPElement.innerHTML = janitor.clean(comment.body);commentSection.appendChild(commentBodyPElement);}commentSection.appendChild(document.createElement("p"));userComments.appendChild(commentSection);}}
};

这里面没有用我们之前很明显的window.x了,相对就比较安全

再评论的大框里面进行了一个过滤

⼀开始就初始化了HTMLJanitor,只能使⽤初始化内的标签及其属性,对于重要的输⼊输出地⽅都使 ⽤了janitor.clean 进⾏过滤。看起来我们没办法很简单地进⾏XSS,那我们就只能来看 看resources/js/htmlJanitor.js 这个过滤⽂件了

(function (root, factory) {if (typeof define === 'function' && define.amd) {define('html-janitor', factory);} else if (typeof exports === 'object') {module.exports = factory();} else {root.HTMLJanitor = factory();}
}(this, function () {/*** @param {Object} config.tags Dictionary of allowed tags.* @param {boolean} config.keepNestedBlockElements Default false.*/function HTMLJanitor(config) {var tagDefinitions = config['tags'];var tags = Object.keys(tagDefinitions);var validConfigValues = tags.map(function(k) { return typeof tagDefinitions[k]; }).every(function(type) { return type === 'object' || type === 'boolean' || type === 'function'; });if(!validConfigValues) {throw new Error("The configuration was invalid");}this.config = config;}var blockElementNames = ['P', 'LI', 'TD', 'TH', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'PRE'];function isBlockElement(node) {return blockElementNames.indexOf(node.nodeName) !== -1;}var inlineElementNames = ['A', 'B', 'STRONG', 'I', 'EM', 'SUB', 'SUP', 'U', 'STRIKE'];function isInlineElement(node) {return inlineElementNames.indexOf(node.nodeName) !== -1;}HTMLJanitor.prototype.clean = function (html) {const sandbox = document.implementation.createHTMLDocument('');const root = sandbox.createElement("div");root.innerHTML = html;this._sanitize(sandbox, root);return root.innerHTML;};HTMLJanitor.prototype._sanitize = function (document, parentNode) {var treeWalker = createTreeWalker(document, parentNode);var node = treeWalker.firstChild();if (!node) { return; }do {if (node.nodeType === Node.TEXT_NODE) {// If this text node is just whitespace and the previous or next element// sibling is a block element, remove it// N.B.: This heuristic could change. Very specific to a bug with// `contenteditable` in Firefox: http://jsbin.com/EyuKase/1/edit?js,output// FIXME: make this an option?if (node.data.trim() === ''&& ((node.previousElementSibling && isBlockElement(node.previousElementSibling))|| (node.nextElementSibling && isBlockElement(node.nextElementSibling)))) {parentNode.removeChild(node);this._sanitize(document, parentNode);break;} else {continue;}}// Remove all commentsif (node.nodeType === Node.COMMENT_NODE) {parentNode.removeChild(node);this._sanitize(document, parentNode);break;}var isInline = isInlineElement(node);var containsBlockElement;if (isInline) {containsBlockElement = Array.prototype.some.call(node.childNodes, isBlockElement);}// Block elements should not be nested (e.g. <li><p>...); if// they are, we want to unwrap the inner block element.var isNotTopContainer = !! parentNode.parentNode;var isNestedBlockElement =isBlockElement(parentNode) &&isBlockElement(node) &&isNotTopContainer;var nodeName = node.nodeName.toLowerCase();var allowedAttrs = getAllowedAttrs(this.config, nodeName, node);var isInvalid = isInline && containsBlockElement;// Drop tag entirely according to the whitelist *and* if the markup// is invalid.if (isInvalid || shouldRejectNode(node, allowedAttrs)|| (!this.config.keepNestedBlockElements && isNestedBlockElement)) {// Do not keep the inner text of SCRIPT/STYLE elements.if (! (node.nodeName === 'SCRIPT' || node.nodeName === 'STYLE')) {while (node.childNodes.length > 0) {parentNode.insertBefore(node.childNodes[0], node);}}parentNode.removeChild(node);this._sanitize(document, parentNode);break;}// Sanitize attributesfor (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 childrenthis._sanitize(document, node);} while ((node = treeWalker.nextSibling()));};function createTreeWalker(document, node) {return document.createTreeWalker(node,NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,null, false);}function getAllowedAttrs(config, nodeName, node){if (typeof config.tags[nodeName] === 'function') {return config.tags[nodeName](node);} else {return config.tags[nodeName];}}function shouldRejectNode(node, allowedAttrs){if (typeof allowedAttrs === 'undefined') {return true;} else if (typeof allowedAttrs === 'boolean') {return !allowedAttrs;}return false;}function shouldRejectAttr(attr, allowedAttrs, node){var attrName = attr.name.toLowerCase();if (allowedAttrs === true){return false;} else if (typeof allowedAttrs[attrName] === 'function'){return !allowedAttrs[attrName](attr.value, node);} else if (typeof allowedAttrs[attrName] === 'undefined'){return true;} else if (allowedAttrs[attrName] === false) {return true;} else if (typeof allowedAttrs[attrName] === 'string') {return (allowedAttrs[attrName] !== attr.value);}return false;}return HTMLJanitor;}));

创建了⼀个新的HTML⽂档⽤作sandbox ,然后对于sandbox内的元素进⾏_sanitize过滤

在_sanitize函数⼀开始调⽤了createTreeWalker函数创建⼀个TreeWalker,这个类表示⼀个当 前⽂档的⼦树中的所有节点及其位置。

.SHOW_TEXT表示 DOM 树中的一个文本节点。.SHOW_COMMENT,筛选注释,.SHOW_ELEMENT会调用子元素

如果此⽂本节点只是空⽩,并且上⼀个或下⼀个元素同级是`blockElement`,则将其删除 } 移除所有的注释

问题查找

如果你传的是黑名单里面的那么我直接删掉,只有form和input才能走到下面的函数

这种黑名单里面的函数和嵌套都会被过滤掉,如果是白名单里面的但属性不是里面的也会被过滤掉

实现


http://www.ppmy.cn/news/1511698.html

相关文章

Harbor仓库push显示

背景&#xff1a; 在做测试时发现harbor仓库端口开放这&#xff0c;却一直登录不上去&#xff0c;重启harbor资源包docker-compose还是不行&#xff0c;修改了docker.service文件不行&#xff0c;json文件也不行&#xff0c;以下是涉及到的命令和报错&#xff08;好像是这个&am…

UE 开发---- Ios ipa 安装包安装到IPhone手机上

由于最近 我在使用ue 开发手机软件 打包成为 Ios ipa 安装包时 安装到手机上遇到的问题 准备&#xff1a; 1、一部 Ios 手机 2、首先下载爱思助手爱思助手官网_苹果助手_苹果刷机助手_苹果越狱助手 (i4.cn) 我下载的Windows端 电脑连接上Ios手机时 是这样的 我们打开工…

SQL Server上机报告_1

题目一&#xff1a;分别用图形化方法和CREATE DATABASE语句创建符合如下条件的数据库。 具体要求&#xff1a; 数据库的名字为students&#xff0c;包含的数据文件的逻辑文件名为students_dat&#xff0c;物理文件名为students.mdf&#xff0c;存放在D&#xff1a;\Test文件夹中…

Ubuntu+QT编译QTXlsx库

1.在GitHub上下载QT Xlsx 的源码&#xff0c;网站链接如下&#xff08;需要科学上网&#xff09; https://github.com/dbzhang800/QtXlsxWriter 下载好的内容如下 然后在目录下右击启动终端 输入如下命令 先输入qmake qtxlsx.pro再输入make最后sudo make install 注意&…

Chromium编译指南2024 - Android篇:全新获取源代码(五)

1.引言 在前面的章节中&#xff0c;我们详细介绍了编译 Chromium for Android 所需的系统和硬件要求&#xff0c;以及如何配置基础开发环境和 depot_tools。完成这些准备工作后&#xff0c;下一步就是获取 Chromium 的源代码。获取源代码是编译 Chromium 的关键步骤&#xff0…

Redis 哈希(Hash)

Redis 哈希(Hash) 介绍 Redis 哈希(Hash) 是一种数据结构&#xff0c;用于存储键值对集合。与字符串(String)不同&#xff0c;哈希可以存储多个键值对&#xff0c;每个键值对由一个字段和一个值组成。这种数据结构非常适合表示对象&#xff0c;例如用户信息、配置设置等。 哈…

ubuntu 安装node

安装node 由于项目使用node 16.x开发&#xff0c;因此在Jenkins上&#xff0c;安装node 16.x 使用curl下载NodeSource的安装脚本&#xff1a; curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash - 安装Node.js&#xff1a; sudo apt-get install -y nodejs验证…

使用 AWS CLI 自动在 Amazon EC2 实例上部署 Apache Web 服务器

“使用 AWS CLI 节省时间” 欢迎来到雲闪世界。今天&#xff0c;我们将利用 AWS CLI 的实际用途来提高效率并自动执行在 Amazon EC2 实例上部署 Apache Web 服务器的步骤。完成“使用 AWS CLI 节省时间”任务后&#xff0c;最后有一个非常有趣的秘密步骤&#xff0c;敬请…