正在加载,请稍候…

Web Security: XSS, CSRF, SQL Injection Prevention and Security Headers

Comprehensive web security guide covering XSS prevention, CSRF protection, SQL injection defense, Content Security Policy, and security headers for modern web apps.

Web Security: Complete Prevention Guide

XSS Prevention

// React - already HTML-escapes by default
// Dangerous: dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userInput }} /> // NEVER do this with untrusted input

// Safe: Sanitize before using dangerouslySetInnerHTML
import DOMPurify from 'dompurify'

function SafeHTML({ content }) {
  const clean = DOMPurify.sanitize(content, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br'],
    ALLOWED_ATTR: [],
  })
  return <div dangerouslySetInnerHTML={{ __html: clean }} />
}

// Server-side: escape all user content
import escapeHtml from 'escape-html'

app.get('/profile', (req, res) => {
  const username = escapeHtml(req.query.username)
  res.send(`<h1>Welcome, ${username}!</h1>`)
})

// CSP header to block inline scripts
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy',
    "default-src 'self'; " +
    "script-src 'self' 'nonce-{RANDOM}'; " +
    "style-src 'self' 'unsafe-inline'; " +
    "img-src 'self' data: https:; " +
    "connect-src 'self' https://api.example.com"
  )
  next()
})

CSRF Protection

import csrf from 'csurf'
import cookieParser from 'cookie-parser'

app.use(cookieParser())
app.use(csrf({ cookie: { httpOnly: true, secure: true, sameSite: 'strict' } }))

// Include CSRF token in forms
app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() })
})

// Frontend: include token in AJAX requests
const token = document.querySelector('meta[name="csrf-token"]').content

fetch('/api/action', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-CSRF-Token': token,
  },
  body: JSON.stringify({ data: 'value' }),
})

// Double Submit Cookie Pattern for SPAs
function getCSRFToken() {
  return document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1]
}

// SameSite cookies provide additional protection
res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict', // Prevents cross-site requests
  maxAge: 3600000,
})

SQL Injection Prevention

// NEVER: string concatenation
const query = `SELECT * FROM users WHERE id = ${userId}` // VULNERABLE

// ALWAYS: parameterized queries
import { Pool } from 'pg'
const pool = new Pool()

async function getUser(userId) {
  const result = await pool.query(
    'SELECT id, name, email FROM users WHERE id = $1',
    [userId]  // Parameterized - safe
  )
  return result.rows[0]
}

// With Prisma ORM - safe by default
const user = await prisma.user.findUnique({ where: { id: userId } })

// Input validation with Zod
import { z } from 'zod'

const UserIdSchema = z.string().uuid()
const SearchSchema = z.object({
  query: z.string().max(100).regex(/^[a-zA-Z0-9 ]+$/),
  page: z.number().int().positive().max(1000),
})

app.get('/search', async (req, res) => {
  const { query, page } = SearchSchema.parse(req.query)
  const results = await searchProducts(query, page)
  res.json(results)
})

Security Headers with Helmet.js

import helmet from 'helmet'

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'nonce-abc123'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", "data:", "https:"],
      upgradeInsecureRequests: [],
    },
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true,
  },
  frameguard: { action: 'deny' },
  noSniff: true,
  referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}))

// Rate limiting
import rateLimit from 'express-rate-limit'

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: 'Too many requests, please try again later.' },
})

app.use('/api/', limiter)

const loginLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5,
  skipSuccessfulRequests: true,
})
app.use('/api/auth/login', loginLimiter)

Input Validation and Sanitization

import { z } from 'zod'
import validator from 'validator'

const RegisterSchema = z.object({
  email: z.string().email().toLowerCase().trim(),
  password: z.string()
    .min(12, 'Password must be at least 12 characters')
    .regex(/[A-Z]/, 'Must contain uppercase')
    .regex(/[0-9]/, 'Must contain number')
    .regex(/[^A-Za-z0-9]/, 'Must contain special character'),
  username: z.string()
    .min(3).max(30)
    .regex(/^[a-zA-Z0-9_-]+$/, 'Only letters, numbers, hyphens, underscores'),
})

// File upload validation
const FileSchema = z.object({
  mimetype: z.enum(['image/jpeg', 'image/png', 'image/webp']),
  size: z.number().max(5 * 1024 * 1024), // 5MB max
})

function validateFile(file) {
  // Check actual content, not just MIME type
  const magic = file.buffer.slice(0, 4)
  const isPNG = magic.equals(Buffer.from([0x89, 0x50, 0x4e, 0x47]))
  const isJPEG = magic[0] === 0xff && magic[1] === 0xd8
  return isPNG || isJPEG
}

Security Checklist

Category Controls
XSS CSP, output encoding, DOMPurify
CSRF SameSite cookies, CSRF tokens
Injection Parameterized queries, input validation
Headers Helmet.js, HSTS, X-Frame-Options
Auth Rate limiting, bcrypt, MFA