Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

Bảo mật web 2026: Checklist 10 bước chống APT, DDoS và rò rỉ dữ liệu người dùng

Ếch Trendy
Ếch Trendy

2 tháng trước · 10 phút đọc

Tại sao bảo mật web vẫn là điểm yếu của hầu hết dev?

Mình từng deploy một REST API lên production mà không có rate limiting. Ba tuần sau, log đầy những request lạ từ một dải IP ở Đông Âu - mỗi giây vài trăm lần hit vào endpoint /login. Không phải hacker cao thủ gì, chỉ là một script brute-force cơ bản. Nhưng đủ để server hết tài nguyên và người dùng thật không đăng nhập được.

Đó là bài học đắt giá nhất mình học được về bảo mật.

346053 Một script đơn giản cũng đủ hạ gục server nếu không có rate limiting

Năm 2026, bối cảnh threat landscape thay đổi nhanh hơn trước nhiều. 46% các vụ xâm nhập an ninh mạng ảnh hưởng đến doanh nghiệp dưới 1.000 nhân viên, và SMB bị nhắm tới gần 4 lần thường xuyên hơn doanh nghiệp lớn (Verizon DBIR 2025) - con số này nói lên rằng không phải chỉ công ty lớn mới bị tấn công. Bài viết này là checklist thực chiến mình tổng hợp sau nhiều năm làm backend và đọc post-mortem của các vụ rò rỉ dữ liệu lớn. Không lý thuyết suông - mỗi bước đều có code hoặc config cụ thể.


Bước 1-3: Nền tảng không thể bỏ qua

Bước 1: Input validation và sanitization

SQL injection vẫn xếp hạng trong OWASP Top 10 sau hơn một thập kỷ. Lý do? Dev vẫn trust input từ người dùng.

Rule đơn giản: không bao giờ trust input, dù từ form, query string, hay header.

// Sai - nối chuỗi trực tiếp vào SQL
const query = `SELECT * FROM users WHERE id = ${req.params.id}`;

// Đúng - dùng parameterized query
const query = 'SELECT * FROM users WHERE id = ?';
const [rows] = await db.execute(query, [req.params.id]);

Với input validation, dùng thư viện như zod hoặc joi thay vì tự viết regex:

import { z } from 'zod';

// Định nghĩa schema rõ ràng
const LoginSchema = z.object({
  email: z.string().email().max(255),
  password: z.string().min(8).max(128)
});

// Validate trước khi xử lý
const result = LoginSchema.safeParse(req.body);
if (!result.success) {
  return res.status(400).json({ error: 'Invalid input' });
}

346054 Parameterized query là tường thành đầu tiên ngăn SQL injection

Bước 2: HTTPS và HSTS bắt buộc

HTTPS không còn là optional. Nhưng nhiều app vẫn thiếu HSTS (HTTP Strict Transport Security) - header báo browser chỉ dùng HTTPS, ngăn downgrade attack.

// Trong Express.js
app.use((req, res, next) => {
  // Bắt buộc HTTPS trong production
  if (process.env.NODE_ENV === 'production' && !req.secure) {
    return res.redirect(301, `https://${req.headers.host}${req.url}`);
  }
  // Set HSTS header - 1 năm, bao gồm subdomain
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains; preload'
  );
  next();
});

Bước 3: Security headers đầy đủ

Dùng helmet trong Node.js để set đủ các security header quan trọng:

import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'nonce-{random}'"],
      styleSrc: ["'self'", "'unsafe-inline'"],
      imgSrc: ["'self'", 'data:', 'https:'],
    }
  },
  // Ngăn clickjacking
  frameguard: { action: 'deny' },
  // Tắt X-Powered-By để không lộ tech stack
  hidePoweredBy: true
}));

CSP (Content Security Policy) là cái hay bị bỏ qua nhất. Nó ngăn XSS bằng cách kiểm soát nguồn script nào được phép chạy.


Bước 4-6: Bảo vệ API và access tokens

Bước 4: CORS configuration đúng cách

CORS sai là một trong những lỗi phổ biến nhất mình thấy trong code review. Nhiều người set origin: '*' cho nhanh và quên mất.

import cors from 'cors';

// Sai - cho phép tất cả origin
app.use(cors({ origin: '*' }));

// Đúng - whitelist cụ thể
const allowedOrigins = [
  'https://yourapp.com',
  'https://admin.yourapp.com',
  // Thêm localhost chỉ trong development
  ...(process.env.NODE_ENV !== 'production' ? ['http://localhost:3000'] : [])
];

app.use(cors({
  origin: (origin, callback) => {
    // Cho phép server-to-server (không có origin)
    if (!origin) return callback(null, true);
    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

346055 CORS origin '' là mời hacker vào nhà - whitelist cụ thể thay thế*

Bước 5: JWT và access token an toàn

Mấy điều hay bị sai với JWT:

  • Lưu token trong localStorage: dễ bị XSS đọc. Dùng httpOnly cookie thay thế.
  • Secret key yếu: dùng ít nhất 256-bit random string, không phải 'mysecret'.
  • Không set expiry: token tồn tại mãi mãi nếu bị leak.
import jwt from 'jsonwebtoken';

// Tạo access token ngắn hạn + refresh token dài hạn
const generateTokens = (userId) => {
  const accessToken = jwt.sign(
    { userId },
    process.env.JWT_SECRET,
    { expiresIn: '15m' } // Ngắn thôi
  );

  const refreshToken = jwt.sign(
    { userId },
    process.env.JWT_REFRESH_SECRET,
    { expiresIn: '7d' }
  );

  return { accessToken, refreshToken };
};

// Set cookie an toàn
res.cookie('accessToken', accessToken, {
  httpOnly: true,   // JS không đọc được
  secure: true,     // Chỉ qua HTTPS
  sameSite: 'strict', // Chống CSRF
  maxAge: 15 * 60 * 1000 // 15 phút
});

Bước 6: Không hardcode secrets

Mình vẫn thấy AWS credentials, database password trong file .env được commit lên GitHub public. Một khi đã lên git history, coi như leak rồi dù bạn có xóa sau.

Dùng secret management tool:

  • AWS Secrets Manager hoặc Parameter Store
  • HashiCorp Vault cho on-premise
  • GitHub Actions Secrets cho CI/CD

Minimum là đảm bảo .env trong .gitignore và rotate key định kỳ. Với production, dùng tool quản lý secret thay vì file .env thật.


Bước 7-8: Rate limiting và chống DDoS

Bước 7: Rate limiting đa tầng

Một lớp rate limiting ở app level là chưa đủ - đặc biệt khi đối mặt với volumetric DDoS. Cần phòng thủ theo chiều sâu:

Tầng 1 - CDN/WAF: Cloudflare, AWS WAF, hoặc Akamai lọc traffic ở edge trước khi đến server.

Tầng 2 - App level: Dùng express-rate-limit với Redis backend để rate limit phân tán:

import rateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import { createClient } from 'redis';

const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

// Rate limit chung cho API
export const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 phút
  max: 100,
  store: new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args) }),
  message: { error: 'Too many requests, please try again later' },
  standardHeaders: true,
  legacyHeaders: false
});

// Strict hơn cho auth endpoints
export const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // 5 lần thử trong 15 phút
  store: new RedisStore({ sendCommand: (...args) => redisClient.sendCommand(args) }),
  skipSuccessfulRequests: true // Không đếm lần đăng nhập thành công
});

// Apply
app.use('/api/', apiLimiter);
app.use('/api/auth/login', authLimiter);

346056 Rate limit auth endpoint nghiêm hơn - 5 lần sai là đủ để ngăn brute force

Bước 8: Chống APT với anomaly detection

APT (Advanced Persistent Threat) khác DDoS ở chỗ: nó âm thầm, kéo dài, và thường đến từ nhiều IP khác nhau để bypass rate limiting thông thường.

Một số pattern cần monitor:

  • Nhiều account khác nhau nhưng cùng behavioral pattern (device fingerprint, timing)
  • Spike nhỏ nhưng liên tục từ nhiều IP
  • Các request scan endpoint lạ như /admin, /.env, /wp-admin
// Middleware phát hiện endpoint scanning
const suspiciousEndpoints = new Set([
  '/.env', '/admin', '/wp-admin', '/config.php',
  '/.git', '/backup', '/phpmyadmin'
]);

app.use((req, res, next) => {
  if (suspiciousEndpoints.has(req.path)) {
    // Log và alert ngay
    logger.warn({
      type: 'ENDPOINT_SCAN',
      ip: req.ip,
      path: req.path,
      userAgent: req.headers['user-agent'],
      timestamp: new Date().toISOString()
    });
    // Trả 404 thay vì 403 để không lộ thông tin
    return res.status(404).json({ error: 'Not found' });
  }
  next();
});

Kết hợp với fail2ban ở server level để tự động ban IP có pattern đáng ngờ.


Bước 9-10: Monitoring và incident response

Bước 9: Logging có ý nghĩa

Log nhiều không phải log tốt. Mình từng xem log production của một startup và thấy họ log từng request nhưng không log context quan trọng: ai làm, từ đâu, kết quả gì.

Structured logging với correlation ID là thứ cứu mình nhiều lần:

import { v4 as uuidv4 } from 'uuid';
import winston from 'winston';

// Logger với format JSON
const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: 'security.log' })
  ]
});

// Middleware gắn correlation ID vào mỗi request
app.use((req, res, next) => {
  req.correlationId = req.headers['x-correlation-id'] || uuidv4();
  res.setHeader('x-correlation-id', req.correlationId);
  next();
});

// Log security event
const logSecurityEvent = (type, req, extra = {}) => {
  logger.warn({
    type,
    correlationId: req.correlationId,
    userId: req.user?.id || 'anonymous',
    ip: req.ip,
    method: req.method,
    path: req.path,
    ...extra
  });
};

// Dùng khi cần
logSecurityEvent('FAILED_LOGIN', req, { email: req.body.email });

346057 Correlation ID giúp trace một request qua toàn bộ hệ thống - không thể thiếu khi debug incident

Bước 10: Incident response plan

Checklist kỹ thuật mà không có plan khi incident xảy ra thì vẫn là lỗ hổng. Mình thấy nhiều team dev rất giỏi về code nhưng không biết làm gì khi bị tấn công thật.

Khi phát hiện breach, thứ tự ưu tiên:

  1. Contain - Cô lập hệ thống bị ảnh hưởng ngay. Disable endpoint hoặc block IP range trước.
  2. Assess - Xác định scope: dữ liệu nào bị ảnh hưởng, bao nhiêu user.
  3. Notify - Theo GDPR và Nghị định 13/2023/NĐ-CP của Việt Nam, rò rỉ dữ liệu cá nhân phải được thông báo trong vòng 72 giờ.
  4. Remediate - Fix lỗ hổng, rotate tất cả secrets và tokens.
  5. Post-mortem - Viết lại timeline, tìm root cause, cải thiện detection.

Tool tham khảo cho monitoring stack: Grafana + Loki cho log aggregation, Prometheus cho metrics, PagerDuty hoặc OpsGenie cho alerting. Với budget nhỏ hơn, Elastic Stack (ELK) free tier là lựa chọn ổn.

Tải checklist đầy đủ tại đây: Tải checklist bảo mật web 10 bước


Case study: Những vụ rò rỉ dữ liệu đáng học nhất

Lý thuyết xong, xem thực tế. Mình phân tích 3 pattern tấn công hay gặp nhất dựa trên các public post-mortem.

Pattern 1: API key lộ trên GitHub

Đây là pattern kinh điển. Dev commit .env file hoặc hardcode key vào code, push lên repo public. Bot của hacker scan GitHub liên tục và phát hiện key trong vài phút. Không cần kỹ năng hack gì cả.

Fix: Dùng git-secrets hoặc detect-secrets để scan trước khi commit. Setup pre-commit hook:

# Cài detect-secrets
pip install detect-secrets

# Tạo baseline
detect-secrets scan > .secrets.baseline

# Thêm vào pre-commit hook
detect-secrets-hook --baseline .secrets.baseline

346058 Bot scan GitHub tìm secrets chạy 24/7 - một lần push là đủ để bị compromise

Pattern 2: Broken authentication do thiếu MFA

Nhiều ứng dụng SaaS bị chiếm account vì credential stuffing - hacker dùng database từ breach cũ để thử đăng nhập. Nếu người dùng dùng cùng mật khẩu nhiều chỗ (66% người dùng tái sử dụng mật khẩu giữa các tài khoản (Google/Harris Poll Online Security Survey) người dùng làm vậy), và app không có MFA hay anomaly detection, account bị chiếm là chuyện thời gian.

Pattern 3: Unpatched dependencies

Log4Shell năm 2021 là ví dụ điển hình. 3.866 tổ chức và 38.278 ứng dụng phụ thuộc Log4j (Veracode) bị ảnh hưởng chỉ vì một dependency phổ biến có lỗ hổng. Chạy npm audit hoặc snyk test định kỳ - và quan trọng hơn là act on the results, đừng chỉ chạy để biết.

# Kiểm tra và fix tự động khi có thể
npm audit fix

# Với Snyk - báo cáo chi tiết hơn
snyk test
snyk monitor # Theo dõi liên tục

Thêm dependency scanning vào CI/CD pipeline để không merge code có lỗ hổng known vulnerability vào main.