Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Test Authentication & Authorization

Software Testing Fundamentals Bài 57/60

Mở đầu — vì sao bài này quan trọng

Nếu bạn hỏi một QA có kinh nghiệm rằng "khu vực nào của sản phẩm mà nếu để lọt bug thì hậu quả nặng nhất?", câu trả lời gần như luôn là: Authentication và Authorization — tức là luồng đăng nhập và phân quyền. Lý do rất đơn giản: một bug ở nút "Thêm vào giỏ hàng" chỉ làm mất một đơn hàng, nhưng một bug ở luồng auth có thể làm lộ toàn bộ dữ liệu người dùng, cho phép người lạ xem đơn hàng của người khác, hoặc để một nhân viên cấp thấp thao tác được chức năng của admin.

Với thị trường Việt Nam, rủi ro này còn cụ thể hơn. Từ khi Nghị định 13/2023 về bảo vệ dữ liệu cá nhân có hiệu lực, việc để lộ thông tin người dùng không chỉ là sự cố kỹ thuật mà còn là vấn đề pháp lý. Các sản phẩm fintech, ví điện tử, sàn thương mại điện tử, ứng dụng y tế — tất cả đều bị soi rất kỹ ở khu vực auth. Là QA, bạn chính là hàng rào cuối cùng trước khi những lỗ hổng này ra tới tay người dùng thật.

Bài này tập trung riêng vào cách kiểm thử luồng xác thực (AuthN) và phân quyền (AuthZ) — hai khái niệm dễ bị nhầm lẫn nhưng đòi hỏi bộ test case rất khác nhau. Chúng ta sẽ không bàn sâu về OWASP Top 10 (bài 17) hay Security Testing tổng quát; ở đây bạn học cách nghĩ như một QA khi ngồi trước một màn hình login và một hệ thống nhiều vai trò.

Khái niệm cốt lõi

Authentication (AuthN) — "Bạn là ai?"

Authentication là quá trình hệ thống xác minh danh tính của người dùng. Khi bạn nhập email và mật khẩu, hệ thống hỏi: "Người này có đúng là người mà họ tự nhận không?". AuthN trả lời câu hỏi "Bạn là ai?".

Các yếu tố xác thực (authentication factors) thường gặp:

  • Something you know — thứ bạn biết: mật khẩu, mã PIN.
  • Something you have — thứ bạn có: điện thoại nhận OTP, thẻ token, ứng dụng Authenticator.
  • Something you are — thứ thuộc về bạn: vân tay, khuôn mặt (Face ID).
Khi hệ thống kết hợp từ hai yếu tố trở lên, ta có MFA (Multi-Factor Authentication) hoặc 2FA (Two-Factor Authentication). Ví dụ: nhập mật khẩu (know) rồi nhập OTP gửi qua SMS (have).

Authorization (AuthZ) — "Bạn được làm gì?"

Authorization xảy ra sau khi đã xác thực xong. Hệ thống đã biết bạn là ai, giờ nó quyết định bạn được phép làm gì. AuthZ trả lời câu hỏi "Bạn được phép truy cập tài nguyên nào, thao tác nào?".

Mô hình phổ biến nhất là RBAC (Role-Based Access Control) — phân quyền theo vai trò. Ví dụ trong một hệ thống quản lý bán hàng:

  • Admin: xem/sửa/xóa mọi thứ, quản lý người dùng.
  • Manager: xem báo cáo, duyệt đơn, nhưng không xóa được tài khoản.
  • Staff: chỉ tạo và xem đơn hàng của chính mình.
  • Guest/Khách: chỉ xem trang public.
Điểm mấu chốt mà QA phải khắc cốt ghi tâm: AuthN đúng không có nghĩa là AuthZ đúng. Một người đăng nhập hợp lệ (AuthN pass) vẫn có thể lợi dụng lỗ hổng để truy cập dữ liệu không thuộc quyền của mình (AuthZ fail). Đây chính là loại bug nguy hiểm và bị bỏ sót nhiều nhất.

Hai lỗ hổng phân quyền kinh điển cần nhớ

  • Broken Object Level Authorization (thường gọi IDOR — Insecure Direct Object Reference): đổi ID trên URL hoặc trong request để xem/sửa dữ liệu người khác. Ví dụ: GET /api/orders/1001 là đơn của bạn, đổi thành /api/orders/1002 lại xem được đơn người khác.
  • Broken Function Level Authorization (leo thang quyền — privilege escalation): một tài khoản staff gọi thẳng API POST /api/admin/delete-user mà server không kiểm tra vai trò, dẫn tới staff làm được việc của admin.

Session và Token — nền tảng của cả hai

Sau khi đăng nhập, hệ thống cấp cho bạn một "vé vào cửa": session cookie hoặc token (thường là JWT). Mọi request sau đó mang theo vé này để hệ thống biết bạn là ai mà không cần đăng nhập lại. QA phải kiểm tra vòng đời của vé này: nó có hết hạn không, có bị hủy khi logout không, có bị đánh cắp và tái sử dụng được không.

Tình huống thực tế

Tình huống 1 — Lỗ hổng IDOR ở ví điện tử (bối cảnh Việt Nam)

Một đội QA làm cho một ví điện tử nội địa (giả định tên "PayGo") đang kiểm thử tính năng "Xem lịch sử giao dịch". Chức năng hoạt động tốt: người dùng A đăng nhập, thấy đúng các giao dịch của mình. Tester ban đầu định pass case này.

Nhưng một QA cẩn thận mở DevTools của trình duyệt, để ý request khi bấm vào một giao dịch: GET /api/v1/transactions/48213. Cô đổi số cuối thành 48214 và gửi lại bằng chính token đăng nhập của mình. Kết quả: server trả về giao dịch của một người dùng hoàn toàn khác — kèm số tiền, số điện thoại và một phần thông tin ngân hàng.

Diễn giải: AuthN đã pass (cô đăng nhập hợp lệ), nhưng AuthZ hoàn toàn hỏng — server không kiểm tra "giao dịch 48214 có thuộc về người đang gọi không". Với ví điện tử, đây là lỗ hổng Severity Critical, vừa vi phạm Nghị định 13 vừa có nguy cơ bị khai thác hàng loạt bằng script (duyệt tuần tự từ ID 1 đến ID n).

Bài học: Đừng bao giờ dừng ở "màn hình hiển thị đúng". Với mọi tài nguyên có ID, hãy thử truy cập chéo bằng ID của người khác. Test case AuthZ phải luôn có kịch bản "người dùng A cố lấy dữ liệu của người dùng B".

Tình huống 2 — Session không bị hủy sau khi đổi mật khẩu (một sàn TMĐT)

Một sàn thương mại điện tử khu vực Đông Nam Á phát hiện sự cố sau khi một tài khoản người bán bị hack. Chủ tài khoản làm đúng quy trình: đổi mật khẩu ngay. Nhưng kẻ tấn công vẫn tiếp tục đăng nhập được và rút tiền, vì session cũ của kẻ tấn công không bị hủy khi mật khẩu thay đổi. Hệ thống chỉ chặn lần đăng nhập mới bằng mật khẩu cũ, còn phiên đang mở thì vẫn sống.

QA điều tra lại và phát hiện đội dev đã bỏ sót một test case quan trọng: "Sau khi đổi mật khẩu, tất cả các session/token khác của tài khoản phải bị vô hiệu hóa (force logout everywhere)".

Diễn giải: Đây là bug về vòng đời session — một khía cạnh AuthN mà QA rất hay quên. Việc đổi mật khẩu thường được coi là "tính năng bảo mật", nhưng nếu không đi kèm hủy phiên cũ thì nó chỉ tạo cảm giác an toàn giả.

Bài học: Với các sự kiện nhạy cảm — đổi mật khẩu, đăng xuất, khóa tài khoản, thu hồi quyền — QA phải kiểm tra rằng session cũ chết ngay lập tức, không chỉ chặn đăng nhập mới. Kỹ thuật kiểm thử: mở hai trình duyệt cùng một tài khoản, đổi mật khẩu ở trình duyệt A, rồi thử thao tác ở trình duyệt B — B phải bị đá ra.

Tình huống 3 — OTP có thể brute-force (một app ngân hàng số)

Một ngân hàng số cho phép xác thực giao dịch bằng OTP 6 số gửi qua SMS. Đội QA test luồng "nhập OTP đúng" và "nhập OTP sai" đều pass, tính năng trông ổn.

Một QA có tư duy bảo mật đặt câu hỏi: "Nếu tôi nhập sai OTP 10.000 lần thì sao?". Anh viết một script nhỏ thử tất cả các mã từ 000000 đến 999999. Kết quả: hệ thống không giới hạn số lần thử, không khóa sau nhiều lần sai, và OTP có hiệu lực tới 5 phút. Nghĩa là kẻ tấn công có thừa thời gian dò trúng mã 6 số bằng brute-force.

Diễn giải: OTP đúng/sai chỉ là bề nổi. Sức mạnh thật sự của OTP nằm ở rate limiting (giới hạn tần suất), số lần thử tối đa, và thời gian hết hạn ngắn. Thiếu ba yếu tố này, 2FA gần như vô nghĩa.

Bài học: Khi test bất kỳ cơ chế xác thực nào (mật khẩu, OTP, mã PIN), luôn thêm test case về chống dò tìm: khóa tạm sau X lần sai, CAPTCHA sau nhiều lần thất bại, thời gian hết hạn hợp lý.

Hướng dẫn từng bước

Dưới đây là quy trình bạn có thể áp dụng để xây bộ test case auth cho bất kỳ dự án nào.

Bước 1 — Vẽ bản đồ vai trò và tài nguyên (permission matrix). Trước khi viết test, lập một bảng ma trận: hàng là các vai trò (Admin, Manager, Staff, Guest), cột là các chức năng/tài nguyên (xem đơn, sửa đơn, xóa user, xem báo cáo…). Mỗi ô đánh dấu Được phép / Không được phép. Bảng này là "chân lý" để bạn kiểm tra AuthZ. Không có bảng này thì test phân quyền chỉ là đoán mò.

Bước 2 — Viết test case cho luồng AuthN cơ bản (đăng nhập).

  • Đăng nhập thành công với credential đúng → vào đúng trang, đúng vai trò.
  • Đăng nhập thất bại với mật khẩu sai → báo lỗi, không tiết lộ email tồn tại hay không (thông báo chung chung kiểu "Email hoặc mật khẩu không đúng").
  • Email đúng, mật khẩu sai / email không tồn tại → cùng một thông báo lỗi (tránh username enumeration).
  • Bỏ trống các trường, nhập ký tự đặc biệt, khoảng trắng đầu/cuối, email hoa/thường.
  • Khóa tài khoản tạm thời sau N lần đăng nhập sai.
Bước 3 — Test đăng ký, đặt lại mật khẩu, và OTP/2FA.
  • Reset password: link/OTP chỉ dùng một lần, hết hạn sau thời gian ngắn, không đoán được.
  • OTP: đúng, sai, hết hạn, dùng lại OTP cũ, giới hạn số lần thử, rate limit.
  • Chính sách mật khẩu: độ dài tối thiểu, chặn mật khẩu quá yếu.
Bước 4 — Test quản lý session/token.
  • Session hết hạn sau thời gian không hoạt động (idle timeout).
  • Logout hủy session; token sau logout không dùng lại được.
  • Đổi mật khẩu → hủy tất cả session khác.
  • Cookie có cờ HttpOnly, Secure, SameSite (kiểm tra qua DevTools).
  • Token JWT: thử sửa payload, thử token hết hạn, thử token của người khác.
Bước 5 — Test AuthZ dựa trên ma trận (phần quan trọng nhất). Với mỗi ô "Không được phép" trong ma trận, tạo một test case cố gắng vi phạm:
  • Vertical privilege escalation: đăng nhập bằng Staff, gọi thẳng URL/API của Admin.
  • Horizontal privilege escalation (IDOR): đăng nhập bằng người dùng A, đổi ID trong URL/request để truy cập dữ liệu người dùng B.
  • Truy cập không đăng nhập: gọi API nội bộ khi chưa có token → phải trả 401.
  • Ẩn nút không đủ: kiểm tra rằng server chặn ở tầng backend, chứ không chỉ ẩn nút ở giao diện.
Bước 6 — Test ở tầng API, không chỉ tầng UI. Rất nhiều lỗ hổng AuthZ chỉ lộ ra khi bạn bỏ qua giao diện và gọi thẳng API bằng Postman hoặc curl. UI ẩn nút "Xóa" không có nghĩa là API DELETE /users/5 bị chặn. Luôn kiểm tra ở tầng request thật.

Bước 7 — Ghi nhận và phân loại bug đúng mức. Lỗi auth thường có Severity cao (Critical/High) vì ảnh hưởng dữ liệu và bảo mật. Khi báo bug, ghi rõ vai trò tài khoản, các bước tái hiện gồm cả request thô (URL, header, body), và dữ liệu bị rò rỉ để dev hiểu mức nghiêm trọng.

Lỗi thường gặp & mẹo

Chỉ test "happy path". Tester mới thường chỉ kiểm tra đăng nhập đúng và đăng nhập sai mật khẩu rồi coi như xong. Auth testing thực sự nằm ở các kịch bản tấn công: truy cập chéo, leo thang quyền, dò OTP. Hãy dành ít nhất 60% công sức cho các case "negative".

Nhầm lẫn "ẩn ở UI" với "chặn ở backend". Đây là sai lầm phổ biến nhất. Việc frontend ẩn nút hay ẩn menu chỉ là trải nghiệm, không phải bảo mật. Luôn xác minh backend từ chối request trái phép bằng mã 403.

Bỏ quên vòng đời session. Nhiều QA test đăng nhập rất kỹ nhưng quên logout, idle timeout, và hủy phiên khi đổi mật khẩu. Đây là nơi bug hay ẩn nấp.

Thông báo lỗi tiết lộ quá nhiều. "Email này không tồn tại" giúp kẻ tấn công biết email nào có thật để nhắm mục tiêu. Mẹo: yêu cầu thông báo đăng nhập/reset luôn chung chung.

Quên test đồng thời nhiều thiết bị. Người Việt hay đăng nhập cùng lúc trên điện thoại và máy tính. Hãy kiểm tra hành vi đa phiên: đăng nhập thiết bị mới có đá thiết bị cũ không (tùy yêu cầu), và mỗi phiên có độc lập không.

Mẹo dùng công cụ: Thành thạo DevTools (tab Network, Application/Storage để xem cookie) và một công cụ gọi API như Postman là đủ để phát hiện phần lớn lỗ hổng auth mà không cần biết hack chuyên sâu. Với JWT, dùng trang jwt.io để giải mã và kiểm tra nội dung token.

Mẹo phối hợp với dev: Xin trước ma trận phân quyền chính thức từ BA/PO. Nếu tài liệu không có, đây đã là một "bug quy trình" cần nêu — vì không ai định nghĩa rõ ai được làm gì thì cả dev lẫn QA đều làm theo cảm tính.

Bài tập thực hành

  • Lập ma trận phân quyền: Cho một hệ thống LMS (quản lý học tập) có 4 vai trò: Admin, Giảng viên, Học viên, Khách. Hãy lập bảng ma trận với ít nhất 6 chức năng (tạo khóa học, ghi danh, chấm điểm, xem điểm người khác, xóa bình luận, xem báo cáo doanh thu). Đánh dấu quyền cho từng vai trò.
  • Viết 8 test case AuthN cho màn hình đăng nhập của một ví điện tử, trong đó ít nhất 3 case là negative liên quan tới bảo mật (khóa tài khoản, thông báo lỗi chung chung, rate limit).
  • Thiết kế kịch bản IDOR: Với endpoint giả định GET /api/students/{id}/grades, viết các bước tái hiện để kiểm tra xem một Học viên có thể xem điểm của Học viên khác hay không. Nêu rõ kết quả mong đợi (server trả 403) và cách bạn thực hiện bằng Postman.
  • Test vòng đời session: Viết test case cho tình huống "đổi mật khẩu phải hủy mọi phiên khác", mô tả cách dùng hai trình duyệt để tái hiện và kết quả mong đợi.
  • Phân loại Severity: Cho 3 bug — (a) nút "Xóa" hiển thị với Học viên nhưng bấm vào báo lỗi, (b) Học viên xem được điểm của bạn cùng lớp qua đổi ID, (c) OTP không giới hạn số lần thử. Xếp Severity cho từng bug và giải thích ngắn.

Tóm tắt

  • Authentication (AuthN) trả lời "Bạn là ai?" — xác minh danh tính qua mật khẩu, OTP, sinh trắc học. Authorization (AuthZ) trả lời "Bạn được làm gì?" — kiểm soát quyền truy cập, thường theo mô hình RBAC.
  • Nguyên tắc vàng: AuthN đúng không đảm bảo AuthZ đúng. Người đăng nhập hợp lệ vẫn có thể truy cập trái phép nếu server không kiểm tra quyền.
  • Hai lỗ hổng AuthZ kinh điển: IDOR (đổi ID để xem dữ liệu người khác) và leo thang quyền (dùng quyền thấp gọi chức năng cao).
  • Đừng chỉ test happy path. Sức mạnh của auth testing nằm ở các case negative: truy cập chéo, dò OTP, session không bị hủy.
  • Luôn test ở tầng API, không chỉ tầng UI — vì ẩn nút không phải là bảo mật.
  • Bắt đầu mọi việc bằng ma trận phân quyền, và luôn kiểm tra vòng đời session (logout, idle timeout, hủy phiên khi đổi mật khẩu).
Nắm vững bài này, bạn đã có tư duy bảo vệ được khu vực nhạy cảm nhất của bất kỳ sản phẩm nào — và đó là kỹ năng khiến một QA trở nên đáng giá trong mắt cả đội dev lẫn khách hàng.