DOM XSS攻击的那些坑我替你们踩过了
DOM-based XSS防护:这几种方案我踩坑无数次了
说到DOM-based XSS,我真的是又爱又恨。前阵子在做项目安全加固的时候,被这玩意儿折腾得够呛。各种防护方案试了个遍,今天把踩过的坑都记录一下。
其实DOM-based XSS和传统的XSS不太一样,它主要是在浏览器端执行恶意脚本,而不是服务器返回的内容。所以防护策略也要从客户端入手,常用的就那么几个:CSP、输入验证、输出编码、DOM操作限制。
这四种方案的核心区别
先说结论:我比较喜欢CSP + 输出编码的组合,这是最稳妥的方案。不过具体选择还是要看业务场景,毕竟每个方案都有自己的适用范围。
首先来看最严格的CSP(Content Security Policy),这玩意儿就是给浏览器定规矩的。你可以告诉浏览器哪些资源能加载,哪些不能加载。配置起来确实有点复杂,但是防护效果是最好的。
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://jztheme.com; object-src 'none';">
上面这个CSP配置就限制了脚本只能来自自身域名或者指定的CDN,基本杜绝了外部注入的可能性。但是说实话,这个配置真的很容易把自己也给拦住了,我就经常因为配错导致某些功能用不了。
再来说说输入验证,这个是最基础的防护手段。原理很简单,就是对用户输入的数据进行过滤:
function sanitizeInput(input) {
if (typeof input !== 'string') return '';
// 移除script标签
let sanitized = input.replace(/<scriptb[^<]*(?:(?!</script>)<[^<]*)*</script>/gi, '');
// 移除javascript:伪协议
sanitized = sanitized.replace(/javascript:/gi, '');
// 移除on事件处理器
sanitized = sanitized.replace(/s*onw+s*=/gi, '');
return sanitized;
}
这个方案的问题是容易被绕过,比如大小写、编码、拼接等方式都能突破检测。而且维护成本高,每次发现新绕过方式都要更新规则。
输出编码是我比较常用的一个,特别是处理HTML插入的时候:
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// 使用示例
const userInput = '<script>alert("xss")</script>';
const safeOutput = escapeHtml(userInput);
document.getElementById('content').innerHTML = safeOutput; // 安全输出
这个方法相对可靠,但需要针对不同的上下文使用不同的编码方式,比如HTML属性、URL、CSS等都需要特殊的处理函数。
最后是DOM操作限制,这个主要是限制直接的DOM修改操作:
// 禁止eval和Function构造函数
window.eval = undefined;
window.Function = undefined;
// 使用安全的DOM方法
function safeSetContent(elementId, content) {
const element = document.getElementById(elementId);
if (element) {
element.textContent = content; // 而不是innerHTML
}
}
谁更灵活?谁更省事?
CSP的配置复杂度最高,但一旦配好了基本就不需要管了。我之前为了调试CSP配了整整一个周末,各种报错日志看得我眼花。不过它的好处是全局生效,不用在代码里到处加防护逻辑。
输入验证最灵活,可以根据具体的业务需求定制规则。但是维护成本也最高,每个输入点都要单独处理。我现在一般只在关键位置使用,比如搜索框、评论框等。
输出编码是我最常用的,因为它针对性强,不会影响整体流程。比如显示用户名的地方,我就会用textContent而不是innerHTML。这种方案改动最小,但需要开发者有安全意识。
DOM操作限制适合已经存在的老项目,可以通过限制危险方法来快速加固。不过这种方法治标不治本,如果代码本身就有问题还是会中招。
性能对比:差距比我想象的小
本来以为CSP会影响页面加载速度,结果测试下来几乎没差别。可能是现代浏览器优化得比较好,CSP检查基本都是毫秒级完成的。
输入验证和输出编码的性能消耗微乎其微,主要是字符串处理的开销。只有在处理大量数据的时候才稍微明显一些,但也在可接受范围内。
DOM操作限制对性能基本没影响,因为它只是替换了原生方法的引用。不过要注意的是,如果过度限制可能会影响正常的JavaScript执行。
我的选型逻辑
对于新项目,我会直接上CSP + 输出编码的组合。CSP提供全局防护,输出编码处理具体的XSS风险点。这样既保证了安全性,又不会给开发带来太大负担。
老项目的改造我会采用渐进式的方式,先加输出编码,再逐步完善CSP配置。毕竟老项目代码复杂,一步到位风险太高。
如果遇到特殊情况,比如需要动态加载外部资源的,我会在CSP里加白名单,同时加强输入验证。这时候就要权衡安全性和功能性了。
这里特别提醒一点,不要指望一种方案解决所有问题。DOM-based XSS的攻击方式多样,最好的办法是多层防护。我在实际项目中就是这样做的,CSP负责全局,输入验证处理入口,输出编码覆盖显示,基本上就没啥问题了。
踩坑提醒:这三点一定注意
第一,CSP配置错了会连自己网站都打不开。我就遇到过一次,配了一个很严格的策略,结果整个页面的JavaScript都不工作了。建议先在开发环境调试好,再推到生产。
第二,输入验证规则写得太松等于没写,写得太严又会影响用户体验。这个平衡点需要根据具体业务来调整,多测试各种边界情况。
第三,输出编码要在正确的时机进行。有些情况下需要在服务端编码,有些情况在客户端编码,搞错了反而会引入新的漏洞。
以上是我对DOM-based XSS防护方案的对比总结,有更优的实现方式欢迎评论区交流。

暂无评论