用户输入的javascript:伪协议怎么防不住XSS?

Mc.金利 阅读 46

在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:字符串会不会太粗暴?

我来解答 赞 14 收藏
二维码
手机扫码查看
2 条解答
峻成
峻成 Lv1
这个问题挺常见的,DOMPurify默认配置下确实防不住javascript:伪协议,因为它只是清理HTML标签和属性,但不会主动过滤掉href属性值里的危险协议。

根本原因是:DOMPurify允许a标签和href属性,所以你输入这样的内容会被完整保留下来。浏览器解析时看到href里的javascript:协议,照样会执行。

解决方法有两个层面:

第一层:配置DOMPurify添加hook来过滤伪协议

DOMPurify支持hook机制,可以在清理后对内容做二次处理。你可以这样写:

function MessageDisplay({ userContent }) {
const sanitized = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['a', 'br', 'strong'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
hook: (dirty) => {
// 用正则把 javascript: vbscript: data: 等危险协议替换掉
return dirty.replace(/(javascript|vbscript|data):/gi, 'blocked:');
}
});

return (
<div dangerouslySetInnerHTML={{ __html: sanitized }} />
);
}


第二层:给链接加上rel="noopener noreferrer"防止钓鱼

这主要是防止用户点击后被恶意跳转或利用window.opener:

hook: (dirty) => {
let cleaned = dirty.replace(/(javascript|vbscript|data):/gi, 'blocked:');
// 给所有a标签自动加上安全属性
cleaned = cleaned.replace(/<as+/g, '<a rel="noopener noreferrer" ');
return cleaned;
}


关于直接替换javascript:字符串会不会太粗暴的问题

其实这个做法是合理的,因为javascript:这个协议本身就是危险的。你替换成blocked:或者直接移除都行,链接会变成不可点击的状态或者变成普通文本。用户那边顶多就是链接失效,总比被弹窗好吧。

如果你想更温柔一点,可以把javascript:替换成http://然后让链接失效但保持显示:

return dirty.replace(/javascript:/gi, 'http://invalid-url:');


这样链接还在,但不会执行任何脚本。

最后提个醒

如果你用的是React 16+,其实更推荐的做法是不要直接用dangerouslySetInnerHTML,而是自己解析内容手动渲染成安全的React组件。比如用React.createElement或者一些现成的markdown/html解析库,这样能从根源上避免这类问题。
点赞
2026-03-13 08:06
轩辕子涵
你这情况我遇到过,React里用 dangerouslySetInnerHTML 渲染用户提交的内容本来就有风险,DOMPurify 默认配置对 javascript: 伪协议确实处理得不够彻底。

关键点有两个:

1. **DOMPurify 要启用 KEEP_CONTENTPOLICY**
默认不会过滤伪协议,得手动加配置:

DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ['a', 'br', 'strong'],
KEEP_CONTENTPOLICY: true
});


2. **强制移除 javascript: 协议**
即使启用了 KEEP_CONTENTPOLICY,默认也不会自动过滤 javascript:,你得加个自定义钩子:

DOMPurify.addHook('afterSanitizeAttributes', function(node) {
if (node.tagName === 'A' && node.hasAttribute('href')) {
const href = node.getAttribute('href').trim().toLowerCase();
if (href.startsWith('javascript:')) {
node.removeAttribute('href');
}
}
});


这样处理完之后,javascript:alert(1) 的链接就不会保留 href 属性了,点击也不会执行。

当然,最安全的做法还是:**尽量避免使用 dangerouslySetInnerHTML**,改成文本渲染,富文本部分用白名单控制。如果只是展示留言,直接用 React 组件结构渲染用户内容会更安全。

XSS 真的防不胜防,特别是带伪协议的链接,光靠 sanitizer 不够,前端加一层清理钩子是必须的。单纯替换 javascript: 也不算粗暴,但容易漏掉变形绕过的 payload,还是结合 sanitizer 钩子更稳妥。
点赞 16
2026-02-04 23:01