从头说下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>
相关推荐
他们叫我 悦儿遥遥雨1 分钟前
.NET 8 Web开发入门(六):Blazor 全栈开发——告别 JavaScript 焦虑
前端·javascript·.net
用户2181697049308 分钟前
Flutter(九)StatelessWidget StatefullWidget
前端
paopaokaka_luck8 分钟前
基于springboot3+vue3的乡村医生诊疗管理系统(AI助手、协同过滤算法、webSocket实时聊天、Echarts图形化分析)
前端·网络·人工智能·spring boot·websocket·网络协议·echarts
编程风暴9 分钟前
3类人群的数据分析实习类型选择+4条选错红线
java·前端·数据分析
其美杰布-富贵-李10 分钟前
Vue 3 模块导入教程:import、export 与文件组织
前端·javascript·vue.js
Bigger21 分钟前
堆了 20 套主题、10+ 组件,用户还是用不出来?我给设计 Skill 加了「自动驾驶」
前端·ai编程·视觉设计
weipt24 分钟前
springboot项目运行的几种方式
javascript·css·html
前端技术官29 分钟前
前端真的比后端简单吗?
javascript·webview·dom·前端工程化·浏览器兼容性
vx-程序开发36 分钟前
【java项目分享】springboot农产品销售平台14952
java·javascript·spring boot·python·eclipse·django·php
贾天佑忆月 迷失的昵37 分钟前
.NET Web开发技术简单整理
前端·.net