DDevArchive
Đăng nhập

Security thực chiến: OWASP Top 10 và cách phòng chống

OWASP Top 10 là danh sách 10 lỗ hổng bảo mật web phổ biến nhất — do hàng ngàn chuyên gia đúc kết. Bạn sẽ thấy từng lỗ hổng hoạt động thế nào (demo tấn công), rồi viết code phòng chống. Đây là kiến thức bắt buộc cho mọi backend developer.

OWASP Top 10 (2021)

#Lỗ hổngMô tả ngắnĐã học?
A01Broken Access ControlUser A xem được dữ liệu user B⚠️ Chưa
A02Cryptographic FailuresMã hoá yếu hoặc thiếu✅ bcrypt (g2 l10)
A03InjectionSQL/NoSQL injection⚠️ Chưa demo
A04Insecure DesignThiết kế không tính tới security✅ Phần nào (g3)
A05Security MisconfigurationConfig sai: default password, debug mode✅ Helmet (g2 l11)
A06Vulnerable ComponentsDùng thư viện có lỗ hổng đã biết⚠️ Chưa
A07Auth FailuresBrute force, weak password✅ JWT + rate limit
A08Software Integrity FailuresCI/CD bị tấn công, dependency bị hack⚠️ Chưa
A09Logging & Monitoring FailuresKhông phát hiện được tấn công✅ Logging (g3 l14)
A10SSRFServer bị lừa gọi URL nội bộ⚠️ Chưa

SQL Injection: tấn công và phòng chống

// File: sqli.js
// ❌ SQL Injection: GHÉP CHUỖI = MỞ CỬA CHO HACKER
const query = `SELECT * FROM users WHERE email = '${email}'`

// Hacker nhập email: ' OR 1=1 --
// Query thành: SELECT * FROM users WHERE email = '' OR 1=1 --'
// → Trả về TẤT CẢ users trong database!

// Tệ hơn: '; DROP TABLE users; --
// → XOÁ TOÀN BỘ BẢNG USERS
// File: safe.js
// ✅ Parameterized query: database tự escape input
const query = "SELECT * FROM users WHERE email = $1"
const result = await db.query(query, [email])

// ✅ ORM (Knex): tự parameterize
const users = await knex("users").where({ email })

// ✅ Zod validate input trước khi dùng
const EmailSchema = z.string().email()
const safeEmail = EmailSchema.parse(email) // throw nếu không phải email

XSS (Cross-Site Scripting)

Hacker nhập <script>alert(“hacked”)</script> vào form. Nếu app hiển thị input này mà không escape, script sẽ chạy trên trình duyệt của MỌI USER nhìn thấy nội dung đó.

// File: xss.js
// ❌ SAI: render HTML trực tiếp từ user input
element.innerHTML = userComment // nếu userComment có <script> → chạy!

// ✅ ĐÚNG: dùng textContent (tự escape HTML)
element.textContent = userComment

// ✅ React: tự escape mặc định
// <p>{userComment}</p> → an toàn, React tự escape

// ⚠️ NGUY HIỂM: dangerouslySetInnerHTML
// <div dangerouslySetInnerHTML={{ __html: userComment }} />
// → CHỈ dùng khi bạn TIN TƯỞNG 100% nội dung (ví dụ: từ CMS admin)

Broken Access Control: ai xem được gì?

// File: access.js
// ❌ SAI: không kiểm tra ownership
app.get("/api/orders/:id", async (req, res) => {
  const order = await db.orders.findById(req.params.id)
  res.json(order) // User A có thể xem order của User B!
})

// ✅ ĐÚNG: kiểm tra user_id trước khi trả data
app.get("/api/orders/:id", authMiddleware, async (req, res) => {
  const order = await db.orders.findById(req.params.id)

  // Kiểm tra: order này có phải của user đang đăng nhập?
  if (order.userId !== req.user.id) {
    return res.status(403).json({ error: "Không có quyền" })
  }

  res.json(order)
})

SSRF: Server bị lừa gọi URL nội bộ

// File: ssrf.js
// ❌ SAI: server fetch URL do user cung cấp mà không kiểm tra
app.post("/api/preview", async (req, res) => {
  const { url } = req.body
  const html = await fetch(url).then(r => r.text()) // NGUY HIỂM!
  // Hacker gửi url = "http://169.254.169.254/metadata"
  // → Server fetch metadata AWS → lộ credentials
})

// ✅ ĐÚNG: validate URL, block IP nội bộ
import { URL } from 'url'

function isInternalIp(hostname) {
  const blocked = ["localhost", "127.0.0.1", "0.0.0.0", "169.254.169.254"]
  if (blocked.includes(hostname)) return true
  if (hostname.startsWith("10.")) return true
  if (hostname.startsWith("192.168.")) return true
  if (hostname.startsWith("172.")) return true
  return false
}

app.post("/api/preview", async (req, res) => {
  const parsed = new URL(req.body.url)
  if (isInternalIp(parsed.hostname)) {
    return res.status(400).json({ error: "URL nội bộ không được phép" })
  }
  // ... tiếp tục fetch an toàn
})
npm audit: quét lỗ hổng thư viện

Chạy npm audit thường xuyên để phát hiện thư viện có lỗ hổng đã biết (CVE). Dùng npm audit fix để tự động cập nhật. Thiết lập Dependabot trên GitHub để nhận alert tự động.

❓ Cách phòng SQL injection hiệu quả nhất?

  • Nói được ít nhất 5 trong OWASP Top 10
  • Demo SQL injection và viết code phòng chống
  • Phân biệt XSS stored và reflected
  • Kiểm tra access control (ownership check)
  • Validate URL để chống SSRF
  • Chạy npm audit và hiểu kết quả