Menu
ESC

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

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

Đang tải...

Bài 42 — Security Review trong Spec

Viết PRD & Product Spec cho PM Bài 42/60

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

Hãy hình dung bạn vừa ship một tính năng mới: cho phép người dùng mời bạn bè bằng link, ai có link là vào xem được thông tin. Một tháng sau, một bạn dev cùng team phát hiện ra rằng chỉ cần đổi một con số trong URL — từ /invite/1024 thành /invite/1025 — là xem được lời mời, số điện thoại và email của một người hoàn toàn xa lạ. Không cần đăng nhập, không cần quyền gì cả. Đó chính là một lỗ hổng bảo mật, và nó lọt qua được vì trong PRD/Spec không ai đặt câu hỏi: "Nếu người dùng cố tình đoán URL thì sao?"

Nhiều PM Việt Nam mặc định rằng bảo mật là việc của engineer hoặc của team security. Điều đó chỉ đúng một nửa. Engineer chịu trách nhiệm implement đúng, nhưng PM là người quyết định yêu cầu bảo mật cần có là gì — và yêu cầu đó phải nằm trong spec. Nếu spec không nói "dữ liệu này là nhạy cảm, chỉ chủ sở hữu mới được xem", thì engineer hoàn toàn có lý do để xây một API mở toang, vì spec không cấm.

Trong bài này, bạn sẽ học cách viết một mục Security Review trong PRD/Spec: liệt kê các mối đe dọa thường gặp, mô tả yêu cầu bảo mật bằng ngôn ngữ PM (không cần biết code), và đưa ra checklist để buộc team trả lời trước khi ship. Đây không phải bài dạy bạn thành chuyên gia an ninh mạng — mục tiêu là giúp bạn đặt đúng câu hỏi, để những lỗ hổng nguy hiểm không lọt qua khe hở giữa "PM tưởng engineer lo" và "engineer tưởng PM không yêu cầu".

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

Security Review trong spec là một mục mô tả: tính năng này xử lý dữ liệu/hành động nhạy cảm nào, ai được phép làm gì, và những kịch bản tấn công nào cần phòng. Nó không thay thế việc review code hay pentest, nhưng nó là điểm khởi đầu — buộc cả team suy nghĩ về bảo mật từ lúc thiết kế, thay vì vá lỗi sau khi ship.

Threat modeling cho PM: nghĩ như kẻ tấn công

Threat modeling nghe có vẻ cao siêu, nhưng với PM nó đơn giản là tự hỏi ba câu:

  • Dữ liệu gì đang được xử lý, và cái nào nhạy cảm? Tên, số điện thoại, email, địa chỉ, CMND/CCCD, số tài khoản, lịch sử giao dịch, vị trí GPS, ảnh cá nhân...
  • Ai được phép truy cập/thao tác cái gì? Chủ sở hữu, admin, người được chia sẻ, người lạ?
  • Nếu một kẻ xấu cố tình lạm dụng tính năng này, họ làm được gì? Xem trộm dữ liệu người khác, giả mạo, phá hoại, đánh cắp tiền?
Trả lời được ba câu này là bạn đã có một threat model thô. Phần còn lại là biến nó thành yêu cầu cụ thể trong spec.

Ba nhóm mối đe dọa thường gặp nhất

1. PII leakage (rò rỉ thông tin cá nhân). PII là Personally Identifiable Information — tên, số điện thoại, email, địa chỉ, số tài khoản, dữ liệu tài chính, dữ liệu sức khỏe. Rò rỉ PII xảy ra khi dữ liệu nhạy cảm bị lộ cho người không có quyền: API trả về cả những trường không cần thiết, log ghi lại số thẻ ngân hàng, màn hình hiển thị đầy đủ số điện thoại thay vì che bớt, hoặc dữ liệu lọt vào URL/email bị forward đi. Ở Việt Nam, đây không chỉ là rủi ro uy tín mà còn là rủi ro pháp lý theo Nghị định 13/2023 về bảo vệ dữ liệu cá nhân.

2. Auth bypass (vượt qua xác thực/phân quyền). Đây là khi ai đó truy cập được thứ họ không được phép. Hai dạng phổ biến:

  • Broken authentication: không đăng nhập mà vẫn vào được (như ví dụ link mời ở đầu bài).
  • Broken authorization / IDOR (Insecure Direct Object Reference): đã đăng nhập, nhưng truy cập được dữ liệu của người khác bằng cách đổi ID. Ví dụ /orders/1001 là đơn của bạn, đổi thành /orders/1002 ra đơn của người lạ. Hệ thống kiểm tra "bạn đã đăng nhập chưa" nhưng quên kiểm tra "đơn này có phải của bạn không".
3. Injection (chèn mã độc). Khi dữ liệu người dùng nhập vào bị hệ thống "tin nhầm" là lệnh:
  • SQL injection: nhập ' OR '1'='1 vào ô tìm kiếm khiến truy vấn database trả về toàn bộ dữ liệu.
  • XSS (Cross-Site Scripting): nhập đoạn script vào ô comment, khi người khác xem comment thì script chạy trong trình duyệt họ — đánh cắp session, redirect sang web lừa đảo.
  • Command/template injection: tương tự nhưng với lệnh hệ thống hoặc template engine.
Với PM, bạn không cần biết cách hacker viết payload. Bạn chỉ cần đánh dấu trong spec: "Mọi input người dùng (tên, comment, mô tả, file upload) đều phải được coi là không đáng tin và phải sanitize/escape trước khi lưu hoặc hiển thị."

Viết yêu cầu bảo mật bằng ngôn ngữ PM

Bạn không viết code, nên đừng viết "dùng prepared statement". Hãy viết yêu cầu ở mức hành vi:

  • "Người dùng A không bao giờ được xem đơn hàng của người dùng B, kể cả khi biết ID đơn hàng."
  • "Số điện thoại hiển thị cho người lạ phải được che: 09xx xxx 789."
  • "API trả về thông tin profile chỉ bao gồm: tên hiển thị, avatar. Không trả email, số điện thoại trừ khi người xem là chính chủ."
  • "Mọi nội dung do người dùng nhập, khi hiển thị lại, không được thực thi như mã."
Engineer sẽ tự chọn kỹ thuật phù hợp. Việc của bạn là định nghĩa "đúng" trông như thế nào.

Tình huống thực tế

Ví dụ 1 — Sàn TMĐT giả định "ChợViệt" và lỗ hổng IDOR ở hóa đơn

ChợViệt, một sàn TMĐT vừa, ra tính năng "Tải hóa đơn điện tử" cho đơn hàng. Spec ban đầu chỉ viết một dòng: "Người dùng bấm nút Tải hóa đơn, hệ thống trả về PDF." Engineer làm endpoint GET /invoice/{order_id}.pdf, chỉ kiểm tra người dùng đã đăng nhập.

Ba tuần sau ship, một khách hàng tinh nghịch nhận ra mình có thể đổi order_id trong URL và tải về hóa đơn của bất kỳ ai — kèm tên, địa chỉ giao hàng, số điện thoại, danh sách sản phẩm đã mua. Với khoảng 40.000 đơn/ngày, đây là một kho PII khổng lồ có thể bị cào tự động. May là khách hàng đó báo lại thay vì rao bán.

Diễn giải: Lỗ hổng kinh điển IDOR. Spec đã kiểm tra "đăng nhập" (authentication) nhưng quên "có quyền với đơn này không" (authorization). Nếu spec có một dòng "chỉ chủ đơn hàng mới tải được hóa đơn của đơn đó", engineer đã thêm bước kiểm tra order.user_id == current_user.id.

Bài học: Bất cứ khi nào spec có một resource truy cập bằng ID (đơn hàng, hồ sơ, file, tin nhắn), hãy viết rõ ai được truy cập, và ghi vào checklist câu hỏi: "Nếu tôi đổi ID này sang của người khác thì sao?"

Ví dụ 2 — Một ví điện tử Đông Nam Á và chuyện log ghi cả OTP

Một ví điện tử (kiểu MoMo/ShopeePay) build tính năng chuyển tiền. Để debug cho dễ, team log toàn bộ request gửi lên server, bao gồm cả số điện thoại, số tiền, và mã OTP. Log này được đẩy vào một hệ thống quan sát của bên thứ ba mà hàng chục nhân viên có quyền xem, và lưu 90 ngày.

Khi team security audit, họ phát hiện OTP và một phần thông tin tài chính nằm trong log dạng plaintext. Về kỹ thuật chưa có ai khai thác, nhưng đây là vi phạm nghiêm trọng: OTP bị log nghĩa là nếu log rò rỉ, kẻ xấu có thể tái hiện giao dịch; và lưu PII tài chính 90 ngày cho hàng chục người xem là vi phạm nguyên tắc tối thiểu hóa dữ liệu.

Diễn giải: Đây là PII leakage qua một đường ít ai để ý — không phải qua API hay màn hình, mà qua logging. Spec tính năng nhạy cảm cần nói rõ: trường nào không bao giờ được ghi log (OTP, mật khẩu, số thẻ đầy đủ, CVV).

Bài học: Trong mục Security Review, thêm một dòng "Logging & data retention": liệt kê các trường nhạy cảm bị cấm log, và thời gian lưu tối đa. Đây là loại rủi ro mà chỉ PM/spec mới chặn được từ đầu, vì nó là quyết định thiết kế chứ không phải bug code.

Ví dụ 3 — Startup edtech và XSS trong phần bình luận

Một startup edtech Việt cho học viên bình luận dưới bài học. Spec không nhắc gì đến việc xử lý nội dung bình luận. Một người dùng dán vào ô comment đoạn <script> chuyển hướng người xem sang một trang giả mạo cổng thanh toán. Khi học viên khác mở bài học, trình duyệt họ chạy script đó.

Hậu quả: một số học viên bị dẫn sang trang lừa đảo, vài người nhập thông tin thẻ. Đội ngũ phải gỡ tính năng comment khẩn cấp trong 6 tiếng và xin lỗi công khai.

Diễn giải: Stored XSS — input độc hại được lưu và phát tán cho mọi người xem. Nguyên nhân gốc: spec coi comment là "text bình thường" thay vì "dữ liệu không đáng tin từ người lạ".

Bài học: Mọi nơi nhận input từ người dùng và hiển thị lại cho người khác (comment, tên hiển thị, bio, mô tả sản phẩm) đều phải có dòng yêu cầu "escape/sanitize khi render, không thực thi như HTML/JS". Đây là một dòng trong spec, nhưng nó là ranh giới giữa an toàn và một sự cố PR.

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

Đây là quy trình viết mục Security Review cho một spec bất kỳ.

Bước 1 — Liệt kê dữ liệu và phân loại nhạy cảm. Lập một bảng nhỏ: tính năng này đọc/ghi những dữ liệu nào, và mỗi loại thuộc mức nào (công khai / nội bộ / nhạy cảm / tài chính-định danh). Đánh dấu rõ PII và dữ liệu tài chính.

Bước 2 — Vẽ ma trận quyền (ai làm được gì). Liệt kê các vai trò (khách vãng lai, người dùng đã đăng nhập, chủ sở hữu, admin) và với mỗi hành động (xem, sửa, xóa, tải về) đánh dấu vai trò nào được phép. Đây chính là cách chặn auth bypass từ gốc.

Bước 3 — Đặt câu hỏi "kẻ tấn công". Với mỗi luồng, tự hỏi: Nếu tôi đổi ID? Nếu tôi gọi thẳng API không qua UI? Nếu tôi nhập ký tự đặc biệt/script? Nếu tôi gửi request lặp lại 1000 lần (rate limiting/abuse)? Ghi lại câu trả lời mong muốn.

Bước 4 — Viết yêu cầu bảo mật ở mức hành vi. Chuyển các câu trả lời thành yêu cầu rõ ràng như đã hướng dẫn ở phần khái niệm. Mỗi yêu cầu nên kiểm chứng được (testable), ví dụ "người dùng B nhận lỗi 403 khi truy cập đơn của A".

Bước 5 — Xử lý logging & retention. Ghi rõ trường nào cấm log, dữ liệu lưu bao lâu, ai có quyền xem dữ liệu thô.

Bước 6 — Đính kèm Security Checklist và gắn người duyệt. Đưa checklist (bên dưới) vào spec và đánh dấu cần ai review (security champion, tech lead). Với tính năng đụng tới tiền hoặc PII quy mô lớn, đánh dấu "cần security review chính thức" trước khi ship.

Một mẫu mục gọn để dán vào PRD:

Security Review

Dữ liệu nhạy cảm

  • PII: tên, SĐT, email, địa chỉ giao hàng
  • Tài chính: số tiền giao dịch (KHÔNG lưu số thẻ, CVV, OTP)

Ma trận quyền

Hành độngKháchĐã loginChủ sở hữuAdmin
Xem hóa đơn--
Tải hóa đơn PDF--

Yêu cầu bảo mật

  • Người dùng chỉ truy cập được resource của chính mình (chống IDOR).
Truy cập trái phép → trả 403, không tiết lộ resource có tồn tại hay không.
  • Mọi input hiển thị lại phải được sanitize (chống XSS).
  • SĐT hiển thị cho người không phải chủ: che dạng 09xx xxx 789.

Logging & Retention

  • CẤM log: OTP, mật khẩu, số thẻ đầy đủ, CVV.
  • Log giao dịch lưu tối đa 30 ngày, chỉ team Risk truy cập.

Người duyệt: @security-champion (bắt buộc, vì tính năng đụng PII tài chính)

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

Lỗi 1: Nghĩ "tính năng nội bộ thì không cần lo bảo mật". Rất nhiều rò rỉ đến từ tính năng tưởng vô hại (export CSV, trang admin sơ sài). Quy mô không quyết định độ nhạy cảm — dữ liệu mới quyết định.

Lỗi 2: Chỉ kiểm tra authentication, quên authorization. "Đã đăng nhập" khác với "có quyền với chính resource này". Đây là nguồn gốc của hầu hết lỗi IDOR. Mẹo: với mọi endpoint nhận ID, luôn hỏi "ID này thuộc về ai, và người gọi có phải người đó không?"

Lỗi 3: Để lộ thông tin qua thông báo lỗi. "Email này không tồn tại" vs "Sai mật khẩu" giúp kẻ xấu dò ra email nào đã đăng ký. Mẹo: dùng thông báo trung tính như "Email hoặc mật khẩu không đúng".

Lỗi 4: Tin vào kiểm tra ở phía client. Ẩn nút trên UI không phải là bảo mật — kẻ xấu gọi thẳng API. Mọi kiểm tra quyền phải nằm ở server. Mẹo: trong spec ghi rõ "kiểm tra quyền ở server, không chỉ ẩn UI".

Lỗi 5: Viết yêu cầu mơ hồ kiểu "phải an toàn". Không testable thì không kiểm chứng được. Mẹo: mỗi yêu cầu bảo mật nên viết được thành một test case có kết quả mong đợi cụ thể (403, dữ liệu bị che, lỗi trung tính...).

Mẹo vàng: Tìm một "security champion" — một engineer quan tâm bảo mật — và mời họ đọc lướt mục Security Review của bạn 15 phút trước khi spec được chốt. Chi phí cực thấp, giá trị cực cao.

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

Chọn một tính năng bạn đang/sắp làm (hoặc dùng tình huống sau: "Cho phép người dùng chia sẻ báo cáo doanh thu của mình qua một link, ai có link là xem được").

  • Phân loại dữ liệu: Liệt kê dữ liệu tính năng xử lý và đánh dấu cái nào là PII/nhạy cảm/tài chính.
  • Ma trận quyền: Vẽ bảng vai trò × hành động. Riêng tình huống "link chia sẻ", trả lời: link có hết hạn không? Ai có link cũng xem được, vậy nếu link bị forward thì sao? Có cần thu hồi link không?
  • Câu hỏi kẻ tấn công: Viết ít nhất 4 kịch bản lạm dụng (đổi ID, gọi thẳng API, nhập script, spam request) và kết quả mong muốn cho mỗi cái.
  • Viết mục Security Review: Dùng template ở trên, hoàn thiện cả phần Logging & Retention.
  • Tự review: Đọc lại và kiểm tra xem mỗi yêu cầu có testable không. Nếu một dòng không thể viết thành test case, hãy viết lại cho cụ thể.
Đặt mục tiêu hoàn thành trong 30 phút. Nếu làm nghiêm túc, bạn sẽ phát hiện ít nhất một lỗ hổng mà bản spec ban đầu bỏ sót.

Tóm tắt

Security Review trong spec không biến bạn thành chuyên gia an ninh, nhưng nó buộc cả team đặt đúng câu hỏi từ lúc thiết kế — thời điểm rẻ nhất để sửa. Ba nhóm mối đe dọa cần nhớ: PII leakage (rò rỉ thông tin cá nhân, kể cả qua log), auth bypass (đặc biệt là IDOR — quên kiểm tra quyền sở hữu), và injection (SQL, XSS — coi mọi input người dùng là không đáng tin). Là PM, bạn viết yêu cầu ở mức hành vi (ai được làm gì, dữ liệu nào bị che, lỗi trả về ra sao), còn engineer chọn kỹ thuật. Quy trình sáu bước — phân loại dữ liệu, ma trận quyền, câu hỏi kẻ tấn công, yêu cầu testable, logging/retention, gắn người duyệt — cùng một template ngắn là đủ để bạn đưa Security Review thành phần cố định trong mọi PRD. Đừng để lỗ hổng lọt qua khe hở giữa "PM tưởng engineer lo" và "engineer tưởng PM không yêu cầu".