Trong nhiều thập kỷ, kiểm thử bảo mật ứng dụng tĩnh (SAST) đã là một trong những cách hiệu quả nhất để các đội ngũ bảo mật mở rộng quy mô đánh giá mã.
Nhưng khi xây dựng tính năng Bảo mật Codex, chúng tôi đã đưa ra một lựa chọn thiết kế có chủ đích: chúng tôi đã không bắt đầu bằng việc nhập một báo cáo phân tích tĩnh và yêu cầu tác nhân phân loại ưu tiên nó. Chúng tôi thiết kế hệ thống để bắt đầu với chính kho lưu trữ—kiến trúc của nó, các ranh giới tin cậy và hành vi dự định—và xác thực những gì nó tìm thấy trước khi yêu cầu một con người dành thời gian cho việc đó.
Lý do rất đơn giản: những lỗ hổng khó nhất thường không phải là vấn đề về luồng dữ liệu. Chúng xảy ra khi mã dường như thực thi một bước kiểm tra bảo mật, nhưng bước kiểm tra đó thực ra không đảm bảo thuộc tính mà hệ thống dựa vào. Nói cách khác, thách thức không chỉ là theo dõi cách dữ liệu di chuyển qua một chương trình—mà là xác định liệu các cơ chế phòng vệ trong mã có thực sự hoạt động hay không.
SAST thường được mô tả như một pipeline rõ ràng: xác định một nguồn đầu vào không đáng tin cậy, theo dõi dữ liệu qua chương trình và gắn cờ các trường hợp mà dữ liệu đó đi tới một điểm nhận nhạy cảm mà không được khử nhiễm. Đó là một mô hình thanh lịch và nó bao quát nhiều lỗi có thật.
Trong thực tế, SAST phải đưa ra các xấp xỉ để vẫn có thể xử lý được ở quy mô lớn—đặc biệt là trong các cơ sở mã thực, nơi có sự gián tiếp, phân phối động, các hàm gọi lại, phản chiếu, và luồng điều khiển phức tạp dựa trên framework. Những sự xấp xỉ đó không phải là lời chê bai SAST; đó là thực tế của việc cố gắng suy luận về mã mà không thực thi nó.
Chỉ riêng điều đó không phải là lý do vì sao tính năng Bảo mật Codex không bắt đầu bằng một báo cáo SAST.
Vấn đề sâu xa hơn là điều gì xảy ra sau khi bạn lần theo thành công một nguồn đến một bồn nhận.
Ngay cả khi phân tích tĩnh theo dõi chính xác đầu vào qua nhiều hàm và nhiều lớp, nó vẫn phải trả lời câu hỏi thực sự quyết định liệu một lỗ hổng có tồn tại hay không:
Lấy một mẫu phổ biến: mã gọi một hàm như sanitize_html() trước khi kết xuất nội dung không đáng tin cậy. Trình phân tích tĩnh có thể thấy rằng bộ lọc đã chạy. Điều mà nó thường không thể xác định được là liệu bộ lọc đó có thực sự đủ cho ngữ cảnh kết xuất cụ thể, công cụ mẫu, hành vi mã hóa và các phép biến đổi xuôi dòng liên quan hay không.
Đó là lúc mọi chuyện trở nên phức tạp. Vấn đề không chỉ là liệu dữ liệu có đến được điểm nhận hay không. Điều đó phụ thuộc vào việc các kiểm tra trong mã có thực sự ràng buộc giá trị theo cách mà hệ thống giả định hay không.
Nói cách khác: có một sự khác biệt lớn giữa “mã gọi một bộ lọc” và “hệ thống an toàn.”
Đây là một mẫu thường xuyên xuất hiện trong các hệ thống thực tế.
Một ứng dụng web nhận một gói dữ liệu JSON, trích xuất redirect_url, xác thực nó với một regex danh sách cho phép, giải mã URL, và chuyển kết quả đến một trình xử lý chuyển hướng.
Một báo cáo nguồn-đích kiểu kinh điển có thể mô tả luồng:
đầu vào không đáng tin cậy → kiểm tra biểu thức chính quy (regex) → giải mã URL → chuyển hướng
Nhưng câu hỏi thực sự không phải là liệu phép kiểm tra có tồn tại hay không. Điều đó là liệu phép kiểm tra đó vẫn còn hạn chế giá trị sau các phép biến đổi tiếp theo hay không.
Nếu regex chạy trước khi giải mã, liệu nó có thực sự ràng buộc URL đã được giải mã theo cách mà trình xử lý chuyển hướng diễn giải không?
Trả lời điều đó có nghĩa là suy luận về toàn bộ chuỗi chuyển đổi: regex cho phép điều gì, cách giải mã và chuẩn hóa hoạt động, cách phân tích URL xử lý các trường hợp biên, và cách logic chuyển hướng phân giải các lược đồ và thẩm quyền.
Nhiều lỗ hổng quan trọng trong thực tế trông giống như thế này: sai sót về thứ tự thực hiện, chuẩn hóa một phần, mơ hồ khi phân tích cú pháp, và sự không khớp giữa việc xác thực và diễn giải. Luồng dữ liệu có thể nhìn thấy được. Điểm yếu nằm ở cách các ràng buộc lan truyền—hoặc không lan truyền—qua chuỗi biến đổi.
Đây không chỉ là một khuôn mẫu lý thuyết. Trong CVE-2024-29041(mở trong cửa sổ mới), Express bị ảnh hưởng bởi một lỗi chuyển hướng mở, trong đó các URL bị định dạng sai có thể vượt qua các triển khai danh sách cho phép phổ biến do cách các đích chuyển hướng được mã hóa rồi sau đó được diễn giải. Luồng dữ liệu đơn giản. Câu hỏi khó hơn—và cũng là câu hỏi quyết định liệu lỗi có tồn tại hay không—là liệu tính hợp lệ vẫn còn giữ được sau chuỗi biến đổi hay không.
tính năng Bảo mật Codex được xây dựng với một mục tiêu đơn giản: giảm bớt quá trình phân loại bằng cách làm nổi bật các vấn đề với bằng chứng thuyết phục hơn. Trong sản phẩm, điều đó có nghĩa là sử dụng bối cảnh dành riêng cho repo (bao gồm mô hình mối đe dọa) và xác thực các vấn đề tín hiệu cao trong một môi trường biệt lập trước khi làm nổi bật chúng.
Khi tính năng Bảo mật Codex gặp phải một ranh giới trông giống như “xác thực” hoặc “làm sạch”, nó không coi đó chỉ là một ô đánh dấu. Nó cố gắng hiểu điều mà đoạn mã đang cố gắng bảo đảm—và sau đó nó cố gắng bác bỏ sự bảo đảm đó.
Trong thực tế, điều đó thường trông như một sự kết hợp của:
- Đọc đường dẫn mã liên quan với đầy đủ ngữ cảnh của kho lưu trữ, theo cách mà một nhà nghiên cứu bảo mật sẽ làm, và tìm kiếm sự không khớp giữa mục đích và việc triển khai. Điều này bao gồm nhận xét, nhưng mô hình không nhất thiết tin vào các nhận xét, vì vậy việc thêm //Halvar says: this is not a bug phía trên mã của bạn không làm nó nhầm lẫn, nếu thực sự có một lỗi.
- Giảm vấn đề xuống lát cắt nhỏ nhất có thể kiểm thử (ví dụ, đường ống chuyển đổi xung quanh một đầu vào duy nhất) để bạn có thể suy luận về nó mà không bị phần còn lại của hệ thống cản trở. Theo nghĩa này, tính năng Bảo mật Codex trích xuất các lát mã nhỏ và sau đó viết các bộ kiểm thử fuzz siêu nhỏ cho chúng.
- Suy luận về các ràng buộc trong các phép biến đổi, thay vì xem mỗi lần kiểm tra là độc lập. Khi phù hợp, điều này có thể bao gồm việc chuyển thành một câu hỏi về tính thỏa mãn. Nói cách khác, chúng tôi cung cấp cho mô hình quyền truy cập vào môi trường Python với z3-solver và nó sử dụng công cụ này rất tốt khi cần, giống như một người sẽ làm khi giải quyết một bài toán ràng buộc đầu vào đặc biệt phức tạp. Điều này đặc biệt hữu ích khi kiểm tra hiện tượng tràn số nguyên hoặc các lỗi tương tự trên các kiến trúc không tiêu chuẩn.
- Thực thi các giả thuyết trong môi trường xác thực hộp cát khi có thể, để phân biệt “điều này có thể là một vấn đề” với “đây là một vấn đề”. Không có bằng chứng nào tốt hơn một PoC hoàn chỉnh từ đầu đến cuối với mã được biên dịch ở chế độ gỡ lỗi.
Đây là sự chuyển dịch then chốt: thay vì dừng lại ở “tồn tại đợt kiểm tra,” hệ thống thúc đẩy hướng tới “bất biến được giữ vững (hoặc không), và đây là bằng chứng”. Và mô hình sẽ chọn công cụ phù hợp nhất cho công việc đó.
Một phản ứng hợp lý là: tại sao không làm cả hai? Bắt đầu với một báo cáo SAST, sau đó sử dụng tác nhân để lý luận sâu hơn.
Có những trường hợp các phát hiện được tính toán trước là hữu ích—đặc biệt là đối với các lớp lỗi hẹp, đã biết. Nhưng đối với một tác nhân được thiết kế để phát hiện và xác thực các lỗ hổng trong ngữ cảnh, việc bắt đầu từ một báo cáo SAST tạo ra ba dạng lỗi có thể dự đoán được.
Trước hết, điều này có thể khuyến khích việc thu hẹp quá sớm. Danh sách các phát hiện là bản đồ cho biết những nơi công cụ đã kiểm tra. Nếu coi đó là điểm khởi đầu, hệ thống có thể bị thiên lệch theo hướng dành nỗ lực không tương xứng cho các khu vực giống nhau, sử dụng các lớp trừu tượng giống nhau và bỏ sót những nhóm vấn đề không phù hợp với thế giới quan của công cụ.
Thứ hai, điều này có thể đưa vào những phán xét ngầm định mà rất khó gỡ bỏ. Nhiều kết quả phát hiện SAST mã hóa các giả định về việc làm sạch dữ liệu, xác thực hoặc ranh giới tin cậy. Nếu những giả định đó sai—hoặc chỉ đơn giản là chưa đầy đủ—việc đưa chúng vào vòng lặp suy luận có thể khiến tác nhân chuyển từ “điều tra” sang “xác nhận hoặc bác bỏ”, điều này không phải là điều chúng ta muốn tác nhân làm.
Thứ ba, điều này có thể khiến việc đánh giá hệ thống suy luận trở nên khó hơn. Nếu quy trình bắt đầu bằng đầu ra SAST, sẽ trở nên khó tách bạch những gì tác nhân phát hiện thông qua phân tích của chính nó với những gì nó kế thừa từ một công cụ khác. Sự tách biệt đó rất quan trọng nếu bạn muốn đo lường chính xác năng lực của hệ thống, điều này là cần thiết để hệ thống cải thiện theo thời gian.
Vì vậy, chúng tôi đã xây dựng tính năng Bảo mật Codex để bắt đầu từ nơi nghiên cứu bảo mật bắt đầu: từ mã và ý định của hệ thống, với xác thực được sử dụng để nâng cao ngưỡng độ tin cậy trước khi chúng tôi làm gián đoạn một con người.
Các công cụ SAST có thể rất xuất sắc ở đúng những gì chúng được thiết kế để làm: thực thi các tiêu chuẩn lập trình an toàn, phát hiện các vấn đề trực tiếp từ nguồn đến đích đơn giản và phát hiện các mẫu đã biết trên quy mô lớn với các đánh đổi có thể dự đoán được. Chúng có thể là một phần mạnh mẽ của phòng thủ theo chiều sâu.
Bài viết này hẹp hơn: nó nói về lý do vì sao một tác nhân được thiết kế để suy luận về hành vi và xác nhận các phát hiện không nên bắt đầu công việc của mình bằng việc bám vào một danh sách phát hiện tĩnh.
Cũng đáng lưu ý một hạn chế liên quan của cách nghĩ thuần túy từ nguồn đến đích: không phải mọi lỗ hổng đều là vấn đề luồng dữ liệu. Nhiều sự cố thực tế là các vấn đề về trạng thái và bất biến—bỏ qua quy trình làm việc, lỗ hổng ủy quyền và các lỗi kiểu "hệ thống đang ở trạng thái sai". Đối với các loại lỗi này, một giá trị bị nhiễm bẩn không đi đến một "điểm nhận nguy hiểm" duy nhất. Rủi ro nằm ở những gì chương trình giả định sẽ luôn đúng.
Chúng tôi kỳ vọng hệ sinh thái công cụ bảo mật sẽ tiếp tục cải thiện: phân tích tĩnh, kiểm thử fuzzing, các lớp bảo vệ thời gian chạy và các quy trình làm việc của tác nhân đều sẽ có vai trò.
Điều chúng tôi muốn tính năng Bảo mật Codex làm tốt là phần khiến các đội ngũ bảo mật tốn kém nhất: biến “điều này có vẻ đáng ngờ” thành “điều này là thật, đây là cách nó thất bại và đây là một bản sửa lỗi phù hợp với mục đích của hệ thống.”
Nếu bạn muốn tìm hiểu thêm về cách tính năng Bảo mật Codex quét các kho lưu trữ, xác thực các phát hiện và đề xuất bản sửa lỗi, hãy xem tài liệu của chúng tôi(mở trong cửa sổ mới).


