{T}

高安全架构 学习笔记(第 13 部分)

6.3 XSS 防护

javascript
// XSS 防护
class XSSProtection {
  constructor() {
    // XSS 攻击模式
    this.patterns = [
      /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
      /javascript:/gi,
      /on\w+\s*=/gi,  // onclick=, onload= 等
      /<iframe/gi,
      /<object/gi,
      /<embed/gi,
      /expression\(.*?\)/gi
    ]
  }
  
  // 检测 XSS
  detect(input) {
    for (const pattern of this.patterns) {
      if (pattern.test(input)) {
        return {
          safe: false,
          pattern: pattern.toString(),
          matched: input.match(pattern)
        }
      }
    }
    
    return { safe: true }
  }
  
  // 转义 HTML
  escapeHtml(input) {
    const htmlEntities = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#x27;',
      '/': '&#x2F;'
    }
    
    return String(input).replace(/[&<>"'\/]/g, char => htmlEntities[char])
  }
  
  // 移除危险标签
  stripTags(input) {
    return input
      .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
      .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
      .replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
      .replace(/<embed\b[^>]*>/gi, '')
      .replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')  // 移除事件处理属性
  }
  
  // CSP (内容安全策略) 设置
  setCSP(res) {
    res.setHeader('Content-Security-Policy', [
      "default-src 'self'",
      "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.example.com",
      "style-src 'self' 'unsafe-inline' https://cdn.example.com",
      "img-src 'self' data: https:",
      "font-src 'self' https://cdn.example.com",
      "connect-src 'self' https://api.example.com",
      "frame-ancestors 'none'",
      "base-uri 'self'",
      "form-action 'self'"
    ].join('; '))
  }
  
  // XSS 防护中间件
  middleware() {
    return (req, res, next) => {
      // 设置安全头
      this.setCSP(res)
      res.setHeader('X-XSS-Protection', '1; mode=block')
      res.setHeader('X-Content-Type-Options', 'nosniff')
      
      // 检查和清理输入
      const sanitizeInput = (obj) => {
        for (const key in obj) {
          if (typeof obj[key] === 'string') {
            const result = this.detect(obj[key])
            
            if (!result.safe) {
              console.warn(`XSS 检测: 字段 ${key}, 模式: ${result.pattern}`)
            }
            
            // 清理输入
            obj[key] = this.escapeHtml(obj[key])
          }
        }
      }
      
      sanitizeInput(req.body)
      sanitizeInput(req.query)
      
      next()
    }
  }
}

// 使用示例
const xssProtection = new XSSProtection()

app.use(xssProtection.middleware())

// 在输出时再次转义
app.get('/user/:id', (req, res) => {
  const user = getUser(req.params.id)
  
  // 转义输出
  const safeUser = {
    name: xssProtection.escapeHtml(user.name),
    bio: xssProtection.escapeHtml(user.bio)
  }
  
  res.json(safeUser)
})