0

Hàng Trăm Lỗ Hổng Bảo Mật Trong Code 3 Năm Tuổi: Lập Trình Viên Nên Bắt Đầu Từ Đâu?

Một buổi sáng, bạn nhận được một file Excel: gần 300 dòng, mỗi dòng là một lỗi bảo mật, kèm điểm CVSS, kèm deadline "trong tuần này". Phần lớn là injection, auth, validation — những lỗi nằm sâu trong logic, không phải kiểu "chỉ cần nâng version thư viện". Code thì đã 2-3 năm tuổi, không ai trong team hiện tại từng đụng vào, và không có một dòng test nào bảo vệ nó.

Nếu bạn từng rơi vào tình huống này, bạn sẽ nhận ra ngay: đây không phải là một bài toán code. Đây là một bài toán quản trị rủi ro bị ngụy trang thành một bài toán code.

Problem

Cái bẫy lớn nhất khi nhận yêu cầu kiểu này là coi nó như một danh sách task bình thường — cứ thế lần lượt sửa từng dòng cho đến khi hết. Với code mới, cách làm đó ổn. Với legacy code không test, không tài liệu, đây là công thức để biến một vấn đề bảo mật thành hai vấn đề: vẫn còn lỗ hổng chưa vá kịp, cộng thêm bug mới do sửa vội sinh ra.

Vấn đề thực sự không nằm ở "làm sao code nhanh hơn". Nó nằm ở chỗ: không ai — kể cả bạn — biết chắc sửa một dòng sẽ ảnh hưởng đến những gì khác trong hệ thống.

Context

Tình huống này gần như luôn là hậu quả của một lỗ hổng quy trình có từ trước, không phải lỗi của người đang cầm task fix bug hôm nay:

  • Code được viết và deploy khi chưa có bước quét bảo mật (SAST/SCA) trong CI/CD.
  • Không ai review security lúc code review thông thường.
  • Nhiều năm sau, một bên thứ ba (đội vận hành, đối tác, hoặc audit) quét lại bằng công cụ riêng của họ và tất cả nợ kỹ thuật bảo mật dồn lại thành một báo cáo duy nhất.

Điều quan trọng cần hiểu: CVSS score trong báo cáo là điểm "base", tính trên lý thuyết của loại lỗ hổng đó, chưa tính đến việc lỗ hổng có thực sự khai thác được trong hệ thống của bạn hay không. Một lỗi "Critical" trên giấy có thể vô hại nếu endpoint đó không expose ra ngoài, và một lỗi "Medium" có thể nguy hiểm hơn nhiều nếu nó nằm ngay ở luồng auth công khai.

Core Concept

Tư duy đúng khi tiếp nhận legacy security debt có thể gói gọn trong một câu:

Nhiệm vụ của bạn không phải là "fix hết lỗi trong danh sách", mà là "làm rõ ràng và giảm rủi ro một cách có kiểm soát, đồng thời để lộ ra đúng chỗ nào cần quyết định vượt quá thẩm quyền của bạn."

Nói cách khác, thành công ở đây không được đo bằng số dòng trong báo cáo được đánh dấu "đã fix", mà được đo bằng: hệ thống có thực sự an toàn hơn không, và những rủi ro chưa xử lý được có được nhìn thấy và quyết định một cách minh bạch hay không — thay vì bị giấu dưới một bản fix vội vàng.

Deep Dive — Bốn thay đổi trong tư duy

1. Từ "người thực thi" sang "người đánh giá trước khi thực thi"

Phản xạ tự nhiên khi nhận một danh sách lỗi là bắt tay vào sửa ngay từ dòng đầu tiên. Tư duy đúng hơn: trước khi sửa bất cứ thứ gì, dành thời gian trả lời "lỗi nào thực sự nguy hiểm, lỗi nào sửa an toàn, lỗi nào sửa nguy hiểm hơn cả để nguyên". Việc đánh giá này quan trọng hơn tốc độ gõ code, vì nó quyết định bạn đang dùng thời gian ít ỏi của mình vào đúng chỗ hay không.

2. Từ "tôi phải tự chịu trách nhiệm hiểu hết" sang "tôi cần làm rõ giới hạn hiểu biết của mình"

Không ai có thể hiểu hết một hệ thống ba năm tuổi trong vài ngày, kể cả người giỏi nhất. Tư duy trưởng thành không phải là cố tỏ ra hiểu hết, mà là nói rõ đâu là phần mình tự tin, đâu là phần mình không chắc — với đồng đội, với lead. Sự minh bạch này không phải là dấu hiệu yếu kém, nó là điều kiện cần để cả team ra quyết định đúng.

3. Từ "nhận task và tự xoay xở" sang "phản biện có dữ liệu"

Khi một yêu cầu không hợp lý về mặt kỹ thuật (ví dụ: khối lượng lớn, thời gian ngắn, hệ thống mình không nắm) được giao xuống, im lặng nhận và cố hoàn thành không phải là chuyên nghiệp — đó là né tránh trách nhiệm thật sự của mình, tức là nêu rõ rủi ro trước khi hành động. Người ra quyết định cuối cùng về đánh đổi giữa tốc độ và an toàn phải là người có thẩm quyền đó (lead, PM, chủ hệ thống) — nhưng họ chỉ quyết định đúng khi được cung cấp thông tin đúng. Việc của bạn là đảm bảo thông tin đó đến được tay họ, bằng văn bản, có cơ sở.

4. Từ "sửa để xong việc" sang "sửa để có thể giải thích được"

Một thay đổi mà bạn không thể giải thích rõ ràng "vì sao sửa như vậy, và vì sao tin rằng nó an toàn" là một thay đổi rủi ro, bất kể nó có chạy được hay không. Ngược lại, một thay đổi nhỏ, có lý do rõ ràng, có thể rollback dễ dàng — dù chưa "triệt để" — vẫn tốt hơn nhiều so với một bản sửa lớn mà chính người viết ra cũng không chắc chắn 100%.

Real-world Example

Hai lập trình viên cùng nhận một danh sách 50 lỗi bảo mật trong cùng một hệ thống cũ, cùng deadline một tuần.

Người thứ nhất chia đều 50 lỗi cho 5 ngày, sửa lần lượt, cố gắng "cho xong". Đến ngày thứ tư, một bản sửa ở luồng auth gây lỗi đăng nhập cho một nhóm user, phải rollback gấp, uy tín cả team bị ảnh hưởng.

Người thứ hai dành nửa ngày đầu tiên phân loại: 30 lỗi có thể sửa an toàn không đụng logic, 15 lỗi cần hiểu sâu hơn, 5 lỗi liên quan trực tiếp đến auth — quá rủi ro để sửa vội. Người này xử lý xong nhóm đầu trong 2 ngày, dành phần còn lại cho nhóm giữa, và gửi văn bản đề xuất hoãn 5 lỗi auth kèm biện pháp giảm thiểu tạm thời. Đến cuối tuần, báo cáo của người này thể hiện ít lỗi được đánh dấu "closed" hơn — nhưng hệ thống thực sự an toàn hơn, không có sự cố nào xảy ra, và mọi rủi ro còn lại đều được ghi nhận rõ ràng.

Về con số, người thứ hai "làm ít hơn". Về bản chất, người thứ hai làm đúng việc hơn.

Trade-offs

Tư duy Được gì Đánh đổi gì
Fix nhanh, fix hết theo danh sách Báo cáo đẹp trong ngắn hạn Rủi ro sinh lỗi mới cao, khó lường trước hậu quả
Đánh giá trước, phân loại, phản biện Rủi ro được kiểm soát, minh bạch Cần thời gian ban đầu, có thể bị hiểu nhầm là "chậm" nếu không giải thích rõ
Im lặng nhận hết trách nhiệm Tránh xung đột trước mắt Nếu có sự cố, một mình gánh hậu quả của một quyết định không phải do mình đưa ra
Nêu rõ giới hạn và phản biện có cơ sở Trách nhiệm được phân bổ đúng người Cần kỹ năng giao tiếp, đôi khi tạo cảm giác "khó chịu" ngắn hạn với người giao việc

Common Mistakes

  • Đánh đồng "đã sửa" với "đã an toàn" — một bản fix vội có thể che dấu hiệu cảnh báo của scanner nhưng không thực sự giải quyết rủi ro, hoặc tệ hơn là tạo ra rủi ro vận hành mới.
  • Coi việc phản biện deadline là thiếu chuyên nghiệp — thực ra ngược lại, im lặng nhận một yêu cầu bất khả thi về mặt an toàn mới là thiếu chuyên nghiệp.
  • Tự nhận hết trách nhiệm của một quyết định vượt thẩm quyền mình — ví dụ tự quyết định "chấp nhận rủi ro" cho một lỗi mà không có ai phê duyệt, thay vì đưa vấn đề lên đúng người.
  • Dùng công cụ (kể cả AI) như một cách để né việc phải hiểu hệ thống — công cụ giúp làm nhanh hơn phần thao tác, nhưng không thay được phần quan trọng nhất: hiểu đủ để biết mình đang đánh đổi gì.

Practical Takeaways

  • Trước khi sửa dòng đầu tiên, hãy dành thời gian trả lời câu hỏi "nếu tôi chỉ có thể làm một phần trong quỹ thời gian này, phần nào mang lại giảm rủi ro nhiều nhất mà an toàn nhất để sửa?"
  • Ghi lại bằng văn bản những gì bạn tự tin, những gì bạn không chắc — đừng giữ điều đó trong đầu.
  • Coi việc phản biội deadline/phạm vi không hợp lý là một phần bắt buộc của công việc, không phải một lựa chọn "nếu dám".
  • Đo thành công của mình bằng "rủi ro hệ thống giảm bao nhiêu và có ai bị bất ngờ không", không phải bằng "bao nhiêu dòng trong Excel được tô xanh".

Conclusion

Legacy technical debt về bảo mật không phải là một cuộc thi tốc độ fix bug. Nó là một bài kiểm tra tư duy: bạn có đủ tỉnh táo để không hoảng loạn trước một danh sách dài, đủ trung thực để nói rõ giới hạn hiểu biết của mình, và đủ chủ động để đưa vấn đề đến đúng người ra quyết định hay không. Người lập trình viên xử lý tốt tình huống này không phải là người đóng nhiều lỗi nhất, mà là người khiến cả hệ thống — và cả những người xung quanh — hiểu rõ hơn về rủi ro thật sự đang nằm ở đâu.


Bảng thuật ngữ

Thuật ngữ Giải thích
CVSS Common Vulnerability Scoring System — hệ thống chấm điểm mức độ nghiêm trọng của lỗ hổng bảo mật, tính trên lý thuyết, chưa xét ngữ cảnh hệ thống cụ thể.
SAST Static Application Security Testing — quét mã nguồn tĩnh để tìm lỗ hổng mà không cần chạy chương trình.
SCA Software Composition Analysis — quét các thư viện/dependency bên thứ ba để tìm lỗ hổng đã biết.
Exploitability Khả năng một lỗ hổng thực sự bị khai thác trong điều kiện thực tế của hệ thống.
False positive Lỗi được công cụ quét báo ra nhưng thực chất không phải là lỗ hổng thật.
Compensating control Biện pháp giảm rủi ro tạm thời ở tầng khác (ví dụ WAF, rate limiting) trong khi chờ vá lỗi gốc trong code.
Characterization test Test được viết để ghi lại hành vi hiện tại của một đoạn code trước khi chỉnh sửa, dùng làm mốc so sánh sau khi sửa.
Technical debt Nợ kỹ thuật — các quyết định kỹ thuật (bao gồm cả về bảo mật) chưa được xử lý triệt để, tích lũy theo thời gian và cần "trả" sau này.
Risk acceptance Quyết định chính thức (có văn bản, có người ký duyệt) chấp nhận một rủi ro chưa được xử lý trong một khoảng thời gian nhất định.

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí