Bảo mật Kiến trúc hệ thống

Thứ ba, 28 Th07 2026 · 15 phút đọc · 12 lượt xem

Xóa mù lỗ hổng web (Phần 2): Phân quyền, xác thực và cấu hình — nơi hệ thống lớn thường vỡ

Xóa mù lỗ hổng web (Phần 2): Phân quyền, xác thực và cấu hình — nơi hệ thống lớn thường vỡ

Các lỗ hổng ở phần một có điểm chung là "code sai" và thường bị công cụ quét bắt được. Nhóm lỗ hổng trong bài này thì khác: code chạy đúng như thiết kế, chỉ là thiết kế và cấu hình sai. Không công cụ tự động nào biết được rằng nhân viên phòng kế toán không nên xem được hồ sơ lương của giám đốc — chỉ có bạn biết.

Đó cũng là lý do OWASP Top 10:2025 xếp Broken Access Control ở vị trí số một với 40 CWE, hơn 1,8 triệu lượt ghi nhận và 32.654 CVE trong dữ liệu đóng góp; còn Security Misconfiguration nhảy từ hạng 5 lên hạng 2 với hơn 719.000 lượt ghi nhận.

2025-mappings.pngBản đồ thay đổi giữa OWASP Top 10:2021 và OWASP Top 10:2025. Nguồn: OWASP Foundation, giấy phép CC BY-SA 4.0.

1. Broken Access Control (A01) — quán quân nhiều kỳ liền

Vài biểu hiện quen thuộc đến mức đáng sợ:

  • IDOR — đổi tham số từ hóa đơn 1042 thành 1043 là xem được hóa đơn của khách khác, vì API chỉ kiểm tra "đã đăng nhập chưa" mà quên kiểm tra "bản ghi này có phải của bạn không".
  • Force browsing — gõ thẳng đường dẫn /admin/dashboard là vào được, vì trang quản trị chỉ được ẩn khỏi menu chứ không được bảo vệ ở server.
  • Phân quyền đặt ở frontend — nút Xóa bị ẩn bằng JavaScript, nhưng API DELETE vẫn nhận request gửi bằng curl. OWASP nêu đúng tình huống này như một kịch bản điển hình.
  • Leo thang đặc quyền qua việc sửa JWT hoặc cookie, cấu hình CORS quá rộng cho phép domain lạ gọi API, và API thiếu kiểm soát cho các method POST, PUT, DELETE.

Cách phòng chống

Nguyên tắc nền tảng là deny by default: mọi thứ đóng, chỉ mở những gì được liệt kê rõ ràng. Việc kiểm tra quyền phải nằm ở server-side, nơi kẻ tấn công không sửa được. Hãy xây một cơ chế phân quyền duy nhất rồi tái sử dụng khắp ứng dụng, thay vì mỗi controller tự viết một kiểu — vì lỗ hổng luôn nằm ở endpoint mà ai đó quên. Model phải kiểm tra quyền sở hữu bản ghi chứ không chỉ vai trò. Session phải bị hủy ở phía server khi logout; JWT nên có thời hạn ngắn kèm refresh token để còn thu hồi được khi cần.

Một việc rất đáng làm mà nhiều đội bỏ qua: viết integration test cho phân quyền. Mỗi endpoint nhạy cảm đều có một test khẳng định user A không thể đụng vào dữ liệu của user B. Đây là việc QA làm được và hiệu quả hơn nhiều so với pentest mỗi năm một lần.

access-control.svgLỗ hổng kiểm soát truy cập. Nguồn: PortSwigger Web Security Academy.

2. CSRF — lợi dụng chính phiên đăng nhập của nạn nhân

Người dùng đang đăng nhập ngân hàng ở một tab, rồi mở phải trang độc ở tab khác. Trang đó âm thầm gửi một request chuyển tiền, và trình duyệt tự động đính kèm cookie phiên. Server thấy request hợp lệ nên thực hiện.

Phòng chống: đặt SameSite ở mức Lax hoặc Strict cho cookie phiên (đã là mặc định của nhiều trình duyệt nhưng vẫn nên khai báo tường minh); áp dụng anti-CSRF token theo synchronizer token pattern cho các form làm thay đổi trạng thái; kiểm tra header Origin hoặc Referer ở endpoint nhạy cảm; và tuyệt đối không dùng phương thức GET cho hành động thay đổi dữ liệu.

forged-request.pngMột request bị giả mạo trong tấn công CSRF. Nguồn: Cloudflare Learning Center.

3. Authentication Failures (A07) — hỏng ở khâu nhận diện người dùng

Rủi ro lớn nhất hiện nay không phải ai đó bẻ khóa mật khẩu của bạn, mà là credential stuffing: kẻ tấn công lấy hàng triệu cặp email và mật khẩu rò rỉ từ nơi khác rồi thử hàng loạt vào hệ thống của bạn. Biến thể phổ biến là dò các mật khẩu biến thể kiểu Password1!, Password2!. Bên cạnh đó là session fixation, session ID lộ trên URL, không hủy phiên khi logout, và quy trình quên mật khẩu dùng câu hỏi bí mật — thứ mà OWASP đánh giá là không thể làm cho an toàn.

Cách phòng chống

Bật xác thực đa yếu tố ở mọi nơi có thể, đặc biệt cho tài khoản quản trị. Không phát hành sản phẩm kèm credential mặc định. Kiểm tra mật khẩu mới với danh sách mật khẩu yếu phổ biến và danh sách đã rò rỉ. Theo hướng dẫn NIST 800-63B, đừng ép người dùng đổi mật khẩu định kỳ — chỉ buộc đổi khi nghi ngờ rò rỉ, vì ép đổi định kỳ khiến mật khẩu yếu đi. Lưu mật khẩu bằng hàm băm chuyên dụng như Argon2 hoặc bcrypt kèm salt. Sinh session ID mới sau khi đăng nhập thành công, lưu trong cookie có Secure, HttpOnly, SameSite, và đặt cả timeout theo phiên lẫn timeout tuyệt đối. Trả về thông báo lỗi giống nhau cho mọi trường hợp đăng nhập sai để chống dò tài khoản. Với JWT, phải kiểm tra aud, iss, exp và thuật toán ký. Và nếu được, hãy dùng giải pháp identity đã được kiểm chứng thay vì tự viết.

4. Cryptographic Failures (A04) — dùng đúng thuật toán vẫn có thể sai

Nhóm này thường không phải do thuật toán bị bẻ, mà do dùng sai: dữ liệu nhạy cảm truyền hoặc lưu ở dạng rõ, dùng thuật toán lỗi thời như MD5 hay SHA-1 cho mật khẩu, tự chế cơ chế mã hóa, hoặc quản lý khóa cẩu thả.

Phòng chống: bắt buộc TLS cho mọi kết nối kể cả nội bộ và bật HSTS; phân loại dữ liệu để biết cái gì thực sự cần mã hóa khi lưu trữ; dùng thư viện chuẩn với chế độ mã hóa có xác thực thay vì tự lắp ghép; đưa khóa và secret vào KMS hoặc secret manager thay vì file cấu hình trong repo; và không lưu thứ bạn không thực sự cần — dữ liệu không tồn tại là dữ liệu không thể bị đánh cắp.

5. Security Misconfiguration (A02) — kẻ leo hạng nhanh nhất

Lý do rất dễ hiểu: ngày càng nhiều hành vi của hệ thống được quyết định bởi cấu hình chứ không phải bởi code. Các kịch bản kinh điển gồm ứng dụng mẫu và console quản trị còn nguyên trên production với mật khẩu mặc định; directory listing chưa tắt; stack trace chi tiết trả thẳng về trình duyệt và lộ phiên bản thư viện đang dùng; và bucket lưu trữ trên cloud để chế độ công khai.

Phòng chống: xây quy trình hardening lặp lại được và tự động hóa bằng Infrastructure as Code, để dựng môi trường mới luôn ra cấu hình an toàn như nhau; giữ dev, QA và production cấu hình giống nhau nhưng credential khác nhau; gỡ mọi tính năng, cổng, tài khoản và tài liệu mẫu không dùng đến; bật đầy đủ security header như Content-Security-Policy, Strict-Transport-Security, X-Content-Type-Options và Referrer-Policy; chặn thông báo lỗi chi tiết bằng một bộ xử lý lỗi tập trung; ưu tiên identity federation và credential ngắn hạn thay vì nhúng khóa tĩnh vào code hay pipeline; và có bước tự động kiểm tra lại cấu hình ở mọi môi trường.

6. Software Supply Chain Failures (A03) — nhóm mới mà cộng đồng lo nhất

Đúng 50% người tham gia khảo sát cộng đồng của OWASP xếp nhóm này ở vị trí số một. Đây là bản mở rộng của "thư viện lỗi thời" ngày xưa, nay bao trùm toàn bộ hệ sinh thái: dependency trực tiếp và gián tiếp, IDE và extension, registry, image, hệ thống build và phân phối. Đáng chú ý, nhóm này có ít CVE nhất nhưng điểm exploit và impact trung bình cao nhất — hiếm nhưng chí mạng.

Phòng chống: sinh và quản lý SBOM cho toàn bộ phần mềm; theo dõi cả dependency gián tiếp; gỡ bỏ thư viện không còn dùng; quét liên tục bằng các công cụ như OWASP Dependency-Track hoặc Dependency-Check và theo dõi nguồn CVE, NVD, OSV; chỉ lấy artifact từ nguồn tin cậy có ký số; siết quyền và tách vai trò trong CI/CD sao cho không một cá nhân nào có thể tự đẩy code lên production mà không qua người thứ hai; và nhớ rằng pipeline build cần được bảo vệ ít nhất ngang bằng hệ thống mà nó tạo ra.

7. Logging & Alerting (A09) và Mishandling of Exceptional Conditions (A10)

Hai nhóm này hay bị bỏ qua nhất vì chúng không gây thiệt hại trực tiếp — chúng chỉ khiến bạn không biết mình đang bị tấn công. Bản 2025 đổi tên A09 thành Logging and Alerting để nhấn mạnh: log rất tốt nhưng không có cảnh báo thì gần như vô giá trị.

Hãy đảm bảo mọi sự kiện đăng nhập thành công và thất bại, mọi lần từ chối quyền truy cập, mọi giao dịch giá trị cao đều được ghi lại với đủ ngữ cảnh để truy vết. Log phải ở định dạng máy đọc được, được đẩy ra ngoài máy chủ ứng dụng, có kiểm soát toàn vẹn chống sửa xóa, và tuyệt đối không chứa mật khẩu, token hay dữ liệu cá nhân. Kèm theo đó là ngưỡng cảnh báo hợp lý và playbook xử lý, vì quá nhiều cảnh báo giả sẽ khiến đội vận hành bỏ lỡ cảnh báo thật.

A10 là nhóm hoàn toàn mới trong bản 2025, gom 24 CWE về xử lý lỗi sai cách: lộ thông tin nhạy cảm trong thông báo lỗi, thiếu xử lý tham số vắng mặt, và đặc biệt là failing open — khi có sự cố thì hệ thống mặc định cho qua thay vì chặn lại. Nguyên tắc là fail closed: nếu dịch vụ xác thực không phản hồi thì từ chối, đừng cho vào. Hãy bắt lỗi ngay tại nơi phát sinh, có global exception handler làm lưới an toàn, rollback trọn vẹn transaction dở dang, và giám sát các mẫu lỗi lặp lại vì đó thường là dấu hiệu của một cuộc dò quét đang diễn ra.

Lộ trình 30 – 60 – 90 ngày cho đội đang chạy production

30 ngày đầu — làm những việc rẻ mà hiệu quả cao: đổi toàn bộ credential mặc định, bật MFA cho tài khoản quản trị, tắt directory listing và stack trace trên production, thêm security header, bật quét phụ thuộc trong CI, rà soát quyền công khai của bucket lưu trữ.

60 ngày tiếp theo — xử lý phần cần sửa code: rà soát toàn bộ endpoint và bổ sung kiểm tra quyền sở hữu bản ghi, viết integration test phân quyền cho các endpoint nhạy cảm, chuẩn hóa cơ chế xác thực và quản lý phiên, đưa secret vào secret manager.

90 ngày — xây năng lực dài hạn: sinh SBOM tự động, hardening bằng Infrastructure as Code, tập trung log kèm bộ cảnh báo và playbook, diễn tập một sự cố giả định để kiểm tra quy trình phản ứng.

Kết

Nếu phần một là câu chuyện của lập trình viên, phần hai là câu chuyện của cả đội: kiến trúc sư quyết định mô hình phân quyền, DevOps quyết định cấu hình, QA quyết định thứ được kiểm thử, và người vận hành quyết định bạn phát hiện sự cố sau năm phút hay sau năm tháng. An toàn ứng dụng không phải một hạng mục kiểm tra trước khi go-live, mà là một thói quen được lặp lại ở mọi sprint.

Nguồn tham khảo

Sẵn sàng chuyển đổi doanh nghiệp?

Hãy thảo luận về cách chúng tôi có thể giúp bạn tận dụng AI và chuyển đổi số.

Chia sẻ