2019年冬天,漏洞一位做电商的漏洞朋友半夜给我打电话,说网站首页突然弹出了个奇怪的漏洞东西——一个本不该存在的广告位。他以为是漏洞服务器被入侵了,查了半天日志,漏洞最后发现问题出在一个jQuery插件上。漏洞那个插件调用了$.html(),漏洞把用户提交的漏洞评论内容直接塞进了DOM,没做转义。漏洞有人提交了一段带<script>的漏洞评论,XSS就这么发生了。漏洞
这不是漏洞jQuery本身的漏洞。准确说,漏洞jQuery核心代码里被公开确认的漏洞严重安全漏洞并不多。真正的漏洞问题在于,jQuery的设计哲学让开发者太容易写出不安全的代码。它把DOM操作变得极其简单,简单到人们忘了背后发生了什么。
那些年被jQuery放大的风险
最典型的例子是$.html()、$.append()、$.prepend()这几个方法。它们接受字符串参数,如果这段字符串来自用户输入且未经处理,浏览器会老老实实把它当HTML解析。jQuery不会帮你做任何过滤。它假设你知道自己传的是什么。
2015年曝出的CVE-2015-9251是个好例子。jQuery的$.ajax()在处理跨域请求时,如果dataType设为jsonp,会执行返回内容中的JavaScript。攻击者可以构造恶意JSONP响应,在受害者浏览器里执行任意代码。这个漏洞影响jQuery 3.0.0之前的版本,直到3.0.0-rc1才修复。
更早的CVE-2014-6071涉及$.parseHTML(),某些情况下仍然会执行内联脚本。jQuery团队当时的回应是“这是预期行为”——因为$.parseHTML()默认就会执行脚本,除非你显式传入keepScripts: false。这个默认值的选择,现在看来相当危险。
真正的问题不是jQuery,是用jQuery的人
我翻过不少老项目的代码。有个规律:越是依赖jQuery的项目,越容易出现DOM型XSS。原因不复杂。jQuery让$(‘#content’).html(userInput)这种写法变得太顺手了。开发者脑子里想的是“把内容放进去”,而不是“这段内容会不会被当作代码执行”。
对比一下原生API。element.textContent = userInput天然安全,浏览器不会解析HTML标签。element.innerHTML = userInput有风险,但至少方法名里的“innerHTML”会让人多想一想。jQuery的$.html()把这种警觉性抹掉了。
还有个容易被忽略的点:jQuery的选择器引擎Sizzle。在旧版本中,如果选择器字符串来自用户输入,比如$(location.hash),攻击者可以构造特殊的选择器触发XSS。jQuery 1.9之后对选择器做了更严格的限制,但仍有绕过案例。
现在还需要担心吗
jQuery 3.x系列修复了已知的严重漏洞。如果你还在用1.x或2.x,那确实该升级了。但升级解决不了根本问题——只要开发者继续用$.html()处理用户输入,XSS就一直在。
我的建议很直接:能用textContent就别用innerHTML,能用$.text()就别用$.html()。如果确实需要渲染HTML,用DOMPurify这类库先过滤一遍。jQuery本身不负责你的安全,它只是个工具。工具没有恶意,但用工具的人得长点心。
那个电商朋友后来把评论渲染改成了$.text(),广告位再也没弹出来过。问题从来不在jQuery,在他写的那行代码。