您现在的位置是:网站首页 > 防御 XSS 的最佳实践文章详情

防御 XSS 的最佳实践

理解 XSS 攻击的本质

XSS(跨站脚本攻击)的核心在于攻击者通过注入恶意脚本到受信任的网站上,当其他用户访问该网站时,这些脚本会在用户的浏览器中执行。XSS 攻击主要分为三类:存储型、反射型和 DOM 型。存储型 XSS 将恶意脚本永久存储在目标服务器上,反射型 XSS 通过 URL 参数即时反射恶意脚本,而 DOM 型 XSS 则完全在客户端执行,不经过服务器。

// 一个典型的反射型 XSS 示例
// 攻击者构造的恶意 URL:http://example.com/search?query=<script>alert('XSS')</script>
const query = new URLSearchParams(window.location.search).get('query');
document.getElementById('results').innerHTML = query; // 直接插入未转义的用户输入

输入验证与过滤

对所有用户输入进行严格的验证是防御 XSS 的第一道防线。前端和后端都应该实施验证规则,确保输入符合预期格式。使用白名单机制比黑名单更安全,因为黑名单很难覆盖所有可能的攻击向量。

// 使用正则表达式验证邮箱格式
function validateEmail(email) {
  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return re.test(String(email).toLowerCase());
}

// 过滤 HTML 标签的简单实现
function stripTags(str) {
  return str.replace(/<[^>]*>/g, '');
}

输出编码

在将用户提供的内容插入到 HTML 中时,必须进行适当的编码。不同的上下文需要不同的编码方式:

  1. HTML 内容编码:将特殊字符转换为 HTML 实体
  2. HTML 属性编码:对属性值进行编码
  3. JavaScript 编码:当动态生成 JavaScript 代码时
  4. URL 编码:用于 URL 参数
// HTML 实体编码函数
function htmlEncode(str) {
  return str.replace(/[&<>'"]/g, 
    tag => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      "'": '&#39;',
      '"': '&quot;'
    }[tag]));
}

// 使用示例
document.getElementById('user-comment').textContent = userInput; // 安全方式
// 而不是:
document.getElementById('user-comment').innerHTML = userInput; // 危险方式

使用现代前端框架的安全特性

现代前端框架如 React、Vue 和 Angular 都内置了 XSS 防护机制:

  • React:默认对所有渲染内容进行转义,除非使用 dangerouslySetInnerHTML
  • Vuev-text 指令自动转义内容,v-html 需要显式使用
  • Angular:插值语法 {{}} 自动转义,[innerHTML] 需要谨慎使用
// React 中的安全实践
function SafeComponent({ userInput }) {
  return (
    <div>
      {/* 安全:自动转义 */}
      <div>{userInput}</div>
      
      {/* 危险:需要确保 userInput 是可信的 */}
      <div dangerouslySetInnerHTML={{ __html: userInput }} />
    </div>
  );
}

内容安全策略 (CSP)

CSP 是一个强大的防御层,通过 HTTP 头告诉浏览器哪些资源可以加载和执行。一个严格的 CSP 可以阻止大多数 XSS 攻击:

Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:

关键指令包括:

  • default-src:默认加载策略
  • script-src:控制 JavaScript 加载
  • style-src:控制 CSS 加载
  • img-src:控制图片加载
  • connect-src:控制 AJAX、WebSocket 等连接

安全的 Cookie 实践

防止通过 XSS 窃取 Cookie:

  • 设置 HttpOnly 标志,防止 JavaScript 访问
  • 设置 Secure 标志,仅通过 HTTPS 传输
  • 考虑使用 SameSite 属性防止 CSRF
// Express 中设置安全 Cookie 的示例
app.get('/login', (req, res) => {
  res.cookie('sessionID', '12345', {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000
  });
});

DOM 型 XSS 的防御

DOM 型 XSS 特别危险,因为它完全在客户端发生,不经过服务器。防御措施包括:

  1. 避免使用 innerHTML,改用 textContent
  2. 谨慎处理 location.hashdocument.referrer 等来源
  3. 使用安全的 API 如 DOMPurify 清理 HTML
// 使用 DOMPurify 清理 HTML
import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize(dirtyHtml);
document.getElementById('content').innerHTML = clean;

安全的第三方库使用

第三方库可能成为 XSS 的入口:

  • 定期更新依赖库
  • 使用受信任的来源
  • 审查库的源代码,特别是处理用户输入的部分
  • 使用子资源完整性 (SRI) 验证 CDN 资源
<!-- 使用 SRI 的 script 标签示例 -->
<script src="https://example.com/script.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous"></script>

测试与自动化检测

建立自动化的安全测试流程:

  • 使用工具如 OWASP ZAP、Burp Suite 进行扫描
  • 实施单元测试检查 XSS 漏洞
  • 进行代码审查,重点关注用户输入处理
  • 使用 linter 插件检测潜在危险模式
// 简单的 XSS 检测单元测试示例
test('should escape HTML in user input', () => {
  const maliciousInput = '<script>alert("XSS")</script>';
  const safeOutput = escapeHtml(maliciousInput);
  expect(safeOutput).not.toContain('<script>');
  expect(safeOutput).toContain('&lt;script&gt;');
});

安全的富文本编辑处理

处理富文本内容时需要特别小心,因为需要保留一些 HTML 标签但过滤危险的属性和标签:

  1. 使用专门的库如 DOMPurifysanitize-html
  2. 定义允许的标签和属性白名单
  3. 过滤 on* 事件处理程序和 javascript: 协议
// 使用 sanitize-html 的配置示例
const sanitizeHtml = require('sanitize-html');

const clean = sanitizeHtml(dirtyHtml, {
  allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
  allowedAttributes: {
    'a': ['href', 'title']
  },
  allowedSchemes: ['http', 'https']
});

安全的 JSON 数据处理

当动态生成 JavaScript 或 JSON 时,确保正确处理数据:

  1. 使用 JSON.stringify() 将数据转换为字符串
  2. 在 HTML 中嵌入 JSON 时,先进行 HTML 转义
  3. 避免拼接字符串生成 JavaScript 代码
// 安全的 JSON 嵌入方式
const userData = {
  name: "John \"<script>alert(1)</script>",
  age: 30
};

// 正确做法
const safeJson = JSON.stringify(userData)
  .replace(/</g, '\\u003c'); // 额外转义 < 字符
document.getElementById('data').textContent = safeJson;

// 错误做法:直接拼接
const unsafeScript = `<script>var data = ${JSON.stringify(userData)};</script>`;

HTTP 安全头部的综合应用

除了 CSP,其他安全头部也能增强 XSS 防护:

X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer-when-downgrade
Feature-Policy: geolocation 'none'; microphone 'none'

这些头部可以:

  • 启用浏览器内置的 XSS 过滤器
  • 防止 MIME 类型嗅探攻击
  • 阻止点击劫持
  • 控制 referrer 信息泄露
  • 限制敏感 API 的使用

安全的动态代码执行

避免使用 eval()new Function()setTimeout/setInterval 的字符串参数形式,这些都可能成为 XSS 的入口点:

// 危险的做法
const userInput = 'alert("XSS")';
eval(userInput);

// 安全的替代方案
const safeFunction = () => console.log('Safe');
setTimeout(safeFunction, 100);

// 如果必须动态执行代码,确保完全控制输入源
const trustedInput = validateAndSanitize(userInput);
const func = new Function('param', `console.log(${JSON.stringify(trustedInput)})`);

安全的 URL 处理

处理 URL 时需要特别注意:

  1. 验证 URL 的协议(禁止 javascript:
  2. 使用 URL API 而不是字符串拼接
  3. 对动态生成的 URL 进行编码
// 安全的 URL 验证
function isSafeUrl(url) {
  try {
    const parsed = new URL(url, window.location.href);
    return ['http:', 'https:', 'mailto:', 'tel:'].includes(parsed.protocol);
  } catch {
    return false;
  }
}

// 使用示例
const userLink = getUserInput();
if (isSafeUrl(userLink)) {
  document.getElementById('link').href = userLink;
} else {
  // 处理不安全 URL
}

前端模板的安全使用

使用模板引擎时要注意:

  1. 选择自动转义的模板引擎(如 Handlebars、EJS)
  2. 了解模板的转义行为
  3. 避免拼接未转义的 HTML
// Handlebars 安全示例
const source = "<p>Hello, {{name}}!</p>";
const template = Handlebars.compile(source);
const safeHtml = template({ name: userInput }); // 自动转义

// 如果需要不转义,必须显式声明
const unsafeTemplate = Handlebars.compile("<p>Hello, {{{rawName}}}!</p>");

安全的 WebSocket 和实时通信

实时通信也可能引入 XSS:

  1. 验证服务器推送的消息
  2. 在客户端渲染前对消息内容进行编码
  3. 使用结构化消息而非原始 HTML
// WebSocket 消息处理示例
socket.onmessage = function(event) {
  const message = JSON.parse(event.data);
  const content = document.createElement('div');
  content.textContent = message.text; // 安全渲染
  document.getElementById('chat').appendChild(content);
  
  // 而不是:
  // document.getElementById('chat').innerHTML += message.html; // 危险
};

移动端和混合应用的特殊考虑

移动混合应用(如 Cordova、Capacitor)需要注意:

  1. 禁用 allow-navigation 到不可信源
  2. 谨慎使用 InAppBrowser 和 WebView
  3. 验证从原生层传递到 WebView 的数据
<!-- Cordova config.xml 安全配置示例 -->
<allow-navigation href="https://example.com/*" />
<allow-intent href="https://*/*" />
<allow-intent href="tel:*" />

持续的安全教育与意识培养

技术措施之外,团队的安全意识同样重要:

  1. 定期进行安全培训
  2. 建立安全编码规范
  3. 鼓励报告潜在漏洞的文化
  4. 保持对新型 XSS 攻击手法的了解

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步