AppSec 4 — Kiểm soát truy cập & Mật mã

13 thg 7, 2026 3 lượt xem
#security
#cryptography
#devops
#access-control
#tls

AppSec 4 — Kiểm soát truy cập & Mật mã

Trong bài OWASP Top 10 chúng ta đã điểm danh cả 10 nhóm rủi ro. Hai nhóm đứng đầu bảng xếp hạng 2021 — A01 Broken Access ControlA02 Cryptographic Failures — không phải ngẫu nhiên leo lên vị trí cao nhất. Chúng là những lỗi bị lạm dụng nhiều nhất trong các vụ rò rỉ dữ liệu thực tế, và với một hệ thống ngân hàng, hậu quả không dừng ở "mất mặt" mà là mất tiền, mất khách hàng, và vi phạm quy định pháp lý.

Bài này đào sâu hai nhóm đó. A01 trả lời câu hỏi "ai được phép làm gì" — và điều gì xảy ra khi câu trả lời đó bị bỏ sót hoặc kiểm tra sai chỗ. A02 trả lời câu hỏi "dữ liệu nhạy cảm được bảo vệ ra sao khi nằm yên và khi di chuyển" — và điều gì xảy ra khi ta dùng sai thuật toán, để lộ khoá, hay đơn giản là quên bật mã hoá.

A01 — Broken Access Control

Nhắc lại: authentication khác authorization

Hai khái niệm này liên tục bị nhầm lẫn, và chính sự nhầm lẫn đó sinh ra lỗ hổng:

  • Authentication (xác thực) — "bạn là ai?". Hệ thống xác nhận danh tính: kiểm tra username/password, token, chứng chỉ. Kết quả là biết được ai đang gọi.
  • Authorization (phân quyền) — "bạn được phép làm gì?". Sau khi biết là ai, hệ thống quyết định người đó có quyền truy cập tài nguyên/thao tác cụ thể hay không.

Access control là phần authorization. Một hệ thống có thể xác thực hoàn hảo (biết chắc bạn là khách hàng A đã đăng nhập) mà vẫn thủng hoàn toàn nếu nó cho phép khách hàng A xem tài khoản của khách hàng B. Xác thực đúng người ≠ chỉ cho phép làm đúng việc.

Các mô hình phân quyền

Có hai mô hình nền tảng, đã bàn kỹ ở bài Access Control trong Data Governance:

  • RBAC (Role-Based Access Control) — gán quyền theo vai trò. Người dùng thuộc role (teller, credit_officer, admin); mỗi role có tập quyền cố định. Đơn giản, dễ audit, phù hợp phần lớn nghiệp vụ ngân hàng.
  • ABAC (Attribute-Based Access Control) — quyết định dựa trên thuộc tính của chủ thể, tài nguyên và ngữ cảnh (chi nhánh, thời gian, hạn mức giao dịch). Linh hoạt hơn nhưng phức tạp, dùng khi quy tắc quá tinh vi để nhét vừa vào role.

Dù chọn mô hình nào, điểm mấu chốt không phải là thiết kế mà là thực thi: quy tắc phân quyền phải được kiểm tra ở server, cho mọi request, trên mọi tài nguyên.

IDOR / BOLA — lỗi phổ biến nhất

IDOR (Insecure Direct Object Reference), trong ngữ cảnh API còn gọi là BOLA (Broken Object Level Authorization), là lỗi access control thường gặp và nguy hiểm nhất. Cơ chế đơn giản đến mức đáng sợ: hệ thống dùng một định danh (ID) để trỏ tới đối tượng, nhưng quên kiểm tra người gọi có sở hữu đối tượng đó không.

Ví dụ kinh điển. Khách hàng đăng nhập, xem tài khoản của mình qua URL:

GET /account/12345

Kẻ tấn công chỉ cần đổi con số:

GET /account/12346
GET /account/12347
...

Nếu server chỉ kiểm tra "đã đăng nhập chưa" mà không kiểm tra "tài khoản 12346 có thuộc về người đang đăng nhập không", kẻ tấn công đọc được sạch tài khoản người khác. Không cần công cụ tinh vi — chỉ cần một vòng lặp tăng ID. Đây là cách rất nhiều vụ rò rỉ dữ liệu ngân hàng/fintech xảy ra: API trả về đúng dữ liệu cho đúng ID, nhưng không hỏi ID đó có phải của bạn không.

BOLA đặc biệt hiểm với API vì API thường phơi bày ID trực tiếp và được gọi tự động, dễ quét hàng loạt. Chúng ta sẽ bàn sâu hơn ở bài API Security.

Các lỗi access control khác

  • Thiếu kiểm tra phía server (tin client) — ẩn nút "Xoá" trên UI với người dùng thường, nhưng endpoint DELETE /account/123 vẫn hoạt động nếu gọi thẳng. UI chỉ là gợi ý; quyền phải kiểm ở server. Bất cứ thứ gì chạy trên trình duyệt/app của người dùng đều có thể bị bỏ qua.
  • Privilege escalation dọc (vertical) — người dùng thường thực hiện được thao tác của admin. Ví dụ đổi tham số role=user thành role=admin trong request, hoặc gọi được endpoint quản trị mà không bị chặn.
  • Privilege escalation ngang (horizontal) — người dùng truy cập tài nguyên của người dùng cùng cấp khác (chính là IDOR/BOLA ở trên: khách A đọc dữ liệu khách B).
  • Path traversal — lợi dụng đường dẫn file để thoát khỏi thư mục cho phép: GET /files?name=../../etc/passwd. Nếu ứng dụng ghép chuỗi đường dẫn mà không chuẩn hoá và kiểm tra, kẻ tấn công đọc file hệ thống.
  • Force browsing — đoán/mò URL tới trang hoặc chức năng không có link công khai nhưng cũng không được bảo vệ (/admin, /export/all-customers.csv). "Không ai biết đường link" không phải là kiểm soát truy cập.

Nguyên tắc phòng thủ

Access control đúng đắn xoay quanh vài nguyên tắc bất di bất dịch:

  1. Deny by default (mặc định từ chối) — trừ tài nguyên công khai, mọi truy cập phải bị chặn cho đến khi có quy tắc cho phép rõ ràng. Không có quy tắc = không được vào. Ngược lại (mặc định cho phép) là công thức thảm hoạ: quên thêm luật ở đâu là thủng ở đó.
  2. Kiểm tra quyền ở SERVER cho MỌI request — không tin bất kỳ dữ liệu nào từ client (tham số ẩn, cookie, header, giá trị trong JWT chưa verify). Server là nơi duy nhất quyết định.
  3. Kiểm tra theo bản ghi (object-level) — không chỉ hỏi "role này được xem loại tài nguyên account không?" mà phải hỏi "user này được xem đúng bản ghi account cụ thể này không?". Đây chính là lá chắn chống IDOR/BOLA. Cách thực thi phổ biến nhất: lọc dữ liệu theo chủ sở hữu ngay trong câu truy vấn.
  4. Không dựa vào ẩn giấu (no security by obscurity) — URL khó đoán, ID ngẫu nhiên, tính năng không public đều không thay thế được kiểm tra quyền thực sự.

Điểm tinh tế ở sơ đồ: khi không tìm thấy bản ghi thuộc về người gọi, nhiều hệ thống trả 404 thay vì 403 — để không tiết lộ rằng "ID này có tồn tại nhưng bạn không có quyền", tránh giúp kẻ tấn công dò sự tồn tại của tài nguyên.

Object-level check bằng SQL — minh hoạ

Cách phòng IDOR gọn nhất là để authorization filter đi thẳng vào câu truy vấn: không bao giờ lấy bản ghi theo mỗi ID, mà luôn kèm điều kiện chủ sở hữu. Câu SELECT dưới đây mô phỏng một truy vấn "an toàn": chỉ trả tài khoản khi vừa khớp account_no được yêu cầu vừa thuộc đúng customer_id của phiên đăng nhập (ở đây giả lập bằng giá trị cứng 42).

-- ▶ Chạy được
SELECT a.id, a.account_no, a.balance, a.currency, c.full_name
FROM accounts a
JOIN customers c ON c.id = a.customer_id
WHERE a.customer_id = 42
  AND a.account_no = 'ACC-0001';

Ý tưởng: nếu kẻ tấn công đổi account_no sang tài khoản của người khác, mệnh đề a.customer_id = 42 khiến truy vấn trả về 0 dòng — server không có gì để lộ. Điều kiện chủ sở hữu (customer_id lấy từ server-side session, không phải từ input) chính là hàng rào object-level. Nếu bỏ mệnh đề đó đi và chỉ lọc theo account_no, ta có ngay một lỗ hổng IDOR.

Trong ứng dụng thực, 42 được thay bằng tham số ràng buộc (bind parameter) lấy từ danh tính đã xác thực — tuyệt đối không lấy từ tham số URL mà người dùng gửi lên.

A02 — Cryptographic Failures

Nhóm A02 (tên cũ 2017 là "Sensitive Data Exposure") gom các lỗi liên quan tới mật mã: dữ liệu nhạy cảm không được mã hoá, mã hoá sai, hoặc quản lý khoá kém. Với ngân hàng, đây là ranh giới giữa "dữ liệu thẻ được bảo vệ" và "một sự cố phải báo cáo cơ quan quản lý".

Chia đơn giản, có hai trạng thái dữ liệu cần bảo vệ:

Mã hoá at-rest (dữ liệu nằm yên)

Dữ liệu nhạy cảm khi lưu trữ — trong database, file, backup, log — phải được mã hoá. Đối tượng cần quan tâm hàng đầu: PII (thông tin định danh cá nhân — CMND/CCCD, số điện thoại), số thẻ (PAN), CVV (thực tế không được lưu CVV theo PCI-DSS), số tài khoản, số dư.

Việc này gắn chặt với mã hoá & masking trong Data Governance: mã hoá cột nhạy cảm, tokenization thay số thẻ bằng token vô nghĩa, masking khi hiển thị. Nguyên tắc quan trọng nhất về at-rest lại rất "phi kỹ thuật": dữ liệu nhạy cảm không cần thì đừng lưu. Không lưu = không thể rò rỉ. Đừng thu thập và cất giữ số thẻ, ngày sinh, địa chỉ nếu nghiệp vụ không thật sự cần.

Mã hoá in-transit (dữ liệu di chuyển)

Mọi dữ liệu truyền qua mạng phải đi qua TLS (Transport Layer Security — tiền thân là SSL). Không có ngoại lệ cho "kênh nội bộ": traffic giữa service-to-service trong datacenter cũng cần mã hoá, vì tấn công có thể đến từ bên trong. Vài điểm cấu hình đúng:

  • TLS bắt buộc — không cho phép fallback về HTTP. Chuyển hướng cứng HTTP→HTTPS.
  • HSTS (HTTP Strict Transport Security) — header bảo trình duyệt chỉ dùng HTTPS với domain này, chống tấn công hạ cấp (SSL stripping).
  • Phiên bản & cipher đúng — chỉ bật TLS 1.2/1.3, tắt các phiên bản/cipher lỗi thời (SSLv3, TLS 1.0/1.1, RC4).
  • Chứng chỉ (cert) hợp lệ — cert do CA tin cậy cấp, còn hạn, đúng domain; client phải verify cert (không tắt kiểm tra chứng chỉ để "cho tiện").

Chọn thuật toán đúng

Nguyên tắc vàng của mật mã ứng dụng: đừng tự chế (don't roll your own crypto). Dùng thư viện chuẩn, thuật toán được cộng đồng kiểm chứng:

  • Mã hoá đối xứng — dùng AES-GCM (hoặc AES với chế độ xác thực tương đương). GCM vừa mã hoá vừa đảm bảo toàn vẹn (authenticated encryption), chống việc kẻ tấn công sửa ciphertext mà không bị phát hiện.
  • Băm mật khẩu — dùng thuật toán băm chuyên cho password: bcrypt / scrypt / Argon2, kèm salt. Đây là chủ đề đã bàn ở bài Injection & Authentication — nhắc lại: không bao giờ dùng MD5/SHA-1 trần cho mật khẩu.
  • Băm toàn vẹn / chữ ký — dùng SHA-256 trở lên; chữ ký số dùng RSA/ECDSA với độ dài khoá đủ.

Quản lý khoá

Mã hoá mạnh đến đâu cũng vô nghĩa nếu khoá bị lộ. Vài quy tắc:

  • Không hardcode khoá trong mã nguồn, config commit vào git, hay biến môi trường phơi bày. (Chủ đề secrets management sẽ đào sâu ở bài tiếp theo.)
  • Dùng KMS (Key Management Service) hoặc vault để lưu, cấp phát và kiểm soát truy cập khoá.
  • Rotation — xoay khoá định kỳ, và có quy trình xoay khẩn khi nghi ngờ rò rỉ. Xem thêm ở gov-05.
  • Phân tách khoá theo mục đích/môi trường; giới hạn ai/service nào được dùng khoá nào (chính là access control áp lên chính khoá).

Những lỗi mật mã thường gặp

LỗiVấn đề
Dữ liệu nhạy cảm truyền/lưu không mã hoáRò rỉ trực tiếp khi bị sniff mạng hoặc lộ DB/backup
Dùng thuật toán yếu/lỗi thời: MD5, SHA-1, DES, 3DES, RC4Đã bị phá; băm/mã hoá có thể bị đảo ngược hoặc va chạm
IV/nonce sai — tái sử dụng nonce, hoặc dùng giá trị cố địnhVới GCM, tái dùng nonce trên cùng khoá làm sập toàn bộ bảo mật
Dùng chế độ ECB cho block cipherECB lộ mẫu (pattern) của plaintext — hai block giống nhau cho ciphertext giống nhau
Tắt verify chứng chỉ TLSMở đường cho tấn công man-in-the-middle
Lưu mật khẩu bằng băm không có saltDễ bị tấn công bằng rainbow table

Điểm cần nhớ: chọn đúng thuật toán là chưa đủ. AES-GCM dùng sai nonce, hay bcrypt nhưng khoá secret lộ trên GitHub, vẫn thủng như thường. Mật mã là một chuỗi, và nó chỉ mạnh bằng mắt xích yếu nhất.

Use case thực tế

Bối cảnh. Đội dữ liệu NCB xây một cổng self-service để khách hàng doanh nghiệp xem sao kê tài khoản qua API. Trước khi go-live, đội security review phát hiện và xử lý hai vấn đề — mỗi vấn đề tương ứng đúng một nhóm trong bài này.

Vấn đề 1 — IDOR (A01). Endpoint GET /api/v1/accounts/{account_no}/statements chỉ kiểm tra JWT hợp lệ (authentication) rồi trả sao kê theo account_no. Pentester đăng nhập bằng tài khoản test của khách hàng X, rồi thử tăng/đổi account_no sang dải của khách hàng khác — và đọc được sao kê 37 tài khoản không thuộc về mình chỉ trong 4 phút bằng một script quét. Nguyên nhân: thiếu object-level check.

Khắc phục: thêm authorization filter vào tầng truy vấn — mọi truy vấn account đều kèm điều kiện customer_id = <lấy từ JWT đã verify>, đúng như câu SQL minh hoạ ở trên. Sau vá, cùng script quét trả về 403/404 cho toàn bộ tài khoản không sở hữu. Đồng thời chuyển từ ID tuần tự sang định danh khó đoán để giảm bề mặt dò quét (biện pháp bổ trợ, không thay thế object-level check).

Vấn đề 2 — Cryptographic failure (A02). File CSV sao kê xuất ra được đẩy sang một internal service qua HTTP thuần trong VPC, "vì nội bộ nên coi là an toàn". Ngoài ra số tài khoản trong bảng staging được lưu plaintext.

Khắc phục: (1) bật TLS 1.3 + verify cert cho toàn bộ traffic service-to-service, kể cả nội bộ; (2) mã hoá cột số tài khoản bằng AES-GCM với khoá lấy từ KMS, bật rotation 90 ngày; (3) rà soát và xoá các cột PII mà báo cáo sao kê không thực sự cần (áp nguyên tắc "không cần thì đừng lưu"). Kết quả: giảm bề mặt rò rỉ, và sự cố "lộ CSV nội bộ" nếu xảy ra cũng chỉ lộ ciphertext.

Ghi nhớ

  • A01 và A02 là hai rủi ro top OWASP 2021 — bị lạm dụng nhiều nhất, hậu quả nặng nhất với hệ thống ngân hàng.
  • Authentication ≠ Authorization. Xác thực đúng người không có nghĩa cho phép làm đúng việc. Access control là phần authorization.
  • IDOR/BOLA là lỗi access control phổ biến nhất: đổi ID (/account/12345/account/12346) đọc dữ liệu người khác vì server quên kiểm tra chủ sở hữu.
  • Bốn nguyên tắc access control: deny by default, kiểm tra quyền ở server cho mọi request, kiểm tra theo bản ghi (object-level), và không dựa vào ẩn giấu.
  • Object-level check hiệu quả nhất khi authorization filter đi thẳng vào truy vấn: luôn lọc theo chủ sở hữu lấy từ session server-side, không bao giờ tin ID từ client.
  • Không tin client — UI ẩn nút không bằng chặn endpoint; mọi tham số client gửi lên đều có thể bị giả mạo.
  • A02 bảo vệ dữ liệu ở hai trạng thái: at-rest (mã hoá lưu trữ, tokenization) và in-transit (TLS bắt buộc, HSTS, cert hợp lệ, TLS 1.2/1.3).
  • Đừng tự chế crypto. Dùng chuẩn: AES-GCM cho mã hoá đối xứng, bcrypt/scrypt/Argon2 cho mật khẩu; tránh MD5/SHA-1/DES, ECB, và tái dùng nonce.
  • Quản lý khoá quyết định tất cả: không hardcode, dùng KMS, xoay khoá định kỳ. Mã hoá mạnh nhưng lộ khoá vẫn thủng.
  • Nguyên tắc tối giản đắt giá nhất: dữ liệu nhạy cảm không cần thì đừng lưu — không lưu thì không thể rò rỉ.

Bài viết liên quan

Continuous Integration/Delivery/Deployment, cấu trúc pipeline, ví dụ GitHub Actions và chiến lược release.

13 thg 7, 2026 4

Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.

13 thg 7, 2026 3

Vì sao cần orchestration; Pod, Deployment, Service, Ingress; scaling, self-healing và cấu hình.

13 thg 7, 2026 3

Bản chất DevOps: phá bỏ rào cản Dev–Ops, vòng lặp vô tận, CALMS, và vì sao tự động hoá.

13 thg 7, 2026 3