从头说下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>
相关推荐
悟空瞎说7 分钟前
# Dispatch(GCD)苹果官方文档 白话文讲解(Objective‑C 版本文档)
javascript
七牛开发者18 分钟前
Coding Agent 如何跑稳长任务?从上下文管理到运行时状态
前端·javascript·后端
半个落月31 分钟前
从 CSR 到 Server Component:吃透 Next.js 16 App Router 路由、布局与 SEO
前端·next.js
七牛开发者36 分钟前
拆解 DeepSeek Harness:Profile 与 Bundle 如何装配运行时
前端·javascript·后端
葡萄城技术团队43 分钟前
表格智能体系列 · 1:一句话怎么变成一次表格操作
前端
梦曦i1 小时前
Vue-Router 2.4.0 新增可控重定向功能
前端·uni-app
deli0070071 小时前
Markdown 实时预览器:左边写右边看,排版效率翻倍
前端·ai编程
郑州光合科技余经理1 小时前
海外版外卖系统架构:订单怎么流转、权限怎么分
java·开发语言·前端·系统架构·uni-app·php·ai编程
pengyu1 小时前
【Kotlin 协程修仙录 · 炼虚境 · 初阶】 | 虚空造物:Channel 基础与协程间通信的管道艺术
android·前端·kotlin
提线木偶2 小时前
CORS 到底谁说了算?一份跨域配置的避坑指南
前端·后端