一行innerHTML背后,可能藏着一次脚本注入

举报
yd_232671198 发表于 2026/11/09 09:27:28 2026/11/09
【摘要】 把用户输入拼入 innerHTML,可能让普通评论变成脚本注入入口。本文介绍纯文本使用 textContent、富文本与 Markdown 输出清洗、链接协议检查,以及 CSP 和 HttpOnly 的作用边界,并给出排查与验证方法。

一个评论列表最初只展示纯文本,后来为了方便,把渲染方式改成了字符串模板:把用户名、评论内容拼进一段 HTML,再一次性插入页面。代码变短了,但数据也获得了影响页面结构的能力。

如果评论来自用户输入,攻击者就可能提交带有特殊标签或事件属性的内容。页面把这些内容当作 HTML 解析时,恶意代码便有机会在当前站点的环境中执行。这就是常见的跨站脚本攻击,也叫 XSS。

对于本来只需要展示文字的区域,最直接的办法是使用 textContent,让浏览器按文字处理输入:

function renderComment(comment) {
  const article = document.createElement('article');
  const author = document.createElement('strong');
  const content = document.createElement('p');

  author.textContent = comment.author;
  content.textContent = comment.text;
  article.append(author, content);
  return article;
}

document.querySelector('#comments').append(renderComment({
  author: '访客',
  text: '<img src=x onerror="alert(1)">'
}));

示例假设页面存在 comments 容器。这段输入会被显示成普通文字,而不会被当成图片标签执行。它适合评论、名称、日志等纯文本内容,也避免了自己手写一套容易遗漏的 HTML 转义规则。

如果业务确实允许富文本,就需要另一套方案。不能简单删除字符串中的 <script>:HTML 还有事件属性、危险 URL、嵌套结构等多种表达方式,正则表达式很难正确覆盖浏览器的解析规则。应使用项目已有、持续维护的 HTML 清洗工具,并配置明确的标签和属性白名单。

Markdown 也不是天然安全的输入。有些渲染器允许原始 HTML,有些允许危险链接协议。要检查实际渲染配置,在最终输出进入页面之前完成适当的清洗。前端框架通常会默认转义普通文本,但使用原始 HTML 注入能力时,开发者仍然要承担对应的处理责任。

另外,安全处理要匹配具体位置。textContent 解决的是文本节点展示,不能自动保护所有属性。例如把用户提供的地址直接放进链接,仍然应该检查允许的协议:

function createLink(label, address) {
  const url = new URL(address, location.origin);
  if (!['https:', 'http:'].includes(url.protocol)) {
    throw new Error('不支持的链接协议');
  }
  const link = document.createElement('a');
  link.textContent = label;
  link.href = url.href;
  return link;
}

这段代码允许普通 HTTP 和 HTTPS 地址,拒绝 javascript: 等协议,但它没有限制目标站点。业务如果只允许站内跳转,还应比较来源或使用更严格的路径规则。创建 URL 失败时,也需要由调用方给出合适的错误反馈,避免让一条坏数据中断整个列表。

内容安全策略 CSP 可以进一步限制脚本来源,降低某些注入的影响,但策略必须结合现有资源和部署方式设计。它不能代替正确的输出处理。HttpOnly Cookie 可以限制脚本直接读取会话标识,却也不能保证注入后账号操作绝对安全,因为恶意脚本仍可能在当前页面发起请求。

验证时,除了正常中文和换行,还可以在测试环境输入尖括号、引号、事件属性字符串以及危险链接协议,观察它们是否保持为文本或被明确拒绝。对富文本,应检查允许的格式保留正常,禁止的结构也确实被清理。测试输入不必真的发送到其他用户可见的公共内容中。

审查代码时,可以先搜索 innerHTML、原始 HTML 渲染接口和动态创建脚本的位置,再追踪数据来源。很多问题并不需要重写整个页面,只要在数据进入浏览器解析器的关键位置,选对原生接口并守住边界,就能消除一类反复出现的风险。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。