AI 生成的网页为什么可能存在 XSS 漏洞?从 innerHTML 到 textContent 的安全讨论

举报
yd_213270272 发表于 2026/11/09 00:30:42 2026/11/09
【摘要】 前言现在使用 AI 编程助手开发网页已经非常方便。例如,只需要输入一句话:“帮我开发一个待办事项工具,支持输入任务名称并添加到列表。”AI 就可以快速生成 HTML、CSS 和 JavaScript。但有一个细节很容易被忽略。AI 生成的代码中,可能会出现这样的写法:list.innerHTML += `<li>${taskName}</li>`;表面上看,这段代码很简单,也能正常显示任务。...


前言

现在使用 AI 编程助手开发网页已经非常方便。

例如,只需要输入一句话:

“帮我开发一个待办事项工具,支持输入任务名称并添加到列表。”

AI 就可以快速生成 HTML、CSS 和 JavaScript。

但有一个细节很容易被忽略。

AI 生成的代码中,可能会出现这样的写法:

list.innerHTML += `<li>${taskName}</li>`;

表面上看,这段代码很简单,也能正常显示任务。

但是,如果 taskName 来自用户输入,并且没有进行安全处理,就可能产生跨站脚本攻击风险。

这类问题通常称为 XSS(Cross-Site Scripting,跨站脚本攻击)。

本文通过一个简单的网页案例,介绍 XSS 产生的原因,以及如何使用 JavaScript 原生 API 编写更安全的 DOM 更新代码。


一、什么是 XSS?

XSS 是一种常见的 Web 安全漏洞。

它通常发生在应用程序将不可信内容当成 HTML 或脚本交给浏览器解析时。

例如,一个网页允许用户填写评论:

这个工具很好用!

正常情况下,网页应该把这段内容当作普通文本显示。

但如果用户提交的不是普通文字,而是包含 HTML 标签、事件处理属性等内容的字符串,程序又直接使用 innerHTML 插入,就可能使浏览器解析出开发者没有预期的元素或行为。

XSS 可能造成的影响包括:

  • 修改网页显示内容。
  • 冒充正常页面诱导用户操作。
  • 在受害者浏览器中执行未经授权的脚本。
  • 在权限允许的情况下访问页面中的敏感数据或以用户身份发起操作。

需要注意,XSS 不代表攻击者一定能够读取所有 Cookie。

例如,设置了 HttpOnly 的 Cookie 不能通过普通 JavaScript 直接读取,但 XSS 仍可能造成其他安全风险。

二、为什么 innerHTML 容易产生问题?

来看一个简单例子:

<div id="result"></div>

JavaScript:

const result = document.getElementById("result");

const userInput = "<strong>你好</strong>";

result.innerHTML = userInput;

运行后,浏览器会解析字符串中的 HTML 标签。

最终显示的“你好”会变成粗体。

这就是 innerHTML 的特点:

它不是单纯显示字符串,而是会把字符串作为 HTML 片段进行解析。

如果字符串完全由开发者控制,且不存在不可信内容,使用 innerHTML 并不必然造成漏洞。

问题在于,当不可信输入参与 HTML 拼接时,浏览器可能创建开发者原本不想允许的元素和属性。

因此,不能把 innerHTML 当作普通文本显示接口使用。

三、textContent 为什么更适合显示用户输入?

对于只需要显示文字的场景,可以使用:

element.textContent

例如:

const result = document.getElementById("result");

const userInput = "<strong>你好</strong>";

result.textContent = userInput;

这时,页面会直接显示:

<strong>你好</strong>

浏览器不会把它解析成粗体标签。

原因在于:

textContent 将输入作为文本内容处理,而不是作为 HTML 标记解析。

对于评论、任务名称、搜索关键词、用户昵称等纯文本字段,这通常是更安全、更符合预期的选择。

当然,textContent 不是可以解决所有前端安全问题的万能方案。它主要解决的是将字符串作为文本插入 DOM 时的 HTML 解析风险。

四、实战:修复一个 AI 生成的待办列表

假设我们让 AI 开发一个简单的待办事项网页。

页面允许用户输入任务名称,然后添加到列表。

一种不安全的实现可能是:

function addTask(taskName) {
  const list = document.getElementById("taskList");

  list.innerHTML += `<li>${taskName}</li>`;
}

这里存在两个问题。

第一,用户输入被直接拼接到 HTML 字符串中。

第二,innerHTML += 会重新解析并替换列表中的部分 DOM 内容,可能导致原有元素状态或事件监听器受到影响。

更合理的方式是创建真实的 DOM 元素。

function addTask(taskName) {
  const list = document.getElementById("taskList");

  const item = document.createElement("li");

  item.textContent = taskName;

  list.appendChild(item);
}

这段代码的关键区别是:

  • createElement() 创建明确的 HTML 元素。
  • textContent 设置普通文本。
  • appendChild() 将元素加入列表。

这样就不需要把用户输入作为 HTML 代码解析。

五、完整示例:一个安全的待办事项小工具

下面使用原生 HTML 和 JavaScript 实现一个简单示例。

新建 index.html:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1"
  >
  <title>安全待办列表</title>

  <style>
    body {
      max-width: 600px;
      margin: 40px auto;
      padding: 20px;
      font-family: system-ui, sans-serif;
      line-height: 1.6;
    }

    input {
      padding: 10px;
      width: 65%;
      box-sizing: border-box;
    }

    button {
      padding: 10px 16px;
      cursor: pointer;
    }

    li {
      padding: 8px 0;
      overflow-wrap: anywhere;
    }
  </style>
</head>

<body>
  <h1>安全待办列表</h1>

  <form id="taskForm">
    <label for="taskInput">任务名称</label>

    <div>
      <input
        id="taskInput"
        maxlength="100"
        required
        placeholder="请输入任务"
      >

      <button type="submit">添加</button>
    </div>
  </form>

  <ul id="taskList"></ul>

  <script>
    const form = document.getElementById("taskForm");

    const input = document.getElementById("taskInput");

    const list = document.getElementById("taskList");

    form.addEventListener("submit", (event) => {
      event.preventDefault();

      const taskName = input.value.trim();

      if (!taskName) {
        return;
      }

      const item = document.createElement("li");

      item.textContent = taskName;

      list.appendChild(item);

      input.value = "";
      input.focus();
    });
  </script>
</body>
</html>

这个页面不需要后端,也不需要第三方 JavaScript 库。

直接使用浏览器打开就可以运行。

如何测试?

首先输入正常任务:

学习 JavaScript

页面应正常添加一条任务。

然后输入包含 HTML 标记的内容:

<strong>测试任务</strong>

如果使用的是上面的安全实现,网页应该将完整字符串显示为普通文本,而不是把“测试任务”渲染为粗体。

还可以输入:

<img src="invalid-image" onerror="alert(1)">

在这个示例中,它应该被显示为文本,不应该创建真正的图片元素,也不应该触发其中的事件处理代码。

这一测试可以帮助发现是否存在把输入错误地当作 HTML 解析的问题。

不过,单次测试通过不代表整个应用不存在 XSS。实际项目中还要检查其他 DOM 更新位置和数据流。

六、如果业务确实需要显示 HTML 怎么办?

有些项目需要展示富文本。

例如:

  • 博客文章。
  • Markdown 转换后的内容。
  • 用户评论中的有限格式。
  • 带有链接和代码块的文档。

这时,单纯使用 textContent 无法满足格式展示要求。

但也不能直接把所有用户内容交给 innerHTML。

更合适的方法是使用经过安全处理的 HTML,并限制允许的元素和属性。

例如,只允许:

p、strong、em、ul、li、a

而不允许任意脚本、事件处理属性或危险 URL。

对于实际项目,可以考虑使用经过维护的 HTML 清理库,例如 DOMPurify,并按具体使用场景配置允许的标签和属性。

需要注意:

清理 HTML 不能简单依靠正则表达式删除 script 标签。

因为潜在危险内容不只存在于 <script> 标签中,还可能出现在事件属性、URL 及其他 HTML 解析上下文中。

如果开发的是纯文本工具,通常没有必要引入富文本解析功能,直接使用 textContent 更简单。

七、另一个容易忽略的风险:动态链接

有些 AI 生成的网页允许用户输入 URL,然后创建链接。

例如:

const link = document.createElement("a");

link.textContent = "点击查看";

link.href = userInput;

这里虽然使用了 textContent,但仍需要注意 href 的内容。

如果输入是不可信的 URL,就应该限制允许的协议。

例如,只允许 HTTP 和 HTTPS:

function getSafeUrl(input) {
  try {
    const url = new URL(input);

    if (
      url.protocol !== "https:" &&
      url.protocol !== "http:"
    ) {
      return null;
    }

    return url.href;
  } catch {
    return null;
  }
}

使用:

const safeUrl = getSafeUrl(userInput);

if (safeUrl) {
  link.href = safeUrl;
  link.target = "_blank";
  link.rel = "noopener noreferrer";
}

这说明:

即使没有使用 innerHTML,开发者仍然需要根据不同的输出位置进行安全处理。

文本内容、HTML 片段和 URL,并不是同一种安全上下文。

此外,允许 HTTP/HTTPS 并不意味着目标网站一定可信。对于存在钓鱼风险的应用,还可能需要进一步限制域名。

八、CSP 能否防止 XSS?

CSP 是 Content Security Policy 的缩写,通常翻译为内容安全策略。

它可以通过 HTTP 响应头限制网页允许加载和执行的资源。

例如,一个简化的策略可能是:

Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

这类策略能够降低部分脚本注入带来的风险。

但是,CSP 不能代替正确的 DOM 操作和输入输出处理。

同时,CSP 的实际效果取决于部署方式及完整策略配置。

例如,上面的策略会阻止某些内联脚本执行,因此如果项目仍然使用内联 JavaScript,就需要调整代码结构或采用合适的 nonce、哈希等机制。

对于正式部署的应用,应当结合实际资源需求制定和测试 CSP,而不是直接复制一段配置就认为已经安全。

九、给 AI 编程助手增加哪些安全要求?

在使用 AI Shell、Codex 或其他 AI 编程工具开发网页时,可以把安全要求写进项目提示词。

例如:

所有用户输入、搜索关键词、任务名称和其他不可信字符串,默认使用 textContent 显示。禁止将未经安全处理的不可信内容直接拼接到 innerHTML、outerHTML 或 insertAdjacentHTML 中。动态 URL 必须进行协议校验。确需富文本时,应采用适当的 HTML 清理方案,并补充相关安全测试。

我认为这种要求比简单写一句“注意安全”更有效。

因为它明确指出了:

  1. 哪些内容是不可信数据。
  2. 哪些 DOM API 需要特别注意。
  3. 推荐使用什么替代方案。
  4. 如何通过测试验证。

对于 AI 自动生成的网站,还可以增加一项验收测试:

使用包含 HTML 标记和事件处理属性的字符串作为输入,确认其不会被意外解析或执行。

十、总结

AI 编程助手可以大幅减少前端代码编写时间,但快速生成代码并不代表代码天然安全。

尤其是涉及用户输入和动态网页内容时,开发者需要理解不同 DOM API 的行为区别。

通过本文可以总结出几个关键原则:

  • innerHTML 会解析 HTML,不适合直接插入不可信字符串。
  • textContent 更适合显示普通文本。
  • createElement() 可以帮助我们明确构建页面结构。
  • 动态链接需要进行 URL 协议校验。
  • 富文本需要使用适当的 HTML 清理方案。
  • CSP 可以提供额外防护,但不能替代安全编码。
  • AI 生成的网页应该包含必要的安全测试。

对于简单的网页项目,我更倾向于采用一个原则:

能用文本 API 解决的问题,就不要让浏览器把用户输入当作 HTML 解析。

这样不仅可以减少安全风险,也能够让代码更容易理解、测试和维护。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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