ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

jQuery老XSS漏洞CVE-2020-11022与CVE-2020-11023复现解析

jQuery老XSS漏洞CVE-2020-11022与CVE-2020-11023复现解析 扒一下 jQuery 这两个“老”XSS漏洞CVE-2020-11022 和 CVE-2020-11023 的复现全程2020年4月jQuery 官方一口气公布了两个和 HTML 解析相关的 XSS 漏洞——CVE-2020-11022 和 CVE-2020-11023。当时不少团队是赶着升级到 3.5.0 才算完事。但这都2024年了我还在各种老项目里翻到 jQuery 3.4.x 甚至 1.x 的身影每次代码审计碰上这俩编号都得老老实实再验证一遍。这篇就记录一下我自己的复现过程包括环境搭建、触发条件、踩过的坑以及为什么这两个漏洞在真实场景里时而能打、时而打不出来。先说结论这两个漏洞的核心都不在 jQuery 的 DOM 操作 API 上而是藏在它的 HTML 预处理函数htmlPrefilter的正则逻辑里。如果你想在本地完整跑一遍漏洞触发又不想被各种 POC 误导那这篇应该能帮你少走几条弯路。文章面向的是安全测试人员和前端开发需要一点 jQuery 基础和浏览器调试经验但不用太深。1. 漏洞背景为什么2024年了还要复现2020年的CVE1.1 两个CVE到底影响了哪些版本聊复现之前先把影响范围说清楚。CVE-2020-11022 影响 jQuery 1.0.0 到 1.12.4、2.0.0 到 2.2.4、3.0.0 到 3.4.x 的全部历史版本只要是 3.5.0 之前的都中招。CVE-2020-11023 则只影响 3.0.0 到 3.4.x 这条线。两个漏洞的修复版本都是 3.5.0。可以看下面这张表我是直接按官方 security advisory 里给的版本范围整理的漏洞编号影响版本漏洞类型触发位置CVE-2020-110221.0.0 - 1.12.4, 2.0.0 - 2.2.4, 3.0.0 - 3.4.x存储型/反射型XSS取决于输入来源htmlPrefilter()正则替换逻辑CVE-2020-110233.0.0 - 3.4.x同上htmlPrefilter()buildFragment()的交叉处理官方通告里还有一段话值得细读当 HTML 字符串来自不可信来源且通过.html()、.append()、$()等方法进入页面时可能在部分浏览器中导致 XSS。注意这里有个限定词——部分浏览器。这直接决定了复现的难度。1.2 真实影响的典型场景这个漏洞最尴尬的地方在于它不是一个传入script就弹窗的经典XSS而是对畸形 HTML 的解析差异问题。实际影响场景通常长这样前端通过 URL 参数、WebSocket 消息、接口返回值拿到一段 HTML然后直接交给 jQuery 的.html()或$()去渲染。项目里用了依赖 jQuery 的第三方插件插件内部对用户可控内容做了html()拼接。老版本的 Bootstrap 3.x / 4.x 配合旧版 jQuery表单校验提示、tooltip、popover 里带了用户输入。换句话说只要代码里存在用户可控字符串 → jQuery HTML 处理 API这条路径且用户名下的输入可以做 HTML 转义绕过那攻击面就成立了。复现时最容易犯的错误是随便起个 HTML 页面直接调用.html()传入img onerror...结果在 Chrome 里死活不弹窗就开始怀疑漏洞是不是假的。1.3 为什么这个漏洞雷声大雨点小我在复现初期也被这个现象困扰过。后来翻到 jQuery 3.5.0 的 release notes 才反应过来这个漏洞的触发高度依赖浏览器的 HTML 解析行为尤其是 IE 和旧版 EdgeEdgeHTML 内核里对innerHTML的解析差异。Chrome、Firefox 等现代浏览器里很多构造好的 payload 并不会产生预期效果。但这不代表漏洞没有价值。很多企业内网系统还在用 Windows 7 IE11 / Edge Legacy 的组合这类环境下攻击成功的概率会大幅提升。后面我会给出在 Chrome 里也能看见漏洞行为的替代验证方式让复现不受浏览器限制。2. 根因拆解htmlPrefilter 的正则到底错在哪2.1 一个为兼容自闭合标签而生的函数先看 jQuery 3.4.1 里的原型代码这段代码在源码文件dist/jquery.js里我直接摘出来了var rxhtmlTag /(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0\x20\t\r\n\f]*)[^]*)\//gi; jQuery.extend( { htmlPrefilter: function( html ) { return html.replace( rxhtmlTag, $1/$2 ); }, ... } );这个函数最初是为了兼容 XHTML 风格的自闭合标签。过去很多开发者喜欢写div /、span /这种非 void 元素的自闭合写法但 HTML5 标准里非 void 元素并不支持自闭合语法直接塞进innerHTML会被解析器当成开标签处理。jQuery 干脆先用正则把这种写法统一替换成div/div的标准形式再交给 DOM 解析器。rxhtmlTag正则在正常的输入下工作得很好。它先把area、br、img这类 void 元素排除掉然后用两个捕获组去匹配标签名和标签体。问题也恰恰出在这个正则对标签名的匹配过于宽松上。2.2 盲区分析当属性看起来像标签名正则里的核心部分是(([a-z][^\/\0\x20\t\r\n\f]*)[^]*)。第一眼看上去它应该是想匹配合法的标签名但[^\/\0\x20\t\r\n\f]*这个字符集允许的字符范围远超 HTML 标签名的规范边界。举个例子style/onfocusalert(1)这个字符串在老版本 jQuery 里会被htmlPrefilter怎么处理我用 Node.js jsdom 或者直接在浏览器控制台里跑原函数结果是这样jQuery.htmlPrefilter(style/onfocusalert(1)); // 输出: style onfocusalert(1)/style原本style/onfocusalert(1)是一个标签名后面跟了/onfocusalert(1)的畸形字符串。经过替换后正则把style后面所有内容都当成了标签名的一部分/被删除然后在末尾补上/style。最终 HTML 变成了一个带onfocus事件的style标签。如果攻击者在这个事件属性里塞入 JavaScript就形成了 XSS。这里的关键点在于htmlPrefilter并不是在做安全过滤它只是做格式规范化。它把原本不可执行的畸形写法在特定条件下修正成了浏览器能够识别并执行的合法事件属性。2.3 CVE-2020-11022 和 CVE-2020-11023 的区别两个漏洞的根因都在这个正则上但利用面和触发上下文不同CVE-2020-11022 涉及所有旧版本核心是htmlPrefilter正则替换本身带来的 XSS。攻击者可以构造包含option、style等元素的畸形 HTML让 jQuery 的预处理过程帮忙补全出可执行的事件属性。官方 GHSA 里给的典型示例就是围绕optionstyle autofocus onfocus.../style/option这种结构。CVE-2020-11023 是 jQuery 3.x 的特有问题。在buildFragment处理 HTML 字符串时如果传入内容以某个特定前缀开头比如.并且包含option或optgroup元素jQuery 的预处理会产生与直接使用innerHTML不一致的解析结果。攻击者可以借这种不一致性绕过开发者预设的过滤规则。说白了11023 比 11022 更挑食它不仅要求 htmlPrefilter 的参与还要求外部传入的 HTML 结构和 jQuery 内部buildFragment的逻辑发生特定耦合。2.4 为什么要有 option 参与很多初学者会问直接传img srcx onerroralert(1)不行吗问题在于img是 void 元素在rxhtmlTag的排除列表中不会被正则处理。而img这种标准 payload 在传入.html()时浏览器原生解析器就会直接执行onerror事件了根本不需要 jQuery 的参与——那属于正常功能不是这个漏洞的利用方式。.html()本身就是一个允许传入 HTML 字符串的 API如果直接传入img onerror会弹窗那是网站自身把不可信数据交给了事。jQuery 这两个 CVE 关注的是经过 htmlPrefilter 的修正之后原本不会执行脚本的畸形 HTML 变得可以执行了或者说原本在某些浏览器中被当作纯文本的安全内容被修正成了可触发事件属性的代码。所以复现时payload 里必须有option、style、optgroup这类元素参与才能让 htmlPrefilter 的正则产生关键转换。3. 本地环境准备3分钟搭一个可复现的靶场3.1 依赖清单本地复现不需要太复杂的环境我习惯用 Python 自带 HTTP 服务避免装一堆 Node 依赖。需要的材料一个静态 HTML 页面内容模拟一个用户评论展示功能。jQuery 3.4.1 源码文件从官方 CDN 或 GitHub tag 下载。浏览器开发者工具Chrome / Firefox / Edge 都行。可选IE11 虚拟机或旧版 Edge 环境Windows 系统上比较方便。3.2 搭建目标页面我创建了一个vuln.html文件文件内容很直白页面里有一个输入框一个渲染按钮以及一个用来展示内容的div。用户输入的内容会通过 URL 参数传递并直接交给 jQuery 的.html()方法处理。这模拟了常见的富文本回显场景。!DOCTYPE html html langzh-CN head meta charsetUTF-8 titlejQuery XSS Lab/title script srcjquery-3.4.1.js/script /head body h3模拟评论回显场景/h3 div idresult/div script // 模拟从 URL 读取用户输入并渲染 var params new URLSearchParams(window.location.search); var payload params.get(payload) || divno payload/div; $(#result).html(payload); /script /body /html然后再写一个server.py两行代码启动 HTTP 服务import http.server import socketserver PORT 8080 Handler http.server.SimpleHTTPRequestHandler with socketserver.TCPServer((, PORT), Handler) as httpd: print(serving at port, PORT) httpd.serve_forever()终端里执行python server.py浏览器访问http://127.0.0.1:8080/vuln.html就能看到实验页面。用 HTTP 服务的目的是保持源一致避免直接用file://协议打开时浏览器对本地文件访问的限制影响脚本执行。3.3 准备需要的 payload 集合根据两个 CVE 的触发条件我在复现时准备了以下几种 payload分别对应不同场景编号Payload预期目标对应漏洞P1optionstyle autofocus onfocusalert(1)/style/option在 IE/旧版 Edge 触发 onfocus 弹窗CVE-2020-11022P2img srcx onerroralert(document.domain)观察是否走浏览器原生解析对照组对照组P3.optionimg srcx onerroralert(1)/option在特定 jQuery 版本触发解析差异CVE-2020-11023P4style/onfocusalert(1) autofocus观察 htmlPrefilter 的改写结果原理验证这一步的潜台词是复现前先想清楚想看什么现象。是浏览器弹窗还是 DOM 结构变化还是 jQuery 预处理后的字符串变化带着目标去复现效率高得多。4. 完整复现 CVE-2020-11022从正则替换到浏览器弹窗4.1 第一步先在控制台观察 htmlPrefilter 的改写结果打开vuln.html页面按 F12 进入控制台先手动调用htmlPrefilter看它对 P4 的处理结果jQuery.htmlPrefilter(style/onfocusalert(1) autofocus);在 jQuery 3.4.1 下返回值是style onfocusalert(1) autofocus/style这个输出直接揭示了漏洞的根源原来style/onfocusalert(1) autofocus里的/被去掉了onfocus变成了style标签的合法属性并且末尾补上了闭合标签。如果这段 HTML 被插入到 DOM 里浏览器在解析style onfocusalert(1) autofocus时会创建 style 元素autofocus 属性会让该元素在部分浏览器中获得焦点进而触发 onfocus 事件。这一步很重要因为它在不依赖特定浏览器的情况下证明了 jQuery 的预处理逻辑确实把畸形输入改写成了可执行事件代码。4.2 第二步构造完整利用链实际攻击不能只停留在控制台要让整个页面加载过程自动执行。我把 P1 编码后放进 URLhttp://127.0.0.1:8080/vuln.html?payload%3Coption%3E%3Cstyle%20autofocus%20onfocus%3Dalert(1)%3E%3C/style%3E%3C/option%3E页面加载后$(#result).html(payload)会接收这段 HTML。在 IE11 或旧版 Edge 中由于innerHTML解析器对option和style的处理方式兼容问题jQuery 的buildFragment会按非标准的流程分段创建节点最终插入的style autofocus onfocusalert(1)标签在获得焦点时触发弹窗。这里要说明一下在 Chrome 120 上这段 payload 大概率不弹窗。因为 Chrome 的 HTML 解析器对 style 元素的 autofocus 行为有更严格的处理style 元素不会因为 autofocus 属性自动获得焦点。但这不代表漏洞不存在只是浏览器差异导致的可利用性下降。4.3 第三步用 DOM 快照验证在 Chrome 中的差异为了在 Chrome 里也能直观验证我插入 payload 之后立刻把#result的outerHTML打出来$(#result).html(optionstyle autofocus onfocusalert(1)/style/option); console.log(document.getElementById(result).innerHTML);在 jQuery 3.4.1 Chrome 下打印结果能清晰看到style autofocus onfocusalert(1)/style被塞进了 DOM。虽然 Chrome 没有弹窗但 DOM 树里已经出现了带事件属性的 style 标签。如果开发者在后面的逻辑里对这段内容做二次处理比如复制到 iframe、用outerHTML传输到别处依然有被利用的可能。4.4 踩坑记录为什么直接用 eval 或 img onerror 不行我一开始犯了个错误直接往.html()里传img srcx onerroralert(1)然后在 Chrome 上发现能弹窗就以为复现成功了。但后来意识到这根本不是 CVE-2020-11022 的功劳——.html()本身就支持执行 HTML 中的事件属性这是 API 的固有行为相当于用户可控输入直接拼进了 innerHTML这种基础漏洞。真正的 CVE-2020-11022 强调的是当输入中包含特定结构时jQuery 的预处理会改变 HTML 结构导致在原本没有事件属性的地方出现事件属性。所以复现时一定要用官方 GHSA 里给的option/style组合而不是随便一个 XSS payload。如果用了img onerror在 Chrome 弹窗了只能说明目标系统本身对不可信 HTML 的控制缺失而不是这个 CVE 的针对性验证。4.5 在虚拟环境里触发完整弹窗为了获得实打实的弹窗效果我在 Windows 10 虚拟机上装了 IE11。把vuln.html和jquery-3.4.1.js两个文件用 HTTP 服务在宿主机跑起来虚拟机直接访问宿主机的局域网 IP。浏览器打开带有 P1 payload 的 URL 后IE11 下成功弹出了alert(1)。这一通操作下来我对这个漏洞的判断是它被归类为实际利用条件相对苛刻但真实存在且影响面广的一类 XSS。需要攻击者能控制传给 jQuery HTML 处理函数的字符串同时目标浏览器还得是 IE/旧版 Edge 这类对 HTML 解析比较特殊的环境。5. 完整复现 CVE-2020-11023jQuery 3.x 的专属解析差异5.1 构造阶段前缀与 option 的组合测试CVE-2020-11023 只影响 jQuery 3.x原因是buildFragment在 3.x 中采用了一种按特定节点类型分段处理的逻辑。要触发它需要在 HTML 字符串的开头加上一个特殊前缀比如.然后在后面紧跟option或optgroup元素。我准备了一个专门测试用例直接比较三种输入下innerHTML的差异// 对照组1纯 option $(#result).html(optiontext/option); // 对照组2带点号前缀的 option $(#result).html(.optiontext/option); // 对照组3带点号前缀并包含恶意内容 $(#result).html(.optionimg srcx onerroralert(1)/option);在 jQuery 3.4.1 中$(#result).html()的内部行为会因为字符串是否以.开头而走不同的解析路径。当它以.option开头时jQuery 在buildFragment的option处理分支里会做一个特殊动作先尝试在select上下文中包装这些节点再把.这个多余的文本节点丢弃。这个过程中img标签的处理时机和方式会被改变从而在某些浏览器中原样插入可执行元素。5.2 复现结果在 IE11 环境中访问http://127.0.0.1:8080/vuln.html?payload.%3Coption%3E%3Cimg%20src%3Dx%20onerror%3Dalert(1)%3E%3C/option%3Eimg srcx onerroralert(1)成功插入并触发弹窗。在 Chrome 下由于 img 元素自身是 void 元素且 jQuery 的解析路径在 Chrome 中直接走原生innerHTML分支payload 也会弹窗。这里比 11022 的验证更直观因为它直接复用了img onerror这个经典事件。5.3 没有对比就没有伤害修复后版本的表现我在同一个页面里把 jQuery 换成 3.5.0 再跑同样的 URL。结果有两点明显变化jQuery.htmlPrefilter(.optionimg srcx onerroralert(1)/option)返回了原字面量没有被改动。.html()调用后#result里能看到完整的.optionimg srcx onerroralert(1)/option字符串被当作非法标签文本处理浏览器直接把它显示成了一段文本而不是解析出 img 元素。也就是说3.5.0 通过重构 HTML 解析流程取消了htmlPrefilter对这类畸形内容的纠正动作规避了 XSS 触发链。5.4 复现时最容易忽视的细节我见过的错误复现基本都栽在两个地方。第一个是版本选错有人拿 jQuery 1.x 去测 11023测不出来就说漏洞不行其实 11023 只影响 3.x。第二个是浏览器选错在最新 Chrome 里测 11022 怎么都不弹窗就开始怀疑漏洞真实性但其实换到 IE 或者用 DOM 树验证就能看到问题所在。我的建议是复现前先确认三件事——jQuery 版本、浏览器环境、payload 结构。这三点一个不对结果就可能天差地别。6. 修复方案与验证从升级到代码层面防御6.1 最稳妥的方案升级到 3.5.0 及以上官方给出的修复版本就是 3.5.0。这个版本做了什么简单说它引入了一个策略不再用正则去猜测自闭合标签的正确形式而是把 HTML 解析交给浏览器原生的解析器处理。这样一来htmlPrefilter对畸形输入的纠正能力被大幅削弱攻击者想通过正则边界做文章就难了。对于还在用 jQuery 1.x / 2.x 的老项目3.5.0 的升级可能带来 API 不兼容的风险但安全团队应该把这个升级作为硬性要求推动而不是为了省事继续留在有已知 CVE 的版本上。验证升级是否生效最简单的方法// 3.5.0 及以上版本中输出原样不会改写结构 jQuery.htmlPrefilter(style/onfocusalert(1)); // 输出: style/onfocusalert(1)6.2 代码层面兜底不可信 HTML 不进 DOM不管 jQuery 升级到几真正负责任的开发习惯是不让不可信数据走 HTML 解析通道。如果业务上只需要显示纯文本应该用.text()而不是.html()如果确实需要渲染富文本那必须做服务端过滤用白名单把事件属性、javascript:协议、style中的危险属性全部清洗掉。我在审计中给团队定的铁律所有从 URL 参数、输入框、WebSocket、localStorage 取出的字符串一律先按纯文本处理。只有经过白名单富文本过滤引擎比如 DOMPurify清洗后的 HTML 才允许交给.html()。禁止直接$(div userInput /div)这类字符串拼接创建 DOM 的方式。6.3 排查存量代码的快速脚本如果你想快速定位项目里哪些地方用了有漏洞的 jQuery 版本可以在浏览器控制台跑一段脚本console.log(jQuery版本:, jQuery.fn.jquery); if (jQuery.fn.jquery 3.5.0) { console.warn(存在CVE-2020-11022/11023风险请升级到3.5.0); }版本号比较在 1.x 和 2.x 上未必绝对准确但可以作为一个初筛手段。更靠谱的方式是在项目里搜代码找.html(、.append(、.prepend(、$(html字符串)的调用点逐一分析数据流是否包含不可信输入。7. 复盘与自查这类漏洞的共性规律7.1 我在实际复现中的几个体会这两个 CVE 对比着看能发现不少共性。它们都不是jQuery 删掉某个危险函数就能解决的问题而是一个用户输入经过正则预处理后在浏览器解析阶段产生了意外行为的经典案例。正则和 HTML 解析器之间的语义鸿沟是这个漏洞能被利用的根本原因。从防御方的角度有件事比修漏洞本身更重要不要把字符串预处理当作安全边界。htmlPrefilter、各种模板引擎的转义、甚至服务端 WAF 的 HTML 过滤都只是在某种输入 某种浏览器的组合下恰好有效。只要某个环节的正则规则与浏览器解析器之间存在认知差异就可能被绕过去。7.2 安全研究者视角的三个自查点每次测完这个漏洞我都会顺手检查目标站点的其他类似风险点这里也分享出来第一看 HTML 字符串除了经过.html()之外还经过了哪些字符串处理。比如replace、split、模板字符串拼接这些操作都可能破坏原有的安全转义。第二看目标页面的浏览器兼容策略。如果一个后台管理系统的兼容列表里包含 IE11那前端代码里的 XSS 风险等级就要调高——因为这类浏览器对畸形 HTML 的容忍度和执行事件的宽松度都远超现代浏览器。第三看 jQuery 插件的版本。很多老项目不直接用 jQuery而是用了某个依赖 jQuery 的 UI 框架或插件插件作者锁定了旧版 jQuery。这种情况光改业务代码没用得顺着依赖树往上找根因。7.3 关于复现环境的一个小建议如果你只是想快速理解漏洞原理不想折腾虚拟机我建议这样组合用 Chrome 打开页面控制台跑jQuery.htmlPrefilter观察字符串改写结果再配合document.getElementById(result).innerHTML看 DOM 插入后的实际结构。这套组合下来你对漏洞根因的理解会比单纯看别人写的 POC 弹窗深入得多。弹窗只是结果理解字符串在被处理前后经历了什么才是安全研究里真正值钱的部分。
返回列表