用户输入的javascript:伪协议怎么防不住XSS?
在React项目里处理用户提交的留言内容时,发现如果用户输入类似javascript:alert(1)这样的内容,直接渲染后居然真的会执行脚本。虽然用了DOMPurify清理和转义特殊字符,但测试输入点击后点击链接还是会触发弹窗…
function MessageDisplay({ userContent }) {
// 使用DOMPurified清理内容
const sanitized = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['a', 'br', 'strong']
});
return (
<div
dangerouslySetInnerHTML={{ __html: sanitized }}
/>
);
}
尝试过设置Content-Security-Policy头和属性转义,但javascript:伪协议似乎能绕过过滤。这种情况下该怎么彻底阻断伪协议执行?单纯替换掉javascript:字符串会不会太粗暴?
根本原因是:DOMPurify允许a标签和href属性,所以你输入
这样的内容会被完整保留下来。浏览器解析时看到href里的javascript:协议,照样会执行。解决方法有两个层面:
第一层:配置DOMPurify添加hook来过滤伪协议
DOMPurify支持hook机制,可以在清理后对内容做二次处理。你可以这样写:
第二层:给链接加上rel="noopener noreferrer"防止钓鱼
这主要是防止用户点击后被恶意跳转或利用window.opener:
关于直接替换javascript:字符串会不会太粗暴的问题
其实这个做法是合理的,因为javascript:这个协议本身就是危险的。你替换成blocked:或者直接移除都行,链接会变成不可点击的状态或者变成普通文本。用户那边顶多就是链接失效,总比被弹窗好吧。
如果你想更温柔一点,可以把javascript:替换成http://然后让链接失效但保持显示:
这样链接还在,但不会执行任何脚本。
最后提个醒
如果你用的是React 16+,其实更推荐的做法是不要直接用dangerouslySetInnerHTML,而是自己解析内容手动渲染成安全的React组件。比如用React.createElement或者一些现成的markdown/html解析库,这样能从根源上避免这类问题。
关键点有两个:
1. **DOMPurify 要启用 KEEP_CONTENTPOLICY**
默认不会过滤伪协议,得手动加配置:
2. **强制移除 javascript: 协议**
即使启用了 KEEP_CONTENTPOLICY,默认也不会自动过滤 javascript:,你得加个自定义钩子:
这样处理完之后,javascript:alert(1) 的链接就不会保留 href 属性了,点击也不会执行。
当然,最安全的做法还是:**尽量避免使用 dangerouslySetInnerHTML**,改成文本渲染,富文本部分用白名单控制。如果只是展示留言,直接用 React 组件结构渲染用户内容会更安全。
XSS 真的防不胜防,特别是带伪协议的链接,光靠 sanitizer 不够,前端加一层清理钩子是必须的。单纯替换 javascript: 也不算粗暴,但容易漏掉变形绕过的 payload,还是结合 sanitizer 钩子更稳妥。