Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là Technical PM của một ví điện tử đang chuẩn bị ra mắt tính năng "chuyển tiền cho bạn bè qua số điện thoại". Đội kỹ thuật đã code xong, QA đã test các luồng happy path, mọi thứ trông hoàn hảo. Ba tuần sau khi ra mắt, một nhóm kẻ gian phát hiện chúng có thể nhập số điện thoại bất kỳ, hệ thống tự động hiển thị tên đầy đủ của chủ tài khoản trước khi bấm "Chuyển" — và thế là họ thu thập được tên thật của hàng triệu người dùng từ chính danh bạ. Không một dòng code nào bị "hack", không có lỗ hổng SQL Injection nào cả. Hệ thống làm đúng những gì nó được thiết kế. Vấn đề là: chẳng ai từng ngồi xuống hỏi câu "kẻ xấu có thể lợi dụng tính năng này như thế nào?".
Đó chính là lý do Threat Modeling tồn tại. Là một Technical PM, bạn không cần biết viết exploit hay cấu hình firewall, nhưng bạn là người duy nhất trong phòng có cái nhìn tổng thể về sản phẩm: ai dùng nó, dữ liệu nào chảy qua đâu, và điều gì có giá trị để bị tấn công. Security không phải là việc "để cuối cùng làm" hay "việc của đội security" — nó bắt đầu từ giai đoạn thiết kế, và bạn là người cầm trịch giai đoạn đó. Bài học này dạy bạn cách suy nghĩ như một kẻ tấn công một cách có hệ thống, để bạn phát hiện rủi ro khi nó còn nằm trên giấy — lúc mà việc sửa chỉ tốn vài giờ thảo luận, thay vì vài tuần xử lý sự cố và một bài báo trên VnExpress về vụ rò rỉ dữ liệu.
Khái niệm cốt lõi
Threat Modeling là gì
Threat Modeling là một quy trình có cấu trúc để xác định, đánh giá và xử lý các mối đe dọa (threat) đối với một hệ thống — trước khi xây dựng nó. Nói đơn giản, đó là việc cả nhóm ngồi lại và trả lời bốn câu hỏi nền tảng do chuyên gia Adam Shostack đề xuất:
- Chúng ta đang xây cái gì? (Vẽ ra hệ thống, luồng dữ liệu, ranh giới tin cậy)
- Cái gì có thể đi sai? (Liệt kê các mối đe dọa)
- Chúng ta sẽ làm gì với những rủi ro đó? (Quyết định biện pháp đối phó)
- Chúng ta đã làm tốt chưa? (Kiểm tra lại, lặp lại)
Các thành phần của một mô hình mối đe dọa
Để mô hình hóa mối đe dọa, bạn cần ba khái niệm:
- Asset (Tài sản): Thứ có giá trị cần bảo vệ. Có thể là dữ liệu (thông tin CCCD của người dùng, lịch sử giao dịch), chức năng (khả năng phê duyệt khoản vay), hoặc danh tiếng (uy tín thương hiệu).
- Attacker / Threat actor (Kẻ tấn công): Ai có thể muốn gây hại? Một hacker bên ngoài, một nhân viên nội bộ bất mãn, một đối thủ cạnh tranh, hay đơn giản là một người dùng tò mò nghịch URL.
- Attack vector (Vector tấn công): Con đường mà kẻ tấn công đi qua để chạm tới tài sản. Ví dụ: một ô input không kiểm tra, một API endpoint quên kiểm tra quyền, một file upload không giới hạn loại.
Trust Boundary — ranh giới tin cậy
Đây là khái niệm quan trọng nhất mà PM hay bỏ qua. Trust boundary là đường ranh giới mà tại đó mức độ tin cậy thay đổi — nơi dữ liệu chuyển từ vùng "không tin cậy" sang vùng "tin cậy". Mỗi lần dữ liệu vượt qua một ranh giới như vậy, đó là nơi nguy hiểm nhất và cần kiểm soát.
Ví dụ điển hình: ranh giới giữa trình duyệt của người dùng (không tin cậy — vì người dùng có thể chỉnh sửa request) và server backend của bạn (tin cậy). Mọi dữ liệu vượt qua ranh giới này phải được kiểm tra (validate), vì bạn không bao giờ được tin tưởng dữ liệu đến từ phía client. Một ví dụ khác: ranh giới giữa hệ thống của bạn và một dịch vụ bên thứ ba (cổng thanh toán VNPay, dịch vụ gửi SMS).
STRIDE Framework
STRIDE là khung phân loại mối đe dọa phổ biến nhất, do Microsoft phát triển. Nó là một công cụ ghi nhớ (mnemonic) giúp bạn không bỏ sót loại đe dọa nào. Mỗi chữ cái tương ứng với một loại đe dọa và mỗi loại "vi phạm" một thuộc tính bảo mật:
- S — Spoofing (Giả mạo danh tính): Kẻ tấn công giả vờ là người khác. Ví dụ: đăng nhập bằng tài khoản người khác, giả mạo email người gửi. Vi phạm thuộc tính Authentication (xác thực).
- T — Tampering (Sửa đổi trái phép): Thay đổi dữ liệu hoặc code mà không được phép. Ví dụ: sửa giá trị
amount=100000thànhamount=1trong request, can thiệp vào dữ liệu trong database. Vi phạm Integrity (toàn vẹn).
- R — Repudiation (Chối bỏ): Người dùng thực hiện một hành động rồi phủ nhận đã làm, và hệ thống không có bằng chứng. Ví dụ: "Tôi không hề chuyển khoản đó" trong khi hệ thống không có log đủ tin cậy. Vi phạm Non-repudiation (tính không thể chối bỏ).
- I — Information Disclosure (Lộ thông tin): Dữ liệu nhạy cảm bị rò rỉ cho người không được phép. Ví dụ vụ ví điện tử ở đầu bài chính là loại này. Vi phạm Confidentiality (bảo mật).
- D — Denial of Service (Từ chối dịch vụ): Làm hệ thống quá tải hoặc không khả dụng. Ví dụ: gửi hàng triệu request để làm sập server, hoặc upload file khổng lồ làm đầy ổ đĩa. Vi phạm Availability (tính khả dụng).
- E — Elevation of Privilege (Leo thang đặc quyền): Người dùng thường giành được quyền của admin. Ví dụ: một user bình thường gọi được API
/admin/delete-user. Vi phạm Authorization (phân quyền).
Đánh giá mức độ ưu tiên với DREAD (hoặc đơn giản hơn)
Không phải mối đe dọa nào cũng đáng xử lý ngay. Bạn cần ưu tiên. Một cách đơn giản mà PM hay dùng là ma trận Khả năng xảy ra (Likelihood) × Mức độ tác động (Impact), giống như cách bạn ưu tiên backlog. Một mối đe dọa "dễ khai thác + thiệt hại nghiêm trọng" (ví dụ leo thang đặc quyền chiếm tài khoản admin) phải được xử lý trước một mối đe dọa "khó khai thác + thiệt hại nhỏ". Đừng cố vá mọi thứ — đó là cách nhanh nhất để làm chậm cả roadmap mà chẳng giảm rủi ro thực sự.
Tình huống thực tế
Tình huống 1: Tính năng "Mời bạn nhận 50.000đ" của một app gọi xe
Một startup gọi xe tại TP.HCM ra mắt chương trình giới thiệu: mỗi khi bạn mời một người bạn mới đăng ký và họ hoàn thành chuyến đi đầu tiên, cả hai nhận 50.000đ vào ví. Đội PM rất hào hứng vì đây là đòn bẩy tăng trưởng.
Diễn giải: Nếu nhóm chạy Threat Modeling, áp STRIDE vào luồng này, chữ T (Tampering) và S (Spoofing) sẽ lập tức bật đèn đỏ. Câu hỏi "cái gì có thể đi sai?" cho ra: kẻ gian có thể tạo hàng loạt tài khoản giả bằng số điện thoại ảo (SIM rác, dịch vụ thuê số OTP online) để tự mời chính mình. Mỗi cặp tài khoản = 100.000đ. Asset bị tấn công ở đây là ngân sách marketing, attacker là gian lận khuyến mãi (promo abuse), attack vector là đăng ký tài khoản không giới hạn + không có cơ chế phát hiện thiết bị/số trùng lặp.
Thực tế, nhiều chương trình khuyến mãi ở Việt Nam đã bị "cày" sạch ngân sách trong vài ngày theo đúng kịch bản này. Có những hội nhóm trên mạng chuyên trao đổi cách lách các chương trình giới thiệu.
Bài học rút ra: Threat Modeling không chỉ về "hacker phá hệ thống". Phần lớn rủi ro thực tế của sản phẩm Việt Nam là lạm dụng business logic — hệ thống hoạt động đúng kỹ thuật nhưng bị lợi dụng theo cách ngoài ý muốn. PM là người hiểu business logic rõ nhất, nên PM phải là người đặt câu hỏi này. Biện pháp đối phó có thể là: giới hạn theo thiết bị (device fingerprint), chỉ trả thưởng sau khi người được mời chi tiêu một số tiền nhất định, và đặt trần ngân sách.
Tình huống 2: API lấy thông tin đơn hàng của một sàn TMĐT
Một sàn thương mại điện tử có endpoint GET /api/orders/{orderId} để hiển thị chi tiết đơn hàng. Lập trình viên kiểm tra rằng người dùng đã đăng nhập trước khi trả về dữ liệu.
Diễn giải: Khi PM ngồi vẽ luồng dữ liệu và đánh dấu trust boundary giữa client và server, một câu hỏi STRIDE loại E (Elevation of Privilege) xuất hiện: "Hệ thống có kiểm tra rằng orderId này thuộc về người dùng đang đăng nhập không, hay chỉ kiểm tra họ đã đăng nhập?". Nếu chỉ kiểm tra đã đăng nhập, thì một người dùng A có thể đổi orderId trong URL từ 1001 (đơn của mình) thành 1002 để xem đơn của người khác — gồm tên, địa chỉ, số điện thoại, sản phẩm đã mua. Đây là lỗ hổng kinh điển tên là IDOR (Insecure Direct Object Reference), và nó đứng đầu danh sách OWASP. Asset là thông tin cá nhân khách hàng, vi phạm cả Information Disclosure lẫn Elevation of Privilege.
Bài học rút ra: Có sự khác biệt sống còn giữa Authentication (bạn là ai?) và Authorization (bạn được phép làm gì?). Rất nhiều lỗ hổng đến từ việc kiểm tra cái đầu mà quên cái thứ hai. Là PM, khi viết acceptance criteria cho bất kỳ API nào trả về dữ liệu theo ID, hãy luôn thêm dòng: "Hệ thống phải kiểm tra người dùng có quyền truy cập tài nguyên cụ thể này, không chỉ kiểm tra đã đăng nhập." Một dòng spec đó có thể cứu công ty khỏi một sự cố lớn.
Tình huống 3: Tính năng upload ảnh đại diện của một mạng xã hội nội bộ
Một công ty fintech Đông Nam Á xây dựng tính năng cho phép nhân viên upload ảnh đại diện trên hệ thống nội bộ. Đơn giản, vô hại — ai cũng nghĩ vậy.
Diễn giải: Chạy STRIDE qua điểm dữ liệu vượt trust boundary (file từ máy người dùng → server), nhóm phát hiện hai mối đe dọa. D (Denial of Service): nếu không giới hạn dung lượng, ai đó upload file 5GB liên tục làm đầy ổ đĩa và sập dịch vụ. E (Elevation of Privilege) / Tampering: nếu server không kiểm tra loại file thật sự mà chỉ tin vào phần mở rộng .jpg, kẻ tấn công có thể upload một file script độc hại đổi tên thành avatar.jpg, rồi nếu thư mục upload cho phép thực thi, họ có thể chạy code trên server — chiếm quyền điều khiển toàn bộ hệ thống. Một tính năng "vô hại" hóa ra là một trong những attack vector nguy hiểm nhất.
Bài học rút ra: Những tính năng trông tầm thường nhất thường là nơi rủi ro ẩn nấp, vì không ai nghĩ cần phải bảo vệ chúng. File upload, ô tìm kiếm, form liên hệ — đây là những điểm cần soi kỹ. Biện pháp: giới hạn dung lượng, kiểm tra loại file thực (magic bytes chứ không chỉ đuôi file), lưu file ở vùng không thể thực thi, và quét virus.
Hướng dẫn từng bước
Đây là quy trình bạn có thể chủ trì cho một tính năng cụ thể, gói gọn trong một buổi họp 60–90 phút với một vài kỹ sư:
- Xác định phạm vi. Chọn một tính năng cụ thể, không cố mô hình hóa cả hệ thống một lần. Ví dụ: "luồng đặt lại mật khẩu" hoặc "luồng rút tiền".
- Vẽ Data Flow Diagram (DFD). Phác thảo các thành phần: người dùng, ứng dụng, API, database, dịch vụ bên thứ ba. Vẽ các mũi tên thể hiện dữ liệu chảy giữa chúng. Quan trọng nhất: vẽ các đường trust boundary (thường là đường nét đứt) ở mỗi nơi mức độ tin cậy thay đổi. Một bản vẽ đơn giản trên giấy hoặc Miro là đủ.
- Liệt kê tài sản (asset). Dữ liệu nào, chức năng nào trong luồng này có giá trị? Đây là thứ kẻ tấn công nhắm tới.
- Áp STRIDE vào từng thành phần và từng luồng. Với mỗi mũi tên và mỗi hộp trong sơ đồ, hỏi qua 6 chữ cái S-T-R-I-D-E. Ghi lại mọi mối đe dọa nghĩ ra được, đừng tự kiểm duyệt. Khuyến khích cả nhóm "nghĩ như kẻ xấu".
- Ưu tiên hóa. Với mỗi mối đe dọa, chấm điểm Khả năng xảy ra × Tác động. Sắp xếp từ cao xuống thấp.
- Quyết định biện pháp đối phó. Với mỗi mối đe dọa ưu tiên cao, chọn một trong bốn cách xử lý: Mitigate (giảm thiểu — thêm kiểm soát), Eliminate (loại bỏ — bỏ tính năng/luồng gây rủi ro), Transfer (chuyển giao — ví dụ dùng cổng thanh toán đã đạt chuẩn thay vì tự lưu thẻ), hoặc Accept (chấp nhận — rủi ro quá nhỏ, ghi nhận và bỏ qua).
- Biến thành công việc cụ thể. Mỗi biện pháp giảm thiểu phải trở thành một acceptance criteria, một story trong backlog, hoặc một mục trong technical spec. Threat model không nằm trong file rồi quên đi — nó phải biến thành hành động.
- Lặp lại. Khi tính năng thay đổi đáng kể, chạy lại. Threat Modeling là việc làm liên tục, không phải một lần.
Lỗi thường gặp & mẹo
Lỗi: Coi đây là việc của riêng đội security. Thực tế, PM và kỹ sư hiểu sản phẩm sâu nhất nên họ tìm ra mối đe dọa thực tế nhất. Đội security có thể hỗ trợ và đào tạo, nhưng không nên là người duy nhất làm. Mẹo: đưa một mục "Security & Privacy Considerations" thành phần bắt buộc trong mọi technical spec.
Lỗi: Cố gắng đạt sự hoàn hảo. Một số nhóm dành cả tuần vẽ sơ đồ chi tiết đến từng request rồi kiệt sức và bỏ cuộc. Mẹo: bắt đầu nhỏ và "đủ tốt". Một buổi brainstorm 60 phút với sơ đồ vẽ tay còn giá trị hơn một tài liệu 50 trang không ai đọc.
Lỗi: Chỉ nghĩ tới hacker bên ngoài. Rất nhiều rủi ro đến từ nội bộ (nhân viên truy cập trái phép), từ business logic abuse (cày khuyến mãi), hoặc từ lỗi vô tình. Mẹo: với threat actor, luôn cân nhắc cả "người dùng tò mò", "nhân viên bất mãn", và "đối tác bên thứ ba".
Lỗi: Làm threat model xong rồi để đó. Nếu không biến thành story trong backlog, toàn bộ buổi họp chỉ là lý thuyết. Mẹo: kết thúc mỗi buổi bằng một danh sách action item có người chịu trách nhiệm.
Lỗi: Tin tưởng dữ liệu từ client. Validation ở phía trình duyệt chỉ để cải thiện trải nghiệm, không phải để bảo mật — vì kẻ tấn công có thể bỏ qua hoàn toàn giao diện và gọi thẳng API. Mẹo: mọi kiểm tra bảo mật phải lặp lại ở server.
Bài tập thực hành
Chọn một tính năng quen thuộc: "Đặt lại mật khẩu qua email" (người dùng nhập email → nhận link đặt lại → bấm link → nhập mật khẩu mới).
- Vẽ một Data Flow Diagram đơn giản cho luồng này, đánh dấu ít nhất hai trust boundary.
- Áp STRIDE vào luồng. Với mỗi chữ cái trong S-T-R-I-D-E, viết ít nhất một mối đe dọa cụ thể. (Gợi ý: link đặt lại có hết hạn không? Có thể đoán được không? Kẻ tấn công nhập email người khác để spam họ không? Có giới hạn số lần thử không?)
- Chọn ba mối đe dọa nghiêm trọng nhất, chấm điểm Khả năng × Tác động, và đề xuất một biện pháp đối phó cho mỗi cái.
- Viết ba acceptance criteria mà bạn sẽ thêm vào story để đảm bảo các biện pháp đó được triển khai.
Tóm tắt
Threat Modeling là quy trình có cấu trúc để trả lời câu hỏi "cái gì có thể đi sai?" ngay từ giai đoạn thiết kế, trước khi viết một dòng code. Cốt lõi gồm: xác định tài sản cần bảo vệ, hiểu kẻ tấn công và vector tấn công, vẽ luồng dữ liệu cùng các trust boundary, rồi dùng khung STRIDE (Spoofing, Tampering, Repudiation, Information Disclosure, Denial of Service, Elevation of Privilege) để liệt kê mối đe dọa một cách có hệ thống. Sau đó ưu tiên theo Khả năng × Tác động và biến mỗi biện pháp giảm thiểu thành công việc cụ thể trong backlog.
Là Technical PM, bạn không cần là chuyên gia bảo mật, nhưng bạn là người hiểu sản phẩm và business logic rõ nhất — nghĩa là bạn ở vị trí tốt nhất để phát hiện rủi ro, đặc biệt là loại "lạm dụng logic" mà công cụ tự động không bắt được. Hãy biến "Security & Privacy Considerations" thành một phần bắt buộc trong mọi spec, bắt đầu nhỏ và thực tế, và nhớ rằng phát hiện một rủi ro trên giấy luôn rẻ hơn xử lý nó sau khi đã thành sự cố hàng nghìn lần.