security-expert

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security Expert

安全专家

Expert guidance for application security, vulnerability assessment, penetration testing, OWASP Top 10, secure coding practices, and security architecture.
为应用安全、漏洞评估、渗透测试、OWASP Top 10、安全编码实践及安全架构提供专业指导。

Core Concepts

核心概念

Security Principles

安全原则

  • Defense in depth
  • Least privilege
  • Secure by default
  • Fail securely
  • Complete mediation
  • Separation of duties
  • Zero trust architecture
  • 纵深防御
  • 最小权限
  • 默认安全
  • 安全失效
  • 完全中介
  • 职责分离
  • 零信任架构

OWASP Top 10 (2021)

OWASP Top 10(2021)

  1. Broken Access Control
  2. Cryptographic Failures
  3. Injection
  4. Insecure Design
  5. Security Misconfiguration
  6. Vulnerable and Outdated Components
  7. Identification and Authentication Failures
  8. Software and Data Integrity Failures
  9. Security Logging and Monitoring Failures
  10. Server-Side Request Forgery (SSRF)
  1. 访问控制失效
  2. 加密失败
  3. 注入攻击
  4. 不安全设计
  5. 安全配置错误
  6. 易受攻击且过时的组件
  7. 身份标识与认证失败
  8. 软件与数据完整性失败
  9. 安全日志与监控失败
  10. 服务器端请求伪造(SSRF)

Security Domains

安全领域

  • Authentication & Authorization
  • Cryptography
  • Input validation
  • Session management
  • Error handling
  • Secure communications
  • Data protection
  • 身份认证与授权
  • 密码学
  • 输入验证
  • 会话管理
  • 错误处理
  • 安全通信
  • 数据保护

OWASP Top 10 Vulnerabilities

OWASP Top 10 漏洞示例

1. Broken Access Control

1. 访问控制失效

javascript
// ❌ Vulnerable: No authorization check
app.get('/api/users/:id/profile', async (req, res) => {
  const profile = await db.users.findById(req.params.id);
  res.json(profile);
});

// ✅ Secure: Verify user owns the resource
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  if (req.user.id !== req.params.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const profile = await db.users.findById(req.params.id);
  res.json(profile);
});

// ✅ Better: Use middleware
const authorizeResource = (resourceType) => async (req, res, next) => {
  const resourceId = req.params.id;
  const resource = await db[resourceType].findById(resourceId);

  if (!resource) {
    return res.status(404).json({ error: 'Not found' });
  }

  if (resource.userId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  req.resource = resource;
  next();
};

app.delete('/api/posts/:id', authenticate, authorizeResource('posts'), async (req, res) => {
  await req.resource.delete();
  res.status(204).send();
});
javascript
// ❌ 存在漏洞:未进行权限校验
app.get('/api/users/:id/profile', async (req, res) => {
  const profile = await db.users.findById(req.params.id);
  res.json(profile);
});

// ✅ 安全实现:验证用户是否拥有资源权限
app.get('/api/users/:id/profile', authenticate, async (req, res) => {
  if (req.user.id !== req.params.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  const profile = await db.users.findById(req.params.id);
  res.json(profile);
});

// ✅ 更优方案:使用中间件
const authorizeResource = (resourceType) => async (req, res, next) => {
  const resourceId = req.params.id;
  const resource = await db[resourceType].findById(resourceId);

  if (!resource) {
    return res.status(404).json({ error: 'Not found' });
  }

  if (resource.userId !== req.user.id && !req.user.isAdmin) {
    return res.status(403).json({ error: 'Forbidden' });
  }

  req.resource = resource;
  next();
};

app.delete('/api/posts/:id', authenticate, authorizeResource('posts'), async (req, res) => {
  await req.resource.delete();
  res.status(204).send();
});

2. Injection (SQL, NoSQL, Command)

2. 注入攻击(SQL、NoSQL、命令注入)

javascript
// ❌ SQL Injection vulnerability
app.get('/users', (req, res) => {
  const query = `SELECT * FROM users WHERE name = '${req.query.name}'`;
  db.query(query, (err, results) => {
    res.json(results);
  });
});
// Attack: ?name=' OR '1'='1

// ✅ Secure: Use parameterized queries
app.get('/users', async (req, res) => {
  const results = await db.query(
    'SELECT * FROM users WHERE name = ?',
    [req.query.name]
  );
  res.json(results);
});

// ❌ Command Injection
const { exec } = require('child_process');
app.post('/convert', (req, res) => {
  exec(`convert ${req.body.filename} output.pdf`, (err, stdout) => {
    res.send(stdout);
  });
});
// Attack: filename="; rm -rf / #"

// ✅ Secure: Use safe APIs, validate input
const { spawn } = require('child_process');
app.post('/convert', (req, res) => {
  const filename = path.basename(req.body.filename); // Remove path traversal
  if (!/^[a-zA-Z0-9_-]+\.(jpg|png)$/.test(filename)) {
    return res.status(400).json({ error: 'Invalid filename' });
  }

  const process = spawn('convert', [filename, 'output.pdf']);
  // Handle process output safely
});

// ❌ NoSQL Injection (MongoDB)
app.post('/login', async (req, res) => {
  const user = await User.findOne({
    username: req.body.username,
    password: req.body.password
  });
});
// Attack: {"username": {"$ne": null}, "password": {"$ne": null}}

// ✅ Secure: Sanitize input, use proper types
app.post('/login', async (req, res) => {
  const { username, password } = req.body;

  if (typeof username !== 'string' || typeof password !== 'string') {
    return res.status(400).json({ error: 'Invalid input' });
  }

  const user = await User.findOne({ username });
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  // Create session
});
javascript
// ❌ SQL注入漏洞
app.get('/users', (req, res) => {
  const query = `SELECT * FROM users WHERE name = '${req.query.name}'`;
  db.query(query, (err, results) => {
    res.json(results);
  });
});
// 攻击方式:?name=' OR '1'='1

// ✅ 安全实现:使用参数化查询
app.get('/users', async (req, res) => {
  const results = await db.query(
    'SELECT * FROM users WHERE name = ?',
    [req.query.name]
  );
  res.json(results);
});

// ❌ 命令注入
const { exec } = require('child_process');
app.post('/convert', (req, res) => {
  exec(`convert ${req.body.filename} output.pdf`, (err, stdout) => {
    res.send(stdout);
  });
});
// 攻击方式:filename="; rm -rf / #"

// ✅ 安全实现:使用安全API,验证输入
const { spawn } = require('child_process');
app.post('/convert', (req, res) => {
  const filename = path.basename(req.body.filename); // 防止路径遍历
  if (!/^[a-zA-Z0-9_-]+\.(jpg|png)$/.test(filename)) {
    return res.status(400).json({ error: 'Invalid filename' });
  }

  const process = spawn('convert', [filename, 'output.pdf']);
  // 安全处理进程输出
});

// ❌ NoSQL注入(MongoDB)
app.post('/login', async (req, res) => {
  const user = await User.findOne({
    username: req.body.username,
    password: req.body.password
  });
});
// 攻击方式:{"username": {"$ne": null}, "password": {"$ne": null}}

// ✅ 安全实现:清理输入,使用正确类型
app.post('/login', async (req, res) => {
  const { username, password } = req.body;

  if (typeof username !== 'string' || typeof password !== 'string') {
    return res.status(400).json({ error: 'Invalid input' });
  }

  const user = await User.findOne({ username });
  if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  // 创建会话
});

3. Cross-Site Scripting (XSS)

3. 跨站脚本攻击(XSS)

javascript
// ❌ Reflected XSS
app.get('/search', (req, res) => {
  res.send(`<h1>Results for: ${req.query.q}</h1>`);
});
// Attack: ?q=<script>alert(document.cookie)</script>

// ✅ Secure: Escape output
const escapeHtml = (unsafe) => {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
};

app.get('/search', (req, res) => {
  res.send(`<h1>Results for: ${escapeHtml(req.query.q)}</h1>`);
});

// ✅ Better: Use templating engine with auto-escaping
app.get('/search', (req, res) => {
  res.render('search', { query: req.query.q }); // Automatically escaped
});

// ✅ Content Security Policy
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;"
  );
  next();
});
javascript
// ❌ 反射型XSS
app.get('/search', (req, res) => {
  res.send(`<h1>Results for: ${req.query.q}</h1>`);
});
// 攻击方式:?q=<script>alert(document.cookie)</script>

// ✅ 安全实现:转义输出
const escapeHtml = (unsafe) => {
  return unsafe
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
};

app.get('/search', (req, res) => {
  res.send(`<h1>搜索结果:${escapeHtml(req.query.q)}</h1>`);
});

// ✅ 更优方案:使用自带自动转义的模板引擎
app.get('/search', (req, res) => {
  res.render('search', { query: req.query.q }); // 自动转义
});

// ✅ 内容安全策略
app.use((req, res, next) => {
  res.setHeader(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:;"
  );
  next();
});

4. Cross-Site Request Forgery (CSRF)

4. 跨站请求伪造(CSRF)

javascript
// ❌ Vulnerable: No CSRF protection
app.post('/api/transfer', authenticate, async (req, res) => {
  await transferMoney(req.user.id, req.body.to, req.body.amount);
  res.json({ success: true });
});

// ✅ Secure: Use CSRF tokens
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.get('/transfer', csrfProtection, (req, res) => {
  res.render('transfer', { csrfToken: req.csrfToken() });
});

app.post('/api/transfer', csrfProtection, authenticate, async (req, res) => {
  await transferMoney(req.user.id, req.body.to, req.body.amount);
  res.json({ success: true });
});

// ✅ Also use SameSite cookies
app.use(session({
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
  }
}));
javascript
// ❌ 存在漏洞:无CSRF防护
app.post('/api/transfer', authenticate, async (req, res) => {
  await transferMoney(req.user.id, req.body.to, req.body.amount);
  res.json({ success: true });
});

// ✅ 安全实现:使用CSRF令牌
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });

app.get('/transfer', csrfProtection, (req, res) => {
  res.render('transfer', { csrfToken: req.csrfToken() });
});

app.post('/api/transfer', csrfProtection, authenticate, async (req, res) => {
  await transferMoney(req.user.id, req.body.to, req.body.amount);
  res.json({ success: true });
});

// ✅ 同时使用SameSite Cookie
app.use(session({
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
  }
}));

5. Security Misconfiguration

5. 安全配置错误

javascript
// ❌ Exposed sensitive information
app.use((err, req, res, next) => {
  res.status(500).json({
    error: err.message,
    stack: err.stack  // Exposes internal details
  });
});

// ✅ Secure: Generic error messages
app.use((err, req, res, next) => {
  console.error(err); // Log internally

  if (process.env.NODE_ENV === 'production') {
    res.status(500).json({ error: 'Internal server error' });
  } else {
    res.status(500).json({ error: err.message, stack: err.stack });
  }
});

// ✅ Security headers
const helmet = require('helmet');
app.use(helmet());

// ✅ Disable unnecessary features
app.disable('x-powered-by');

// ✅ Rate limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', limiter);
javascript
// ❌ 暴露敏感信息
app.use((err, req, res, next) => {
  res.status(500).json({
    error: err.message,
    stack: err.stack  // 暴露内部细节
  });
});

// ✅ 安全实现:通用错误信息
app.use((err, req, res, next) => {
  console.error(err); // 内部日志记录

  if (process.env.NODE_ENV === 'production') {
    res.status(500).json({ error: '内部服务器错误' });
  } else {
    res.status(500).json({ error: err.message, stack: err.stack });
  }
});

// ✅ 安全头部
const helmet = require('helmet');
app.use(helmet());

// ✅ 禁用不必要功能
app.disable('x-powered-by');

// ✅ 速率限制
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100 // 限制每个IP在窗口时间内最多100次请求
});
app.use('/api/', limiter);

Authentication & Authorization

身份认证与授权

Password Security

密码安全

javascript
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;

// Hash password
async function hashPassword(password) {
  // Validate password strength
  if (password.length < 12) {
    throw new Error('Password must be at least 12 characters');
  }

  if (!/[A-Z]/.test(password) || !/[a-z]/.test(password) ||
      !/[0-9]/.test(password) || !/[^A-Za-z0-9]/.test(password)) {
    throw new Error('Password must contain uppercase, lowercase, number, and special character');
  }

  return await bcrypt.hash(password, SALT_ROUNDS);
}

// Verify password
async function verifyPassword(password, hash) {
  return await bcrypt.compare(password, hash);
}

// Registration
app.post('/register', async (req, res) => {
  const { email, password } = req.body;

  // Check if user exists
  const existingUser = await User.findOne({ email });
  if (existingUser) {
    return res.status(400).json({ error: 'Email already registered' });
  }

  // Hash password
  const passwordHash = await hashPassword(password);

  // Create user
  const user = await User.create({
    email: email.toLowerCase(),
    passwordHash
  });

  res.status(201).json({ id: user.id });
});
javascript
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;

// 哈希密码
async function hashPassword(password) {
  // 验证密码强度
  if (password.length < 12) {
    throw new Error('密码长度至少为12位');
  }

  if (!/[A-Z]/.test(password) || !/[a-z]/.test(password) ||
      !/[0-9]/.test(password) || !/[^A-Za-z0-9]/.test(password)) {
    throw new Error('密码必须包含大写字母、小写字母、数字和特殊字符');
  }

  return await bcrypt.hash(password, SALT_ROUNDS);
}

// 验证密码
async function verifyPassword(password, hash) {
  return await bcrypt.compare(password, hash);
}

// 用户注册
app.post('/register', async (req, res) => {
  const { email, password } = req.body;

  // 检查用户是否已存在
  const existingUser = await User.findOne({ email });
  if (existingUser) {
    return res.status(400).json({ error: '该邮箱已注册' });
  }

  // 哈希密码
  const passwordHash = await hashPassword(password);

  // 创建用户
  const user = await User.create({
    email: email.toLowerCase(),
    passwordHash
  });

  res.status(201).json({ id: user.id });
});

JWT Authentication

JWT身份认证

javascript
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET; // Use strong secret
const JWT_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';

// Generate tokens
function generateTokens(user) {
  const accessToken = jwt.sign(
    { userId: user.id, email: user.email },
    JWT_SECRET,
    { expiresIn: JWT_EXPIRY }
  );

  const refreshToken = jwt.sign(
    { userId: user.id, type: 'refresh' },
    JWT_SECRET,
    { expiresIn: REFRESH_TOKEN_EXPIRY }
  );

  return { accessToken, refreshToken };
}

// Verify token middleware
function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = authHeader.substring(7);

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Login
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email: email.toLowerCase() });
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    // Generic error to prevent username enumeration
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  // Update last login
  await user.update({ lastLoginAt: new Date() });

  const tokens = generateTokens(user);
  res.json(tokens);
});

// Refresh token
app.post('/refresh', async (req, res) => {
  const { refreshToken } = req.body;

  try {
    const decoded = jwt.verify(refreshToken, JWT_SECRET);

    if (decoded.type !== 'refresh') {
      return res.status(401).json({ error: 'Invalid token type' });
    }

    const user = await User.findById(decoded.userId);
    if (!user) {
      return res.status(401).json({ error: 'User not found' });
    }

    const tokens = generateTokens(user);
    res.json(tokens);
  } catch (err) {
    return res.status(401).json({ error: 'Invalid refresh token' });
  }
});
javascript
const jwt = require('jsonwebtoken');
const JWT_SECRET = process.env.JWT_SECRET; // 使用强密钥
const JWT_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';

// 生成令牌
function generateTokens(user) {
  const accessToken = jwt.sign(
    { userId: user.id, email: user.email },
    JWT_SECRET,
    { expiresIn: JWT_EXPIRY }
  );

  const refreshToken = jwt.sign(
    { userId: user.id, type: 'refresh' },
    JWT_SECRET,
    { expiresIn: REFRESH_TOKEN_EXPIRY }
  );

  return { accessToken, refreshToken };
}

// 验证令牌中间件
function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: '未提供令牌' });
  }

  const token = authHeader.substring(7);

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: '令牌已过期' });
    }
    return res.status(401).json({ error: '无效令牌' });
  }
}

// 用户登录
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email: email.toLowerCase() });
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    // 通用错误信息,防止用户名枚举
    return res.status(401).json({ error: '凭据无效' });
  }

  // 更新最后登录时间
  await user.update({ lastLoginAt: new Date() });

  const tokens = generateTokens(user);
  res.json(tokens);
});

// 刷新令牌
app.post('/refresh', async (req, res) => {
  const { refreshToken } = req.body;

  try {
    const decoded = jwt.verify(refreshToken, JWT_SECRET);

    if (decoded.type !== 'refresh') {
      return res.status(401).json({ error: '无效令牌类型' });
    }

    const user = await User.findById(decoded.userId);
    if (!user) {
      return res.status(401).json({ error: '用户不存在' });
    }

    const tokens = generateTokens(user);
    res.json(tokens);
  } catch (err) {
    return res.status(401).json({ error: '无效刷新令牌' });
  }
});

Multi-Factor Authentication (MFA)

多因素认证(MFA)

javascript
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// Generate MFA secret
app.post('/mfa/setup', authenticate, async (req, res) => {
  const secret = speakeasy.generateSecret({
    name: `MyApp (${req.user.email})`
  });

  // Store secret temporarily
  await redis.setex(`mfa:setup:${req.user.id}`, 300, secret.base32);

  // Generate QR code
  const qrCode = await QRCode.toDataURL(secret.otpauth_url);

  res.json({
    secret: secret.base32,
    qrCode
  });
});

// Verify and enable MFA
app.post('/mfa/verify', authenticate, async (req, res) => {
  const { token } = req.body;
  const secret = await redis.get(`mfa:setup:${req.user.id}`);

  if (!secret) {
    return res.status(400).json({ error: 'MFA setup expired' });
  }

  const verified = speakeasy.totp.verify({
    secret,
    encoding: 'base32',
    token,
    window: 2
  });

  if (!verified) {
    return res.status(400).json({ error: 'Invalid token' });
  }

  // Enable MFA for user
  await User.update(req.user.id, {
    mfaEnabled: true,
    mfaSecret: secret
  });

  await redis.del(`mfa:setup:${req.user.id}`);

  res.json({ success: true });
});

// Login with MFA
app.post('/login/mfa', async (req, res) => {
  const { email, password, mfaToken } = req.body;

  const user = await User.findOne({ email });
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  if (user.mfaEnabled) {
    if (!mfaToken) {
      return res.status(401).json({ error: 'MFA token required' });
    }

    const verified = speakeasy.totp.verify({
      secret: user.mfaSecret,
      encoding: 'base32',
      token: mfaToken,
      window: 2
    });

    if (!verified) {
      return res.status(401).json({ error: 'Invalid MFA token' });
    }
  }

  const tokens = generateTokens(user);
  res.json(tokens);
});
javascript
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// 生成MFA密钥
app.post('/mfa/setup', authenticate, async (req, res) => {
  const secret = speakeasy.generateSecret({
    name: `MyApp (${req.user.email})`
  });

  // 临时存储密钥
  await redis.setex(`mfa:setup:${req.user.id}`, 300, secret.base32);

  // 生成二维码
  const qrCode = await QRCode.toDataURL(secret.otpauth_url);

  res.json({
    secret: secret.base32,
    qrCode
  });
});

// 验证并启用MFA
app.post('/mfa/verify', authenticate, async (req, res) => {
  const { token } = req.body;
  const secret = await redis.get(`mfa:setup:${req.user.id}`);

  if (!secret) {
    return res.status(400).json({ error: 'MFA设置已过期' });
  }

  const verified = speakeasy.totp.verify({
    secret,
    encoding: 'base32',
    token,
    window: 2
  });

  if (!verified) {
    return res.status(400).json({ error: '无效令牌' });
  }

  // 为用户启用MFA
  await User.update(req.user.id, {
    mfaEnabled: true,
    mfaSecret: secret
  });

  await redis.del(`mfa:setup:${req.user.id}`);

  res.json({ success: true });
});

// 带MFA的登录
app.post('/login/mfa', async (req, res) => {
  const { email, password, mfaToken } = req.body;

  const user = await User.findOne({ email });
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    return res.status(401).json({ error: '凭据无效' });
  }

  if (user.mfaEnabled) {
    if (!mfaToken) {
      return res.status(401).json({ error: '需要MFA令牌' });
    }

    const verified = speakeasy.totp.verify({
      secret: user.mfaSecret,
      encoding: 'base32',
      token: mfaToken,
      window: 2
    });

    if (!verified) {
      return res.status(401).json({ error: '无效MFA令牌' });
    }
  }

  const tokens = generateTokens(user);
  res.json(tokens);
});

Cryptography

密码学

Encryption at Rest

静态数据加密

javascript
const crypto = require('crypto');

const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); // 32 bytes

function encrypt(plaintext) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);

  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  const authTag = cipher.getAuthTag();

  return {
    iv: iv.toString('hex'),
    encrypted,
    authTag: authTag.toString('hex')
  };
}

function decrypt(iv, encrypted, authTag) {
  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    KEY,
    Buffer.from(iv, 'hex')
  );

  decipher.setAuthTag(Buffer.from(authTag, 'hex'));

  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  return decrypted;
}

// Store sensitive data
async function storeSensitiveData(userId, data) {
  const { iv, encrypted, authTag } = encrypt(JSON.stringify(data));

  await db.sensitiveData.create({
    userId,
    iv,
    encrypted,
    authTag
  });
}
javascript
const crypto = require('crypto');

const ALGORITHM = 'aes-256-gcm';
const KEY = Buffer.from(process.env.ENCRYPTION_KEY, 'hex'); // 32字节

function encrypt(plaintext) {
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(ALGORITHM, KEY, iv);

  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');

  const authTag = cipher.getAuthTag();

  return {
    iv: iv.toString('hex'),
    encrypted,
    authTag: authTag.toString('hex')
  };
}

function decrypt(iv, encrypted, authTag) {
  const decipher = crypto.createDecipheriv(
    ALGORITHM,
    KEY,
    Buffer.from(iv, 'hex')
  );

  decipher.setAuthTag(Buffer.from(authTag, 'hex'));

  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');

  return decrypted;
}

// 存储敏感数据
async function storeSensitiveData(userId, data) {
  const { iv, encrypted, authTag } = encrypt(JSON.stringify(data));

  await db.sensitiveData.create({
    userId,
    iv,
    encrypted,
    authTag
  });
}

Secure Random Generation

安全随机数生成

javascript
// ✅ Cryptographically secure random
const crypto = require('crypto');

function generateSecureToken(length = 32) {
  return crypto.randomBytes(length).toString('hex');
}

function generateSecureId() {
  return crypto.randomUUID();
}

// ❌ Don't use Math.random() for security
const insecureToken = Math.random().toString(36); // Predictable!
javascript
// ✅ 符合密码学安全的随机数
const crypto = require('crypto');

function generateSecureToken(length = 32) {
  return crypto.randomBytes(length).toString('hex');
}

function generateSecureId() {
  return crypto.randomUUID();
}

// ❌ 不要使用Math.random()生成安全相关随机数
const insecureToken = Math.random().toString(36); // 可预测!

Input Validation

输入验证

javascript
const { body, validationResult } = require('express-validator');

app.post('/api/users',
  // Validation rules
  body('email').isEmail().normalizeEmail(),
  body('name').trim().isLength({ min: 2, max: 100 }).escape(),
  body('age').optional().isInt({ min: 18, max: 120 }),
  body('website').optional().isURL(),

  async (req, res) => {
    // Check validation results
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    // Process validated data
    const user = await User.create(req.body);
    res.status(201).json(user);
  }
);

// File upload validation
const multer = require('multer');

const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024, // 5MB max
  },
  fileFilter: (req, file, cb) => {
    // Check file type
    const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedMimes.includes(file.mimetype)) {
      return cb(new Error('Invalid file type'));
    }

    // Check file extension
    const ext = path.extname(file.originalname).toLowerCase();
    if (!['.jpg', '.jpeg', '.png', '.gif'].includes(ext)) {
      return cb(new Error('Invalid file extension'));
    }

    cb(null, true);
  }
});

app.post('/upload', upload.single('image'), (req, res) => {
  // Verify file content matches extension
  // Store with random filename to prevent path traversal
  const filename = `${crypto.randomUUID()}${path.extname(req.file.originalname)}`;
  // Save file...
});
javascript
const { body, validationResult } = require('express-validator');

app.post('/api/users',
  // 验证规则
  body('email').isEmail().normalizeEmail(),
  body('name').trim().isLength({ min: 2, max: 100 }).escape(),
  body('age').optional().isInt({ min: 18, max: 120 }),
  body('website').optional().isURL(),

  async (req, res) => {
    // 检查验证结果
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    // 处理验证后的数据
    const user = await User.create(req.body);
    res.status(201).json(user);
  }
);

// 文件上传验证
const multer = require('multer');

const upload = multer({
  limits: {
    fileSize: 5 * 1024 * 1024, // 最大5MB
  },
  fileFilter: (req, file, cb) => {
    // 检查文件类型
    const allowedMimes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedMimes.includes(file.mimetype)) {
      return cb(new Error('无效文件类型'));
    }

    // 检查文件扩展名
    const ext = path.extname(file.originalname).toLowerCase();
    if (!['.jpg', '.jpeg', '.png', '.gif'].includes(ext)) {
      return cb(new Error('无效文件扩展名'));
    }

    cb(null, true);
  }
});

app.post('/upload', upload.single('image'), (req, res) => {
  // 验证文件内容与扩展名是否匹配
  // 使用随机文件名存储,防止路径遍历
  const filename = `${crypto.randomUUID()}${path.extname(req.file.originalname)}`;
  // 保存文件...
});

Security Headers

安全头部

javascript
const helmet = require('helmet');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"], // Avoid unsafe-inline in production
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },
  frameguard: {
    action: 'deny'
  },
  noSniff: true,
  xssFilter: true,
}));

// Additional headers
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
  next();
});
javascript
const helmet = require('helmet');

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"], // 生产环境避免使用unsafe-inline
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'"],
      fontSrc: ["'self'"],
      objectSrc: ["'none'"],
      mediaSrc: ["'self'"],
      frameSrc: ["'none'"],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },
  frameguard: {
    action: 'deny'
  },
  noSniff: true,
  xssFilter: true,
}));

// 额外头部
app.use((req, res, next) => {
  res.setHeader('X-Content-Type-Options', 'nosniff');
  res.setHeader('X-Frame-Options', 'DENY');
  res.setHeader('X-XSS-Protection', '1; mode=block');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
  next();
});

Secure Logging

安全日志

javascript
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// Log security events
function logSecurityEvent(event, details) {
  logger.warn('Security Event', {
    event,
    ...details,
    timestamp: new Date().toISOString()
  });
}

// Failed login attempts
app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });

  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    logSecurityEvent('failed_login', {
      email,
      ip: req.ip,
      userAgent: req.headers['user-agent']
    });

    return res.status(401).json({ error: 'Invalid credentials' });
  }

  // Success
  logSecurityEvent('successful_login', {
    userId: user.id,
    ip: req.ip
  });

  // Generate tokens...
});

// ❌ Don't log sensitive data
logger.info('User data', user); // May contain passwordHash, tokens

// ✅ Sanitize before logging
logger.info('User data', {
  id: user.id,
  email: user.email,
  // Omit sensitive fields
});
javascript
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' })
  ]
});

// 记录安全事件
function logSecurityEvent(event, details) {
  logger.warn('Security Event', {
    event,
    ...details,
    timestamp: new Date().toISOString()
  });
}

// 登录失败尝试
app.post('/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await User.findOne({ email });

  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    logSecurityEvent('failed_login', {
      email,
      ip: req.ip,
      userAgent: req.headers['user-agent']
    });

    return res.status(401).json({ error: '凭据无效' });
  }

  // 登录成功
  logSecurityEvent('successful_login', {
    userId: user.id,
    ip: req.ip
  });

  // 生成令牌...
});

// ❌ 不要记录敏感数据
logger.info('User data', user); // 可能包含密码哈希、令牌等

// ✅ 记录前清理敏感信息
logger.info('User data', {
  id: user.id,
  email: user.email,
  // 省略敏感字段
});

Best Practices

最佳实践

Secure Development Lifecycle

安全开发生命周期

  1. Threat modeling
  2. Security requirements
  3. Secure coding standards
  4. Code review
  5. Security testing (SAST/DAST)
  6. Vulnerability scanning
  7. Penetration testing
  8. Security monitoring
  1. 威胁建模
  2. 安全需求定义
  3. 安全编码标准
  4. 代码评审
  5. 安全测试(SAST/DAST)
  6. 漏洞扫描
  7. 渗透测试
  8. 安全监控

Defense in Depth

纵深防御

  • Multiple layers of security
  • Assume breach mentality
  • Principle of least privilege
  • Input validation at every layer
  • Output encoding
  • Secure configuration
  • 多层安全防护
  • 假设已被攻破的思维
  • 最小权限原则
  • 每一层都进行输入验证
  • 输出编码
  • 安全配置

Security Testing

安全测试

  • Static Application Security Testing (SAST)
  • Dynamic Application Security Testing (DAST)
  • Interactive Application Security Testing (IAST)
  • Software Composition Analysis (SCA)
  • Penetration testing
  • Bug bounty programs
  • 静态应用安全测试(SAST)
  • 动态应用安全测试(DAST)
  • 交互式应用安全测试(IAST)
  • 软件成分分析(SCA)
  • 渗透测试
  • 漏洞赏金计划

Anti-Patterns to Avoid

需要避免的反模式

Storing passwords in plaintext: Always hash with bcrypt/argon2 ❌ Rolling your own crypto: Use established libraries ❌ Trusting user input: Validate and sanitize everything ❌ Exposing sensitive errors: Use generic error messages ❌ No rate limiting: Implement rate limiting on all endpoints ❌ Weak session management: Use secure, httpOnly cookies ❌ No logging: Log security events for monitoring ❌ Hardcoded secrets: Use environment variables
明文存储密码:始终使用bcrypt/argon2进行哈希 ❌ 自行实现密码学:使用成熟的库 ❌ 信任用户输入:对所有输入进行验证和清理 ❌ 暴露敏感错误信息:使用通用错误信息 ❌ 未实现速率限制:为所有端点添加速率限制 ❌ 弱会话管理:使用安全的HttpOnly Cookie ❌ 无日志记录:记录安全事件以便监控 ❌ 硬编码密钥:使用环境变量

Resources

参考资源