Nhiều người nghe "kiểm tra bảo mật" là nghĩ ngay tới chuyện thuê một đội bên ngoài, tốn tiền và chờ lâu. Thực ra phần lớn lỗi mình tìm thấy trên app của chính mình đều lòi ra nhờ một danh sách kiểm làm đều đặn mỗi lần phát hành. Bài này là danh sách đó, viết để chủ shop hiểu cần hỏi dev điều gì, và để dev có sẵn cách tự thử từng mục.

OWASP Top 10 là gì, và vì sao là bản 2025
OWASP là tổ chức phi lợi nhuận về bảo mật web. Mỗi vài năm họ công bố mười nhóm lỗi phổ biến nhất, gọi là Top 10, và cả ngành lấy đó làm khung để rà soát. Trước khi viết bài mình đã mở trang chính thức: bản OWASP Top 10:2025 là bản hiện hành, thay cho bản 2021. Nhiều bài hướng dẫn trên mạng vẫn bám bản cũ. Những thay đổi đáng biết: mục chuỗi cung ứng phần mềm là mục mới (A03); mục xử lý tình huống ngoại lệ cũng mới hoàn toàn (A10); lỗi buộc máy chủ đi gọi địa chỉ do người dùng đưa (SSRF) không còn là mục riêng mà gộp vào A01; cấu hình sai nhảy từ hạng 5 lên hạng 2.
Chuẩn bị
Chỉ kiểm thứ của mình, hoặc thứ bạn có văn bản cho phép. Nên làm trên môi trường thử có dữ liệu giả, với hai tài khoản thử (gọi là A và B) có quyền khác nhau. Mọi phép thử dưới đây mình chạy trên một app mẫu viết riêng, cố ý có lỗi, chạy ở máy cục bộ.
Làm thế nào: đi qua mười mục
A01. Vào được chỗ không phải của mình (Broken Access Control)
Lỗi hay gặp nhất: nút "xem đơn hàng" bị ẩn trên giao diện, nhưng API vẫn trả dữ liệu cho ai gọi. Tự thử: đăng nhập tài khoản A, lấy mã đơn của B rồi gọi thẳng API; mở các trang quản trị khi chưa đăng nhập. Kết quả đúng là 403 hoặc 404, không bao giờ 200. Với tính năng "dán đường dẫn để app tải ảnh về" (nơi SSRF xuất hiện), chỉ cho tên miền trong danh sách trắng và chặn địa chỉ nội bộ.
A02. Cấu hình sai (Security Misconfiguration)
Chế độ debug còn bật, file /.env hay /.git tải về được, trang lỗi in cả đường dẫn máy chủ, tài khoản mặc định chưa đổi, thiếu các header bảo mật. Phần máy chủ web mình viết riêng ở Làm website chạy Nginx an toàn hơn, từng bước.
A03. Phụ thuộc có lỗ hổng (Software Supply Chain Failures)
App của bạn dựa trên hàng trăm gói của người khác. Hãy giữ file khoá phiên bản (lockfile) trong git, chạy npm audit hoặc composer audit đều đặn, bật xác thực hai lớp cho tài khoản kho gói, và cho CI chạy với quyền tối thiểu. Mình chạy npm audit và composer audit trên thư mục thử, không có lỗ hổng đã biết. pip-audit trong môi trường ảo thử thì báo lỗ hổng của chính pip bản cũ, đúng loại phát hiện bạn cần thấy ở mục này.
A04. Mã hoá sai cách (Cryptographic Failures)
Mật khẩu phải được băm bằng hàm chậm như Argon2id, scrypt hay bcrypt, không phải MD5 hay SHA-256 trần. Dữ liệu nhạy cảm cần mã hoá có tem xác thực, và khoá không nằm trong mã nguồn. Chi tiết ở Mã hoá dữ liệu nhạy cảm đúng cách với AES-GCM.
A05. Chèn mã (Injection)
Dữ liệu người dùng gõ vào bị ghép thẳng vào câu truy vấn database, nên một dấu nháy đơn cũng đổi nghĩa câu lệnh. Cách sửa là dùng tham số hoá. Mình chạy trên SQLite trong bộ nhớ: ghép chuỗi với x' OR '1'='1 trả về cả hai người dùng, còn tham số hoá trả về rỗng.
user_input = "x' OR '1'='1"
db.execute(f"select name from users where name = '{user_input}'") # nối chuỗi: ra cả alice và bob
db.execute("select name from users where name = ?", (user_input,)) # tham số hoá: ra rỗng
A06. Thiết kế thiếu an toàn (Insecure Design)
Không phải lỗi code mà là thiếu cơ chế: mã giảm giá dùng lại vô hạn, đặt lại mật khẩu bằng mã dễ đoán, không giới hạn số lần thử. Cách kiểm là ngồi vẽ luồng nghiệp vụ và hỏi "nếu có người cố tình lạm dụng thì sao", rồi viết mỗi kịch bản thành một ca thử.
A07. Đăng nhập yếu (Authentication Failures)
Giới hạn số lần nhập sai, có xác thực hai lớp, đổi mã phiên sau khi đăng nhập, link đặt lại mật khẩu chỉ dùng một lần và có hạn. Nếu dùng JWT, xem Thẻ đăng nhập JWT dùng chung giữa Node.js, PHP, Python và C#.
A08. Dữ liệu và phần mềm bị đổi giữa đường (Software or Data Integrity Failures)
Script nạp từ CDN nên có thuộc tính integrity để trình duyệt tự so mã. Không giải mã dữ liệu lạ vào đối tượng trong code. Pipeline triển khai phải kiểm soát ai được sửa.
A09. Ghi log và cảnh báo (Security Logging and Alerting Failures)
Phải ghi lại đăng nhập thất bại, lỗi phân quyền, thay đổi quyền. Log không chứa mật khẩu hay token. Quan trọng nhất là có người nhận cảnh báo thật, vì một file log không ai mở thì như không có.
A10. Xử lý lỗi sai cách (Mishandling of Exceptional Conditions)
Khi có sự cố, hệ thống phải "đóng cửa" chứ không "mở cửa". Ví dụ kinh điển: đoạn kiểm quyền bị lỗi, câu except nuốt lỗi rồi cho qua.
def can_view(user, doc):
try:
return policy_service.check(user, doc)
except Exception:
logger.exception("policy check failed")
return False # lỗi thì từ chối, không phải return True
Cho bạn nào viết code: tự động hoá phần dễ
Một số mục máy kiểm được. Mình viết một script nhỏ (chỉ dùng thư viện chuẩn của Python) gọi vài địa chỉ rồi kiểm 13 điều: năm header bảo mật, cờ của cookie, file /.env, trang lỗi 500 có lộ đường dẫn không, và phép thử đọc chéo đơn hàng giữa hai tài khoản.
st, _, _ = get('/api/orders/2', {'Authorization': 'Bearer tok-alice'}) # đơn của bob
check(st in (403, 404), f'alice không đọc được đơn của bob (HTTP {st})')
Mình chạy lại hôm nay trên hai bản của app mẫu. Bản cố ý yếu: 13 mục FAIL. Bản đã sửa: 13 trên 13 PASS, và đơn của người khác trả 404. Với SSRF, mình viết hàm kiểm đường dẫn (phải là https, tên miền thuộc danh sách trắng, mọi địa chỉ IP phân giải ra đều là IP công khai) rồi thử năm trường hợp: chỉ đúng một trường hợp hợp lệ được qua, còn http, tên miền lạ, tên miền trỏ về 127.0.0.1 và địa chỉ metadata 169.254.169.254 đều bị chặn.
Lỗi hay gặp khi tự kiểm
Mình hay thấy người ta chỉ bấm thử trên giao diện mà không gọi thẳng API, hoặc chỉ thử bằng tài khoản quản trị nên không bao giờ thấy lỗi phân quyền. Có người làm một lần cho xong rồi để đó, trong khi danh sách này chỉ có giá trị khi nằm trong quy trình phát hành. Còn công cụ quét tự động thì không hiểu nghiệp vụ, nên gần như không bắt được A01 và A06. Nếu chỉ tin vào nó là bỏ sót những lỗi nặng nhất.

Nên làm gì tiếp
Chọn một buổi, làm hai tài khoản thử, chạy qua mười mục này trên môi trường thử, ghi lại cái nào chưa đạt rồi xếp việc sửa theo mức nghiêm trọng. Sau đó đưa danh sách vào quy trình mỗi lần phát hành. Khi app của bạn đã qua được các mục trên, bước tiếp theo là nhờ một người kiểm thử có phép theo quy trình bài bản, mình kể ở Thử xâm nhập website của chính mình (có giấy phép): các bước cơ bản.



