DOM型XSS黑盒绕过实战思路
发布时间:2026/10/1 7:47:38来源:尧图网络
---免责声明本文章所有内容仅供学习交流与安全研究使用,旨在帮助开发者和安全爱好者理解漏洞原理、提升防御能力。1. 授权前提:本文涉及的技术、payload、绕过方法仅可在你拥有所有权或已获得明确书面授权的目标上进行测试。未经授权对任何系统进行测试均属违法行为。2. 禁止非法用途:读者不得将本文内容用于任何非法入侵、攻击、破坏或窃取他人数据的活动。一切未经授权的渗透测试、未授权访问均违反《中华人民共和国网络安全 法》《刑法》第二百八十五条、第二百八十六条等相关法律法规,可能构成非法侵入计算机信息系统罪、破坏计算机信息系统罪等。3. 责任自负:读者使用本文所载信息及技术所产生的一切后果由其本人承担,作者不承担任何直接或间接的法律责任及连带责任。4. 内容性质:本文所述技术均为公开的安全研究知识,作者不保证内容的完整性与时效性,亦不代表鼓励或教唆任何违法行为。5. 学习导向:文中涉及的代码示例仅用于演示漏洞原理,实际环境中请始终遵循最小授权、合规测试、及时修复的原则。读者继续阅读即视为已阅读、理解并同意上述声明。 若不同意,请立即停止阅读并关闭本页面。网络安全人人有责。学技术,先学规矩;懂攻击,是为了更好地防御。---————————————————版权声明本文为CSDN博主「..梧桐..」的原创文章遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。原文链接https://blog.csdn.net/2401_84857109/article/details/166645909从不进服务器这个最朴素的认识出发,由浅入深把 DOM 型 XSS 的黑盒发现与验证讲透。全程以黑盒视角为主(没有源码怎么推),必要时读前端 JS 验证。不堆 payload,只讲原理与手法。一、本质:重新说清不进服务器很多人对 DOM 型 XSS 的第一印象是不进服务器。这话抓到了核心,但不够精确,需要修正成两句:payload(实际触发 JS 的那串字符串)不在服务器的 HTTP 响应体里但服务器返回了有漏洞的 JS 代码——这段 JS 把某个可控数据不经净化塞进了危险操作两个要点缺一不可。最典型的场景:payload 放在 URL 的#后(fragment),按 HTTP 规范 fragment 不发往服务器,但同页面的 JS 会读取它。用一个具体页面对照 Burp 流量有漏洞的页面search.html:div idresult/div script document.getElementById(result).innerHTML location.hash.slice(1); /script这段 JS 本身就是漏洞:逻辑是把 hash 塞进 innerHTML。攻击者构造链接:https://target.com/search.html#img src1 onerroralert(1)请求(受害者→服务器):GET /search.html HTTP/1.1 Host: target.com请求里没有 payload。#后被浏览器剥掉了,服务器从头到尾没看到 payload。响应(服务器→受害者):HTTP/1.1 200 OK div idresult/div script document.getElementById(result).innerHTML location.hash.slice(1); /script响应体里也 grep 不到 payload,只有一段读 hash 塞进 innerHTML的 JS。浏览器执行 JS(这一步 Burp 看不到):取出 hash → 赋给 innerHTML → 浏览器当 HTML 解析 → 触发 onerror → 弹窗。payload 只活在两处:URL 的#后(本地) 浏览器执行时的内存。从头到尾没碰服务器。这就是不进服务器的技术根因。与反射型的分水岭判定标准只有一条:Burp 响应体里 grep 得到 payload 吗。类型payload 在哪响应体能 grep 到?反射型响应体 HTML 里能存储型响应体 HTML 里能DOM 型URL fragment / window.name 等,不在响应体不能反射型页面reflect.php用同样的 payload,响应里直接出现divimg src1 onerroralert(1)/div,grep 得到。DOM 型 grep 不到——这是测 DOM 型光看 Burp 流量不够、必须读 JS 的原因。二、source→sink:DOM 型的唯一心智模型DOM XSS 全部本质一句话:攻击者可控的数据入口(source),流进了一个会把数据当代码/标记解析的危险操作(sink),中间没净化。用水管比喻:source 进水口:攻击者能把脏水灌进去的地方sink 出水口:水从这里出来、被人喝到的地方source→sink 水从进水口流到出水口,中间有没有过滤器在前面的页面上圈出来:script document.getElementById(result).innerHTML location.hash.slice(1); // ↑ sink ↑ source /scriptlocation.hash.slice(1)是 source:数据来自 URL 的#后,攻击者可控.innerHTML 是 sink:数据流到这里被当 HTML 解析中间那个就是连接,没过滤 → 漏洞漏洞 链连着;防御 断开断开有两种典型写法:断法一:换 sink,换成不当 HTML 解析的document.getElementById(result).textContent location.hash.slice(1);textContent只把数据当纯文本,img...进去只是几个字符,不触发 → 链断了。断法二:中间加过滤器document.getElementById(result).innerHTML DOMPurify.sanitize(location.hash.slice(1));sink 还是 innerHTML,但数据进 sink 前被 DOMPurify 清洗 → 链断了。真实代码里它们不挨着,要追链真实 JS 不会这么直白,source 和 sink 往往隔几行、跨函数:var input location.hash.slice(1); // ① source:取数据存进 input doStuff(input); // ② 传给函数 function doStuff(data){ document.getElementById(result).innerHTML data; // ③ sink }追链:location.hash → input → data → innerHTML。中间变量只是水流过的管子名。判断有没有漏洞,就是看这条链从头到尾有没有断点。三、source/sink 是白盒概念,黑盒怎么碰它source/sink 本是白盒/代码审计(污点分析)的术语——source 是污点源,sink 是污点汇聚点,审计就是读代码追数据流有没有被净化。纯黑盒(不读代码)时,这两个词确实不直接出现在眼前。但黑盒流程里一直在间接地碰它们,只是没用这个词。把黑盒动作对回去:黑盒动作骨子里在判断 source/sink 的什么grep 响应体有没有 payloadsource 是服务器(反射型)还是前端 JS(DOM型)输btest/b看解析不解析推断 sink 是 innerHTML 还是 textContentpayload 弹窗证实 source→sink 这条链没断读那段 JSsource 和 sink 直接显形在代码里关键在输btest/b这一步:你不知道源码里写的是哪个函数,但页面显示粗体 标签被解析 sink 一定是会把数据当 HTML 解析的那种。这就是黑盒的本质——通过行为反推内部结构。源码里 sink 叫什么名字你看不见,但它解不解析 HTML这个行为你看得见。两套视角的关系:黑盒:看行为,间接推断有没有一条没断的数据链白盒:读代码,直接看见 source 在哪、sink 在哪、中间断没断描述的是同一个漏洞,只是黑盒看行为、白盒看代码。实战里它俩不是非此即彼——前端 JS 在浏览器 DevTools 里基本都能看,所以测 Web 经常是黑盒探测 读前端 JS 验证来回切。四、完整黑盒流程(从输入原样返回开始)以一个搜索页search.html为靶子,从零开始,每一步做什么 → 看到什么 → 怎么判断。第 0 步:输入原样返回(起点)搜索框输入test123,点搜索。页面显示你搜索了:test123,URL 变成https://target.com/search.html#test123。此时还不知道有没有漏洞,只知道输入的词出现在页面上。第 1 步:打开 Burp,看响应体里有没有你的输入这是判断 DOM 型 vs 反射型的分水岭。在 Burp 响应里 greptest123:搜不到 → 这串字符不是服务器拼的,是页面那段 JS 在浏览器里塞进去的 → DOM 型的信号搜得到 → 反射型第 2 步:看 URL,确认输入怎么传的URL 是#test123——hash 形式。hash 后的东西浏览器不发服务器,所以服务器看不到 test123,自然不拼进响应——解释了第 1 步为什么 grep 不到。到这里确定:这页有前端 JS 在读 hash、往页面塞东西。第 3 步:判断塞东西用的是安全方式还是危险方式那段 JS 把 hash 塞进页面时用的哪种?用textContent只当纯文本 → 安全;用innerHTML当 HTML 解析 → 危险。黑盒没源码时,换个带标签的输入试。第 4 步:输入btest/b,看页面怎么显示显示粗体 test → 标签被解析了 → 用的 innerHTML → 大概率有 DOM XSS显示btest/b这几个字符 → 用的是 textContent → 安全第 5 步:输入真 payload,验证输入img src1 onerroralert(1),页面弹窗 → 实锤 DOM 型 XSS。第 6 步:回 Burp 把证据链完整看一遍请求只有GET /search.html,没有 payload响应只有页面骨架和一段 JS,没有 payloadpayload 只在 URL 的#后和浏览器内存里 → 从头到尾没碰服务器第 7 步(可选):读前端 JS 验证黑盒推断DevTools → Sources 找到那段 JS:document.getElementById(result).innerHTML location.hash.slice(1); // ↑ sink ↑ source两者没过滤直接连上 → 和第 5 步弹窗的黑盒结论完全一致。整个流程的主线:输入词 → 查 Burp 响应里有没有 → 没有DOM 型 → 换带标签的输入看解析不解析 → 解析innerHTML可打 → 真 payload 验证。五、变体:source 和 sink 都能换DOM XSS 的公式是固定的:可控 source → 危险 sink,中间没断。换 source、换 sink 就是变体。source 变体(不止#)source数据从哪来黑盒怎么发现location.hashURL 的#后改#看页面变不变location.searchURL 的?后改 query 看页面变不变document.referrer从哪个页面跳来控制来源页再跳过来window.name跨页保留(上一页留的)URL/Burp 都看不到,得读 JSpostMessage跨域消息发来的看addEventListener(message)localStorage本地存储看有没有别处能写进去sink 变体(不止innerHTML)sink危险类型黑盒怎么探测innerHTML/document.write/outerHTML当 HTML 解析输b看变不变粗体eval/new Function/setTimeout(字符串)当代码执行输b没用,得输 JS 语法location /location.href 走javascript:协议试javascript:alert(1)jQuery.html()/$(html)当 HTML 解析同 innerHTML六、最阴的变体:window.name(source 不在 URL)前面所有 source 变体的数据都在 URL 里,改 URL、看 Burp 都能碰到。但window.name的 source根本不在当前页 URL 里,黑盒只看 Burp 流量完全发现不了。原理:window.name有个特性,同一标签页从 A 页跳到 B 页,这个值会保留。攻击者在自己域设好window.name,让标签跳到目标页,目标页 JS 读它塞进 sink:// 攻击者页面 attacker.com/trap.html window.name svg onloadalert(1); // 先把脏数据存进 window.name location https://target.com/vuln.html; // 跳到目标页 // target.com 的 JS(漏洞在这) document.getElementById(x).innerHTML window.name; // 读 window.name 塞进 sink黑盒发现:target.com 的请求干净、响应干净、URL 干净。必须读前端 JS,发现它读window.name(或postMessage)并塞进 sink,才会意识到这个 source 存在。postMessage同理——source 是别的页面发来的消息,也不在 URL。认知上要记牢:source 不一定来自当前页 URL,可能来自上一页、跨域消息、本地存储。七、各变体的黑盒流程(只讲手法不同的部分)和第四节流程一样的部分(改输入看解析、弹窗验证)一带而过,只讲每个变体黑盒手法不同的地方。source 变体location.search(query 变体)和 hash 几乎一样,区别在判定边界——query(?q)发服务器,容易误判成反射型。所以 grep 响应体这步不能省:搜不到 DOM 型,搜得到 反射型。判定标准始终是payload 在不在响应体,不是用了 hash 还是 query。window.namesource 不在 URL,改 hash、改 query、grep 响应体全没反应/没线索。卡住时不能死磕 URL,要切到读 JS,grepwindow.name,发现读它塞进 sink。验证要你当那个塞数据的上一页:自己搭 attacker 页设window.name再跳过来。postMessage读 JS,grepaddEventListener(message),看收到消息后塞进什么 sink、有没有校验event.origin。不校验 origin 任何域都能发。验证时你当发送方:iframe src目标页再frames[0].postMessage(svg onloadalert(1),*)。速过:document.referrer:同window.name思路(非 URL source),source 是从哪个页跳来,验证靠控制来源页诱导跳转。localStorage:需先找到另一个能写 localStorage 的输入点(二阶),通常读 JS 才看得出来。sink 变体eval/setTimeout(字符串)(代码执行类)黑盒手法和 innerHTML 不一样。输btest/b没反应(它不当 HTML),容易误以为没洞——这是陷阱。读 JS 发现setTimeout(do(input))这种字符串拼接后,验证要输 JS 语法如;alert(1);//(闭合引号注入代码),而不是 HTML 标签。不同 sink,探测 payload 形态不同。location (URL 类)读 JS 发现把输入赋给location(或页面把输入当跳转)。验证输javascript:alert(1)看执行——这类 sink 用伪协议 payload,不是 HTML 标签。速过:document.write/outerHTML:同 innerHTML 手法,输b看解析。jQuery.html()/$(html):同 innerHTML 手法;其中$()老版本对以开头的字符串走 HTML 解析,要单独留意。八、sink 分类:1 个安全阵营 3 个不安全阵营sink 确实分安全/不安全两阵营,但不安全内部还要再分三种,因为黑盒探测手法不一样:阵营类别代表 sink黑盒怎么探测安全纯文本类textContent/innerText/createTextNode输啥都不弹,只当字符不安全HTML 解析类innerHTML/document.write/outerHTML输b看变不变粗体不安全代码执行类eval/setTimeout(字符串)/new Function输b没用,得输 JS 语法不安全URL 类location /location.href 输javascript:看执行所以是 13,不是 11。卡在二分的话,遇到代码执行类 sink(输b没反应)会误以为没洞——其实只是探测 payload 形态用错了。危险 sink 不是禁用,是用了就必须配净化。innerHTML配DOMPurify.sanitize(...),或干脆换textContent。sink 有天然的安不安全之分,但漏洞成不成立,取决于用了危险 sink 且没断开。九、收口:黑盒的本质黑盒探测的本质,就是通过行为反推 sink 属于哪一类。输b看变不变粗体——这个动作就是在判断 sink 是不是 HTML 解析类。整个 DOM XSS 黑盒,绕来绕去就是在做这个判断。两个变体维度决定黑盒手法:source 变体决定数据从哪灌进来——决定你怎么找入口、要不要当外部来源方验证sink 变体决定探测 payload 要用什么形态——HTML 解析类用b,代码执行类用 JS 语法,URL 类用javascript:所有变体通用的黑盒四步套路① grep 响应体 → 判断 DOM 型还是反射型(payload 在不在响应体) ② 读前端 JS,grep source 关键字(location./window.name/postMessage/referrer/localStorage) 和 sink 关键字(innerHTML/eval/setTimeout/location/document.write/$() ③ 按 sink 类型选探测 payload 形态: HTML 解析类 → btest/b 看变不变粗体 代码执行类 → ;alert(1);// 这种 JS 语法 URL 类 → javascript:alert(1) 这种伪协议 ④ source 不在 URL 时(window.name/postMessage),你要当那个外部来源来验证一句话收:DOM XSS 黑盒 判断 sink 类别 按类别选 payload 形态 按来源方式选验证手法。source→sink 是白盒语言,黑盒时它藏在行为背后,靠探测把它逼出来;能读代码时就直接显形了。环境与工具说明DOM Invader:Burp 浏览器扩展,自动标 source/sink 并追链,省去手动 grep。DOMPurify:客户端 HTML 净化库,有过 mXSS 绕过历史,保持更新。Trusted Types:浏览器原生机制,直接挡 innerHTML 等危险 sink;Chrome 系支持好,Firefox 支持较晚,老项目需评估兼容性。前端 JS 通常在浏览器 DevTools → Sources 可见,黑盒与白盒切换成本低。
网站建设高端定制企业官网