您现在的位置是:网站首页 > 前端框架(React/Vue/Angular)的 XSS 防护机制文章详情

前端框架(React/Vue/Angular)的 XSS 防护机制

XSS 攻击的基本原理

XSS(跨站脚本攻击)是一种常见的 Web 安全漏洞,攻击者通过在网页中注入恶意脚本,当其他用户访问该页面时,这些脚本会在用户的浏览器中执行。XSS 攻击可以分为三种类型:反射型、存储型和 DOM 型。

// 一个简单的反射型 XSS 示例
// 假设 URL 为:http://example.com/search?query=<script>alert('XSS')</script>
const query = new URLSearchParams(window.location.search).get('query');
document.getElementById('search-results').innerHTML = query; // 危险操作

React 的 XSS 防护机制

React 默认提供了良好的 XSS 防护,主要通过 JSX 的自动转义机制实现。React DOM 在渲染所有内容之前都会进行转义,将特殊字符转换为它们的 HTML 实体形式。

function SafeComponent() {
  const userInput = '<script>alert("XSS")</script>';
  return <div>{userInput}</div>; // 安全,React 会自动转义
}

dangerouslySetInnerHTML 的注意事项

虽然 React 默认安全,但使用 dangerouslySetInnerHTML 时会绕过这种保护,需要特别小心:

function DangerousComponent() {
  const html = '<span style="color:red">用户内容</span>';
  
  // 安全做法:先清理 HTML
  const cleanHtml = DOMPurify.sanitize(html);
  
  return (
    <div 
      dangerouslySetInnerHTML={{ __html: cleanHtml }} 
    />
  );
}

其他安全实践

  1. 属性绑定安全:React 会自动处理属性绑定中的特殊字符
const userHref = "javascript:alert('XSS')";
<a href={userHref}>点击</a> // React 会阻止这种危险操作
  1. 使用 React 的 Context API 而非直接将数据插入脚本
// 不安全的做法
<script dangerouslySetInnerHTML={{ __html: `var config = ${JSON.stringify(userData)}` }} />

// 安全的做法
const ConfigContext = React.createContext();
<ConfigContext.Provider value={userData}>
  {/* 子组件 */}
</ConfigContext.Provider>

Vue 的 XSS 防护机制

Vue 也提供了自动的 HTML 内容转义,使用双大括号语法 {{ }} 时会自动转义内容。

<template>
  <div>{{ userInput }}</div> <!-- 自动转义 -->
</template>

<script>
export default {
  data() {
    return {
      userInput: '<script>alert("XSS")</script>'
    }
  }
}
</script>

v-html 指令的风险

类似于 React 的 dangerouslySetInnerHTML,Vue 的 v-html 也会绕过保护:

<template>
  <div v-html="sanitizedHtml"></div>
</template>

<script>
import DOMPurify from 'dompurify';

export default {
  data() {
    return {
      rawHtml: '<span onclick="alert(\'XSS\')">点击我</span>'
    }
  },
  computed: {
    sanitizedHtml() {
      return DOMPurify.sanitize(this.rawHtml);
    }
  }
}
</script>

Vue 的其他安全特性

  1. 属性绑定安全:Vue 会自动处理动态属性中的潜在危险
<template>
  <a :href="userHref">链接</a>
</template>

<script>
export default {
  data() {
    return {
      userHref: "javascript:alert('XSS')"
    }
  }
}
</script>
<!-- Vue 会安全地处理这个 href -->
  1. 自定义指令的安全使用
<template>
  <div v-safe-html="rawHtml"></div>
</template>

<script>
import DOMPurify from 'dompurify';

export default {
  directives: {
    safeHtml: {
      inserted(el, binding) {
        el.innerHTML = DOMPurify.sanitize(binding.value);
      },
      update(el, binding) {
        el.innerHTML = DOMPurify.sanitize(binding.value);
      }
    }
  },
  data() {
    return {
      rawHtml: '<img src=x onerror=alert(1)>'
    }
  }
}
</script>

Angular 的 XSS 防护机制

Angular 的模板系统默认会对所有绑定进行净化,根据上下文采用不同的净化策略。

@Component({
  template: `
    <div>{{ userInput }}</div> <!-- 自动转义 -->
  `
})
export class SafeComponent {
  userInput = '<script>alert("XSS")</script>';
}

bypassSecurityTrust API

当确实需要插入 HTML 时,可以使用 Angular 的 DomSanitizer 服务:

import { DomSanitizer } from '@angular/platform-browser';

@Component({
  template: `
    <div [innerHTML]="safeHtml"></div>
  `
})
export class UnsafeComponent {
  constructor(private sanitizer: DomSanitizer) {}
  
  rawHtml = '<span style="color:red">用户内容</span>';
  safeHtml = this.sanitizer.bypassSecurityTrustHtml(this.rawHtml);
}

Angular 的安全上下文

Angular 会根据绑定目标自动选择正确的净化方式:

  1. HTML 内容:使用 innerHTML 绑定时会净化 HTML
  2. 样式绑定:净化 CSS 内容
  3. URL 绑定:净化 URL 以防止 JavaScript URL
  4. 资源 URL:净化媒体资源 URL
@Component({
  template: `
    <a [href]="userUrl">链接</a>
    <img [src]="userImage">
    <div [style.background]="userStyle"></div>
  `
})
export class VariousBindingsComponent {
  userUrl = 'javascript:alert("XSS")'; // 会被净化
  userImage = 'data:image/png;base64,...'; // 安全
  userStyle = 'url("javascript:alert(1)")'; // 会被净化
}

框架无关的 XSS 防护策略

内容安全策略 (CSP)

CSP 是防御 XSS 的强大工具,通过 HTTP 头设置:

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

输入验证和输出编码

无论使用哪个框架,都应该:

  1. 验证所有用户输入
  2. 根据输出上下文进行适当的编码
// HTML 编码函数
function htmlEncode(str) {
  return str.replace(/[&<>'"]/g, 
    tag => ({
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      "'": '&#39;',
      '"': '&quot;'
    }[tag]));
}

使用专门的库处理危险内容

推荐使用 DOMPurify 等库处理 HTML:

import DOMPurify from 'dompurify';

const dirty = '<img src=x onerror=alert(1)>';
const clean = DOMPurify.sanitize(dirty);

安全的 Cookie 设置

// 设置 HttpOnly 和 Secure 的 Cookie
document.cookie = `sessionId=${sessionId}; Path=/; HttpOnly; Secure; SameSite=Strict`;

现代框架中的 XSS 新挑战

服务端渲染 (SSR) 的安全考虑

在 SSR 场景下,需要特别注意:

// Next.js 示例 - 安全地序列化数据
export async function getServerSideProps(context) {
  const userData = fetchUserData();
  return {
    props: {
      // 使用安全的序列化方法
      userData: JSON.parse(JSON.stringify(userData))
    }
  };
}

Web Components 的安全使用

使用 Shadow DOM 时仍需注意:

class SafeElement extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    // 仍然需要清理内容
    shadow.innerHTML = DOMPurify.sanitize(this.getAttribute('content'));
  }
}
customElements.define('safe-element', SafeElement);

第三方库的安全集成

集成富文本编辑器等第三方库时:

// 使用受信任的库并配置安全选项
import ClassicEditor from '@ckeditor/ckeditor5-build-classic';

ClassicEditor
  .create(document.querySelector('#editor'), {
    // 配置允许的 HTML 标签和属性
    htmlSupport: {
      allow: [
        {
          name: /.*/,
          attributes: true,
          classes: true,
          styles: true
        }
      ]
    }
  })
  .then(editor => {
    console.log('Editor was initialized', editor);
  })
  .catch(error => {
    console.error(error);
  });

常见 XSS 漏洞场景分析

JSON 注入风险

不安全的 JSON 处理方式:

// 不安全的做法
const userData = { name: "</script><script>alert('XSS')</script>" };
const jsonStr = JSON.stringify(userData);
document.write(`<script>var data = ${jsonStr}</script>`);

// 安全的做法
const jsonStr = JSON.stringify(userData)
  .replace(/</g, '\\u003c'); // 转义 < 字符

动态模板字符串的风险

// 不安全的模板字符串使用
const template = (input) => `<div>${input}</div>`;
document.body.innerHTML = template(userInput);

// 安全的替代方案
const template = (input) => {
  const div = document.createElement('div');
  div.textContent = input;
  return div;
};
document.body.appendChild(template(userInput));

URL 处理的安全问题

// 不安全的 URL 构造
const search = 'javascript:alert(1)';
location.href = search;

// 安全的 URL 验证
function safeRedirect(url) {
  const allowedProtocols = ['http:', 'https:', 'mailto:', 'tel:'];
  const parsed = new URL(url, window.location.href);
  
  if (!allowedProtocols.includes(parsed.protocol)) {
    throw new Error('不安全的跳转协议');
  }
  return parsed.toString();
}

location.href = safeRedirect(userInput);

安全开发工作流程

代码审查中的 XSS 检查点

  1. 查找所有直接操作 DOM 的地方
  2. 检查所有动态生成的 HTML
  3. 审查所有第三方库的使用方式
  4. 验证所有 URL 和资源加载

自动化安全测试

使用工具进行 XSS 检测:

// 使用 ESLint 插件检测潜在 XSS
module.exports = {
  rules: {
    'no-dangerous-html': {
      create(context) {
        return {
          JSXAttribute(node) {
            if (node.name.name === 'dangerouslySetInnerHTML') {
              context.report({
                node,
                message: '避免使用 dangerouslySetInnerHTML'
              });
            }
          }
        };
      }
    }
  }
};

安全响应头配置

示例的安全响应头设置:

# Nginx 配置示例
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' cdn.example.com; style-src 'self' 'unsafe-inline' fonts.googleapis.com; img-src 'self' data:; font-src 'self' fonts.gstatic.com";

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

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