Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là QA của một sàn thương mại điện tử. Đội dev vừa đẩy tính năng "đăng nhập bằng số điện thoại". Bạn test đủ mọi kịch bản chức năng: nhập đúng OTP thì vào được, nhập sai OTP thì báo lỗi, để trống thì validate. Tất cả pass. Bạn ký duyệt release. Ba tuần sau, một bạn trẻ 19 tuổi ngồi ở quán cà phê phát hiện rằng chỉ cần thay số điện thoại trong URL từ user_id=1052 thành user_id=1053, hệ thống trả về toàn bộ thông tin đơn hàng, địa chỉ giao hàng và số điện thoại của người khác. Không cần hack gì cao siêu. Chỉ cần đổi một con số.
Đây chính là lý do bài học này tồn tại. Là QA, bạn không cần trở thành chuyên gia pentest (penetration testing — kiểm thử xâm nhập chuyên sâu), cũng không cần biết viết exploit hay dùng thành thạo Metasploit. Nhưng bạn bắt buộc phải hiểu những lỗ hổng bảo mật cơ bản nhất — cụ thể là danh sách OWASP Top 10 — để có thể phát hiện ra những lỗi "ngớ ngẩn nhưng chết người" ngay trong quá trình test chức năng hằng ngày. Rất nhiều vụ rò rỉ dữ liệu lớn ở Việt Nam và Đông Nam Á không đến từ hacker thiên tài, mà đến từ những lỗi cơ bản đáng lẽ QA có thể chặn được từ sớm.
Trong bài này, chúng ta sẽ nắm chắc OWASP Top 10 phiên bản 2021, hiểu cách mỗi lỗi biểu hiện ra sao dưới góc nhìn của người test, và quan trọng nhất: biết cách "đánh hơi" ra chúng mà không cần công cụ đắt tiền.
Khái niệm cốt lõi
Security Testing là gì và vai trò của QA
Security Testing (kiểm thử bảo mật) là hoạt động nhằm phát hiện các lỗ hổng khiến hệ thống có thể bị khai thác, đánh cắp dữ liệu, hoặc bị chiếm quyền điều khiển. Nó khác với functional testing ở chỗ: functional testing hỏi "hệ thống có làm đúng điều được yêu cầu không?", còn security testing hỏi "hệ thống có làm những điều không được phép không?".
Trong một tổ chức trưởng thành, có ba tầng phòng thủ:
- Developer viết code an toàn (secure coding).
- QA phát hiện lỗi bảo mật cơ bản trong quá trình test hằng ngày (đây là vai trò của bạn).
- Security Engineer / Pentester kiểm thử xâm nhập chuyên sâu và audit.
OWASP là gì
OWASP (Open Worldwide Application Security Project) là một tổ chức phi lợi nhuận toàn cầu chuyên về bảo mật ứng dụng. Cứ vài năm, họ tổng hợp dữ liệu từ hàng nghìn ứng dụng thực tế và công bố danh sách 10 loại rủi ro bảo mật nghiêm trọng nhất — gọi là OWASP Top 10. Đây là "kim chỉ nam" mà mọi QA nên thuộc lòng. Phiên bản mới nhất tính đến thời điểm học là OWASP Top 10 - 2021.
OWASP Top 10 (2021) dưới góc nhìn QA
| # | Tên | Ví dụ QA cần để ý |
|---|---|---|
| A01 | Broken Access Control (Kiểm soát truy cập bị hỏng) | User thường sửa URL/ID để xem dữ liệu người khác hoặc vào trang admin |
| A02 | Cryptographic Failures (Lỗi mã hóa) | Mật khẩu lưu dạng plaintext, trang login chạy HTTP thay vì HTTPS |
| A03 | Injection (Tiêm mã) | SQL Injection, nhập ' OR '1'='1 vào ô đăng nhập vẫn vào được |
| A04 | Insecure Design (Thiết kế thiếu an toàn) | Không giới hạn số lần nhập OTP, cho brute-force thoải mái |
| A05 | Security Misconfiguration (Cấu hình sai) | Để lộ trang debug, error message in ra cả stack trace và query |
| A06 | Vulnerable & Outdated Components (Thành phần lỗi thời) | Dùng thư viện/plugin cũ đã có lỗ hổng công bố |
| A07 | Identification & Authentication Failures (Lỗi xác thực) | Cho đặt mật khẩu 123456, session không hết hạn, không khóa sau nhiều lần sai |
| A08 | Software & Data Integrity Failures (Lỗi toàn vẹn) | Cập nhật phần mềm/CI-CD không kiểm tra chữ ký, dữ liệu bị chỉnh sửa mà không phát hiện |
| A09 | Security Logging & Monitoring Failures (Lỗi ghi log & giám sát) | Không log các lần đăng nhập thất bại, không cảnh báo khi có tấn công |
| A10 | Server-Side Request Forgery – SSRF (Giả mạo request phía server) | Cho nhập URL để server đi tải về, kẻ xấu lừa server gọi vào mạng nội bộ |
Ba "vũ khí" đơn giản mà QA nào cũng dùng được
Bạn không cần công cụ đắt tiền. Ba thứ này là đủ để bắt đầu:
- DevTools của trình duyệt (F12) — tab Network để xem request/response, tab Application để xem cookie và storage.
- Sửa URL trực tiếp — công cụ mạnh nhất để test Broken Access Control.
- Nhập dữ liệu "độc" vào các ô input — dấu nháy đơn
', thẻ<script>, để test Injection và XSS.
Tình huống thực tế
Tình huống 1: Sàn TMĐT và lỗi IDOR (Broken Access Control)
Bối cảnh: Một sàn thương mại điện tử tầm trung tại TP.HCM, gọi tắt là "ShopViet", có khoảng 200.000 người dùng. Đội QA test tính năng "xem chi tiết đơn hàng" tại URL:
https://shopviet.vn/order/detail?order_id=48219
Bạn Linh — QA của team — sau khi test chức năng xong, quyết định thử một thứ: cô đăng nhập bằng tài khoản của mình (order thật là 48219), rồi đổi tay thành order_id=48220. Kết quả: hệ thống hiển thị đầy đủ đơn hàng của một khách khác — tên, số điện thoại, địa chỉ nhà, danh sách sản phẩm đã mua. Không hề kiểm tra xem đơn hàng đó có thuộc về người đang đăng nhập hay không.
Đây là lỗi kinh điển tên IDOR (Insecure Direct Object Reference), thuộc nhóm A01 Broken Access Control. Server "tin tưởng" con số trên URL mà quên hỏi "người này có quyền xem đơn hàng này không?".
Diễn giải: Nếu lọt ra production, một kẻ xấu chỉ cần viết một script chạy vòng lặp từ order_id=1 đến order_id=48219 là "cào" được toàn bộ dữ liệu khách hàng — vi phạm nghiêm trọng Nghị định 13/2023 về bảo vệ dữ liệu cá nhân, có thể bị phạt và mất uy tín thương hiệu.
Bài học rút ra: Bất cứ khi nào bạn thấy một định danh (ID) xuất hiện trên URL hoặc trong request body, hãy thử đổi nó sang ID của tài khoản khác. Nếu xem được dữ liệu không thuộc về mình → đây là bug bảo mật nghiêm trọng nhất mà QA thường bắt được.
Tình huống 2: Ứng dụng nội bộ và SQL Injection
Bối cảnh: Một công ty logistics ở Bình Dương xây dựng hệ thống quản lý kho nội bộ. Ô đăng nhập nhận username và password. QA tên Đức, trong lúc test negative, thử nhập vào ô username chuỗi:
' OR '1'='1' --
Và mật khẩu để trống. Kết quả: anh đăng nhập thành công với quyền của user đầu tiên trong bảng database, thường là admin.
Chuyện gì đã xảy ra? Code phía server ghép chuỗi truy vấn kiểu:
SELECT * FROM users WHERE username = '[nhập vào]' AND password = '[nhập vào]'
Khi Đức nhập ' OR '1'='1' --, câu truy vấn biến thành ... WHERE username = '' OR '1'='1' --.... Điều kiện '1'='1' luôn đúng, còn -- biến phần còn lại thành comment (bị bỏ qua). Kết quả là mệnh đề kiểm tra mật khẩu bị vô hiệu hóa hoàn toàn.
Đây là SQL Injection, thuộc nhóm A03 Injection — một trong những lỗi nguy hiểm và cổ điển nhất.
Diễn giải: Với ứng dụng nội bộ, nhiều team chủ quan nghĩ "chỉ nhân viên mới truy cập được, không sao đâu". Nhưng một nhân viên bất mãn, hoặc một máy tính bị nhiễm mã độc trong mạng LAN, là đủ để khai thác lỗ hổng này và đánh cắp toàn bộ dữ liệu vận đơn.
Bài học rút ra: Luôn có một bộ "payload độc" để thử vào mọi ô input: ' OR '1'='1, admin'--, "; DROP TABLE users;--. Nếu hệ thống phản ứng bất thường (đăng nhập được, hiện lỗi SQL, trả về dữ liệu lạ) → báo bug ngay.
Tình huống 3: Fintech và error message lộ thông tin (Security Misconfiguration)
Bối cảnh: Một startup fintech ở Hà Nội làm ví điện tử. QA test luồng nạp tiền và cố tình gửi một số tiền âm (amount = -500000) qua DevTools. Server trả về lỗi HTTP 500 kèm nguyên một stack trace dài, trong đó lộ ra: tên database là wallet_prod, đường dẫn file server, phiên bản framework đang dùng (một bản cũ đã có lỗ hổng công bố), và cả một phần câu truy vấn SQL.
Đây là A05 Security Misconfiguration, kết hợp với dấu hiệu của A06 Vulnerable & Outdated Components.
Diễn giải: Bản thân việc lộ stack trace chưa đánh cắp tiền ngay, nhưng nó là "bản đồ kho báu" cho hacker — họ biết chính xác công nghệ, phiên bản, và cấu trúc để nhắm mục tiêu. Ở môi trường tài chính, đây là rủi ro không thể chấp nhận.
Bài học rút ra: Ở môi trường production, error message hiển thị cho người dùng phải chung chung ("Đã có lỗi xảy ra, vui lòng thử lại"). Chi tiết kỹ thuật chỉ được ghi vào log nội bộ. Khi test, hãy cố tình tạo lỗi (nhập sai kiểu dữ liệu, số âm, ký tự lạ) và quan sát xem hệ thống có "buột miệng" khai ra thông tin nhạy cảm không.
Hướng dẫn từng bước
Đây là quy trình một buổi "security smoke test" mà QA có thể chèn vào công việc hằng ngày, chỉ mất thêm 20-30 phút cho mỗi tính năng.
Bước 1 — Kiểm tra HTTPS và truyền dữ liệu nhạy cảm.
Mở DevTools tab Network. Đăng nhập và quan sát: mật khẩu có được gửi qua HTTPS không? Có thấy mật khẩu hiện dạng chữ thường (plaintext) trong response nào không? Toàn site có ép HTTPS không, hay gõ http:// vẫn vào được? (Liên quan A02.)
Bước 2 — Test Broken Access Control (A01). Đây là bước quan trọng nhất. Chuẩn bị hai tài khoản: một tài khoản thường (User A), một tài khoản khác (User B), và nếu có thì một tài khoản admin. Sau đó:
- Đăng nhập User A, sửa mọi ID trên URL sang dữ liệu của User B. Có xem được không?
- Copy một URL trang admin, dán vào trình duyệt khi đang đăng nhập bằng User thường. Có vào được không?
- Đăng xuất hoàn toàn, dán lại URL riêng tư đó. Còn xem được không?
' OR '1'='1, <script>alert(1)</script>, admin'--. Quan sát: có đăng nhập được không, có popup alert bật lên không (dấu hiệu XSS — Cross-Site Scripting), có báo lỗi SQL không.Bước 4 — Test Authentication (A07).
Thử đặt mật khẩu cực yếu (123, a). Có bị chặn không? Nhập sai mật khẩu 10-20 lần liên tiếp — hệ thống có khóa tài khoản hay bắt captcha không, hay cho brute-force thoải mái? Đăng nhập xong để yên vài giờ — session có tự hết hạn không?
Bước 5 — Test error handling & cấu hình (A05). Cố tình gửi dữ liệu sai kiểu qua DevTools (số âm, chuỗi vào ô số, thiếu trường bắt buộc). Quan sát response: có lộ stack trace, tên database, phiên bản framework không?
Bước 6 — Ghi nhận và báo cáo. Bug bảo mật cần được report riêng, đánh Severity cao (thường Critical/High), và mô tả rõ các bước tái hiện cùng hậu quả tiềm ẩn (ví dụ: "lộ toàn bộ dữ liệu 200.000 khách hàng"). Đừng đính kèm dữ liệu thật của khách trong bug — dùng ảnh che thông tin nhạy cảm.
Lỗi thường gặp & mẹo
Lỗi 1: Chỉ test "happy path" rồi ký duyệt. Rất nhiều QA test đúng luồng chức năng là xong. Nhưng lỗi bảo mật gần như luôn nằm ở luồng bất thường — cái mà người dùng "tử tế" không bao giờ làm. Mẹo: luôn đội "mũ kẻ xấu" ít nhất một lượt cho mỗi tính năng.
Lỗi 2: Nhầm giữa "ẩn nút" và "cấm truy cập". Dev thường ẩn nút "Xóa" khỏi giao diện của user thường và nghĩ thế là an toàn. Nhưng nếu server không kiểm tra quyền, kẻ xấu vẫn gọi thẳng API xóa được. Mẹo: luôn test ở tầng request (qua DevTools/Postman), không chỉ tầng giao diện.
Lỗi 3: Bỏ qua vì "chỉ là app nội bộ". Như tình huống 2 cho thấy, "nội bộ" không có nghĩa là an toàn. Mối đe dọa từ bên trong (insider threat) là có thật.
Lỗi 4: Test bảo mật trên môi trường production thật. Tuyệt đối không chạy payload như DROP TABLE hay brute-force trên hệ thống thật đang phục vụ khách. Luôn test trên staging/test environment.
Mẹo hay:
- Giữ sẵn một checklist OWASP Top 10 rút gọn trong tài liệu test của bạn, đối chiếu cho mọi tính năng quan trọng.
- Khi thấy bất kỳ ID, token, hoặc URL nhạy cảm nào, phản xạ đầu tiên là "thử đổi nó".
- Học cách dùng tab Network của DevTools thành thạo — 80% việc security test cơ bản của QA nằm ở đây.
- Phối hợp sớm với dev: nhiều lỗi bảo mật rẻ hơn rất nhiều nếu được phát hiện ở giai đoạn design, không phải sau khi code xong.
Bài tập thực hành
- Lập checklist cá nhân: Viết lại OWASP Top 10 (2021) bằng ngôn ngữ của riêng bạn, mỗi mục kèm một câu hỏi test mà bạn sẽ đặt ra khi kiểm thử. Ví dụ với A01: "Tôi có thể đổi ID để xem dữ liệu người khác không?".
- Săn IDOR: Trên một ứng dụng web bạn có quyền test (hoặc một trang demo hợp pháp như các "vulnerable app" công khai như OWASP Juice Shop), tạo hai tài khoản và thử đổi ID trên URL để truy cập dữ liệu chéo. Ghi lại kết quả.
- Thử payload Injection: Với OWASP Juice Shop (chạy local hoặc bản demo), thử đăng nhập bằng
' OR 1=1--và ghi lại điều gì xảy ra, giải thích tại sao.
- Viết một bug report bảo mật hoàn chỉnh cho tình huống IDOR ở Tình huống 1: gồm tiêu đề, Severity, các bước tái hiện, kết quả thực tế, kết quả mong đợi, và mô tả hậu quả tiềm ẩn. (Lưu ý: không dùng dữ liệu khách hàng thật.)
Tóm tắt
- QA không cần là pentester, nhưng bắt buộc hiểu OWASP Top 10 để bắt lỗi bảo mật cơ bản trước khi lên production.
- OWASP Top 10 (2021) gồm 10 nhóm rủi ro; QA gặp nhiều và test được nhất là A01 Broken Access Control, A02 Cryptographic Failures, A03 Injection, A05 Security Misconfiguration, A07 Authentication Failures.
- Ba "vũ khí" đơn giản: DevTools (F12), sửa URL/ID, nhập payload độc.
- Phản xạ vàng của QA bảo mật: thấy ID thì thử đổi, thấy ô input thì thử payload, thấy lỗi thì xem có lộ thông tin không.
- Luôn test ở tầng request chứ không chỉ tầng giao diện, và luôn đội "mũ kẻ xấu" cho mỗi tính năng.
- Bug bảo mật cần được ưu tiên Severity cao, mô tả rõ bước tái hiện và hậu quả, và không bao giờ test payload phá hoại trên production thật.