前端安全防护完全指南:从原理到最佳实践

2018-06-09

前端安全防护完全指南

本文深入探讨前端开发中的常见安全问题,并提供全面的防护策略和最佳实践,帮助开发者构建更安全的 Web 应用。

安全威胁概述

graph TD A[前端安全威胁] --> B[XSS攻击] A --> C[CSRF攻击] A --> D[点击劫持] B --> E[反射型XSS] B --> F[存储型XSS] B --> G[DOM型XSS] C --> H[GET类型CSRF] C --> I[POST类型CSRF] D --> J[UI覆盖] D --> K[透明iframe]

1. 跨站脚本攻击(XSS)

1.1 XSS 类型及原理

反射型 XSS

  • 非持久性攻击
  • 通过 URL 参数注入恶意代码
  • 服务端未经处理直接返回用户输入
sequenceDiagram participant U as 用户 participant A as 攻击者 participant B as 浏览器 participant S as 服务器 A->>U: 1. 发送恶意链接 U->>B: 2. 点击链接 B->>S: 3. 发送请求(带有恶意代码) S->>B: 4. 响应(包含恶意代码) B->>B: 5. 执行恶意代码 B->>A: 6. 发送用户数据
// 危险的URL示例
https://example.com/search?q=<script>alert('XSS')</script>
 
// 服务端不安全的处理
app.get('/search', (req, res) => {
    res.send(`搜索结果: ${req.query.q}`); // 直接输出用户输入
});

存储型 XSS

  • 持久性攻击
  • 恶意代码存储在数据库中
  • 其他用户访问页面时触发攻击
sequenceDiagram participant A as 攻击者 participant B1 as 攻击者浏览器 participant S as 服务器 participant DB as 数据库 participant B2 as 用户浏览器 participant U as 用户 A->>B1: 1. 输入恶意代码 B1->>S: 2. 提交数据 S->>DB: 3. 存储恶意代码 U->>B2: 4. 访问页面 B2->>S: 5. 请求数据 S->>DB: 6. 读取数据 DB->>S: 7. 返回含恶意代码的数据 S->>B2: 8. 响应 B2->>B2: 9. 执行恶意代码 B2->>A: 10. 发送用户数据
// 危险的评论输入
const comment = '<script>steal(document.cookie)</script>';
 
// 数据库中存储的恶意脚本
{
    "comment": "<script>steal(document.cookie)</script>",
    "user": "attacker"
}

DOM 型 XSS

  • 前端 JavaScript 直接使用不可信数据
  • 修改 DOM 结构触发攻击
  • 不经过服务端
sequenceDiagram participant A as 攻击者 participant U as 用户 participant B as 浏览器 participant D as DOM A->>U: 1. 提供恶意URL U->>B: 2. 访问URL B->>D: 3. JavaScript处理URL参数 D->>D: 4. 修改DOM D->>B: 5. 执行恶意代码 B->>A: 6. 发送用户数据
// 危险的 DOM 操作
document.getElementById('content').innerHTML = location.hash.substring(1);

1.2 XSS 防护措施

输入验证和净化

// 使用 DOMPurify 净化用户输入
import DOMPurify from 'dompurify';
 
const clean = DOMPurify.sanitize(userInput, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong'],
    ALLOWED_ATTR: ['title']
});

输出编码

// HTML 转义函数
function escapeHtml(unsafe) {
    return unsafe
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#039;");
}
 
// React 自动转义
const UserInput = ({ data }) => <div>{data}</div>;

CSP 配置

<!-- 内容安全策略配置 -->
<meta http-equiv="Content-Security-Policy" 
    content="default-src 'self'; 
    script-src 'self' 'unsafe-inline' 'unsafe-eval'; 
    style-src 'self' 'unsafe-inline';">

2. 跨站请求伪造(CSRF)

2.1 攻击原理

  • 利用用户已登录的身份
  • 在第三方网站发起请求
  • 自动携带目标站点的 Cookie
sequenceDiagram participant U as 用户 participant B as 浏览器 participant T as 目标网站 participant A as 攻击者网站 U->>B: 1. 登录目标网站 B->>T: 2. 身份验证 T->>B: 3. 设置Cookie U->>B: 4. 访问攻击者网站 B->>A: 5. 加载攻击页面 A->>B: 6. 返回恶意表单/请求 B->>T: 7. 自动发送请求(带Cookie) T->>B: 8. 执行操作
// 攻击者的恶意网站代码
<form action="https://bank.example.com/transfer" method="POST" id="stealMoney">
    <input type="hidden" name="amount" value="10000">
    <input type="hidden" name="to" value="attacker">
</form>
<script>document.getElementById('stealMoney').submit();</script>

2.2 防护措施

Token 验证

// 服务端生成 token
const csrfToken = crypto.randomBytes(16).toString('hex');
 
// 前端请求携带 token
const headers = {
    'X-CSRF-Token': csrfToken,
    'Content-Type': 'application/json',
};
 
fetch('/api/data', {
    method: 'POST',
    headers,
    credentials: 'include',
    body: JSON.stringify(data)
});

SameSite Cookie

// 设置 Cookie
Set-Cookie: sessionId=abc123; SameSite=Strict; Secure; HttpOnly

3. 点击劫持

3.1 攻击原理

sequenceDiagram participant U as 用户 participant B as 浏览器 participant A as 攻击者网站 participant T as 目标网站 U->>B: 1. 访问攻击者网站 B->>A: 2. 请求页面 A->>B: 3. 返回带透明iframe的页面 B->>T: 4. 加载目标网站(iframe) T->>B: 5. 返回目标页面 B->>B: 6. 显示诱饰界面 U->>B: 7. 点击诱饰按钮 B->>T: 8. 触发目标操作
<!-- 攻击者的页面 -->
<style>
    iframe {
        width: 500px;
        height: 500px;
        position: absolute;
        top: -100px;
        left: -100px;
        opacity: 0.0001;
        z-index: 2;
    }
    .fake-button {
        position: absolute;
        top: 300px;
        left: 300px;
        z-index: 1;
    }
</style>
<iframe src="https://victim-site.com/"></iframe>
<button class="fake-button">点击领取奖励</button>

3.2 防护措施

sequenceDiagram participant A as 攻击者网站 participant B as 浏览器 participant T as 受保护网站 A->>B: 1. 尝试嵌入iframe B->>T: 2. 请求页面 T->>B: 3. 返回X-Frame-Options Note over B: 4. 检查X-Frame-Options B->>B: 5. 阻止iframe加载 Note over B: 6. 攻击失败

X-Frame-Options

// 服务端设置
response.setHeader('X-Frame-Options', 'SAMEORIGIN');

CSP frame-ancestors

// 服务端设置
response.setHeader(
    'Content-Security-Policy',
    "frame-ancestors 'self' https://trusted-site.com"
);

JavaScript 防护

// 判断当前网页是否被嵌入iframe
if (window.self !== window.top) {
    window.top.location = window.self.location;
}

4. 其他安全措施

// 设置安全的 Cookie
app.use(session({
    cookie: {
        secure: true,         // 仅通过 HTTPS 传输
        httpOnly: true,       // 禁止 JavaScript 访问
        sameSite: 'strict',   // 严格的同源策略
        maxAge: 3600000      // 有效期限
    }
}));

4.2 HTTPS 配置

// Node.js HTTPS 服务器配置
const https = require('https');
const fs = require('fs');
 
const options = {
    key: fs.readFileSync('private-key.pem'),
    cert: fs.readFileSync('certificate.pem'),
    ciphers: [
        "ECDHE-RSA-AES128-GCM-SHA256",
        "ECDHE-ECDSA-AES128-GCM-SHA256"
    ].join(':')
};
 
https.createServer(options, app).listen(443);

安全检查清单

  1. XSS 防护

    • 实施内容安全策略 (CSP)
    • 对用户输入进行验证和净化
    • 使用安全的模板引擎
    • 对输出进行适当转义
  2. CSRF 防护

    • 实施 CSRF Token
    • 配置 SameSite Cookie
    • 验证请求来源
    • 使用安全的 HTTP 方法
  3. 点击劫持防护

    • 配置 X-Frame-Options
    • 实施 frame-ancestors CSP
    • 添加 JavaScript 防护层
  4. 通用安全措施

    • 启用 HTTPS
    • 配置安全的 Cookie
    • 实施适当的访问控制
    • 定期进行安全审计

参考资源

作者注

本文章首次发布于 2018 年 06 月 09 日,如有更新会在文末标注。如果您发现任何错误或有任何建议,欢迎在评论区留言或通过邮件联系我。

最后更新:2024 年 12 月 31 日

本文章遵循 CC BY-NC-SA 4.0 协议