从头说下DOM XSS

Demo此问题

  1. 写个html 叫test.html吧
html 复制代码
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <title>DOM XSS test:test.html</title>
</head>
<body>
  <h1>DOM XSS test:test.html</h1>
  <div id="app"></div>
  <script>
    const params = new URLSearchParams(location.search);
    const msg = params.get('msg') || 'hello';
    console.log('msg =', msg); // ← 自检:确认读到 < ...>
    document.getElementById('app').innerHTML = `<p>${msg}</p>`;
  </script>
</body>
</html>

2 powershell 启动web

python -m http.server 8000

3 浏览器访问 localhost:8000/test.html?msg=<img%20src=x%20οnerrοr=alert(1)>

4 看到个弹窗

原因

它直接将用户可控的 URL 参数(msg)拼接到 innerHTML,未做任何转义或过滤。攻击者可以通过构造恶意的 msg 参数注入 JavaScript 代码,从而执行任意脚本。

运行中debug所见

修复方式

1. 输入验证与输出编码

  • 输入验证 :对来自 location, document.URL, document.referrer, window.name 等的值进行严格校验,只允许预期格式(如数字、固定字符串)。
  • 输出编码 :在插入 HTML 时使用合适的编码:
    • HTML 内容 → textContentinnerText
    • 属性值 → setAttribute()
    • URL → 使用 encodeURIComponent()

2. 避免危险的 DOM API

  • 禁止使用
    • innerHTML, outerHTML, document.write()
  • 替代方案
    • 使用 textContentcreateElement() + appendChild() 来构建安全 DOM。
html 复制代码
<script>
  const params = new URLSearchParams(location.search);
  const msg = params.get('msg') || 'hello';
  const mode = (params.get('mode') || 'text').toLowerCase();
  const app = document.getElementById('app');

  // 方案 1:纯文本渲染(默认)
  function renderText(s){
    const p = document.createElement('p');
    p.textContent = s; // ✅ 不解析为 HTML
    app.replaceChildren(p);
  }

  // 方案 2:白名单消毒(示例实现,生产用成熟库更好)
  function renderSanitized(html){
    const allowedTags = new Set(['b','i','em','strong','u','a','code','pre','br']);
    const allowedAttrs = { 'a': new Set(['href','title']) };

    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    const fragment = document.createDocumentFragment();

    const walk = (node, outParent) => {
      if (node.nodeType === Node.TEXT_NODE) { outParent.appendChild(node.cloneNode()); return; }
      if (node.nodeType === Node.ELEMENT_NODE) {
        const tag = node.tagName.toLowerCase();
        if (!allowedTags.has(tag)) {
          Array.from(node.childNodes).forEach(child => walk(child, outParent)); // 剥离不安全标签,仅保留文本/安全子节点
          return;
        }
        const el = document.createElement(tag);
        for (const attr of Array.from(node.attributes)) {
          const name = attr.name.toLowerCase(), value = attr.value;
          if (name.startsWith('on')) continue; // 禁止事件属性
          const allowSet = allowedAttrs[tag];
          if (allowSet && !allowSet.has(name)) continue;
          if (tag === 'a' && name === 'href') {
            try {
              const u = new URL(value, location.origin);
              if (!['http:', 'https:', 'mailto:'].includes(u.protocol.toLowerCase())) continue; // 禁止 javascript:/data:/file:/vbscript:
            } catch (e) { continue; }
          }
          el.setAttribute(name, value);
        }
        Array.from(node.childNodes).forEach(child => walk(child, el));
        outParent.appendChild(el);
      }
    };

    Array.from(doc.body.childNodes).forEach(n => walk(n, fragment));
    app.replaceChildren(fragment);
  }

  if (mode === 'sanitize') renderSanitized(msg);
  else renderText(msg);
</script>
相关推荐
尾善爱看海2 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
驳是3 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea3 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持3 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者4 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn4 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
计算机魔术师6 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端
计算机魔术师6 小时前
OpenAI的AI代理偷偷给RubyGems下毒,我们却毫无察觉
前端
甲维斯6 小时前
0代码,0建模,3句话开发一个3D游戏!
前端·游戏·游戏开发
IT_陈寒7 小时前
Vue的响应式更新把我坑惨了,原来问题出在这
前端·人工智能·后端