Mongoose là một thư viện Mô hình hóa dữ liệu đối tượng (ODM) cho MongoDB giúp đơn giản hóa các tương tác cơ sở dữ liệu trong các ứng dụng Node.js. Bằng cách cung cấp giải pháp dựa trên lược đồ, Mongoose cho phép ánh xạ các đối tượng JavaScript vào các tài liệu MongoDB, hoạt động như một lớp trừu tượng giúp cấu trúc dữ liệu để quản lý và xác thực dễ dàng hơn. Với các tính năng như phần mềm trung gian để thực thi logic tùy chỉnh và hệ thống xây dựng truy vấn trực quan, Mongoose nâng cao hiệu quả làm việc với MongoDB. Mongoose, được mô tả là "mô hình hóa đối tượng MongoDB thanh lịch cho Node.js", đã đạt được 27 nghìn sao trên GitHub, phản ánh việc sử dụng rộng rãi và đánh giá cao trong số các nhà phát triển.
OPSWAT Chương trình học bổng và khám phá lỗ hổng quan trọng
Chương trình Học bổng Sau đại học về An ninh mạng OPSWAT cơ sở hạ tầng trọng yếu có trụ sở tại Việt Nam, cung cấp cho sinh viên sau đại học kinh nghiệm thực tế trong việc bảo mật cơ sở hạ tầng trọng yếu . Là một phần của chương trình này, các học viên có cơ hội phân tích và giải quyết các lỗ hổng an ninh mạng, hợp tác với OPSWAT các chuyên gia giải quyết những thách thức thực tế trong các lĩnh vực như phát hiện phần mềm độc hại, Bảo mật tập tin và ngăn chặn mối đe dọa.
Trong suốt OPSWAT Chương trình học bổng, những người tham gia sẽ điều tra và tái tạo một cách có hệ thống các CVE đã biết trên nhiều sản phẩm, thư viện và hệ điều hành khác nhau. Là một phần của sáng kiến này, Dat Phung - một trong những thành viên xuất sắc của chúng tôi - đã chọn nghiên cứu Mongoose do nó được áp dụng rộng rãi trong môi trường sản xuất. Vào tháng 11 năm 2024, anh đã phát hiện ra một lỗ hổng nghiêm trọng trong Mongoose khi thực hiện phân tích chuyên sâu về thư viện. Lỗ hổng này cho phép kẻ tấn công khai thác giá trị $where , có khả năng dẫn đến Thực thi mã từ xa (RCE) trên máy chủ ứng dụng Node.js. Sau khi báo cáo sự cố kịp thời cho Mongoose, một bản vá đã được phát hành như một phần của phiên bản 8.8.3 và CVE-2024-53900 đã được tiết lộ trong Cơ sở dữ liệu lỗ hổng quốc gia (NVD).
Dòng thời gian CVE-2024-53900 & CVE-2025-23061
- Ngày 7 tháng 11 năm 2024: Đạt Phụng đã xác định được lỗ hổng nghiêm trọng trong Mongoose và gửi báo cáo bảo mật cho Snyk.
- Ngày 26 tháng 11 năm 2024: Mongoose phát hành phiên bản 8.8.3 để giải quyết và khắc phục lỗ hổng bảo mật này.
- Ngày 2 tháng 12 năm 2024: Cơ sở dữ liệu lỗ hổng quốc gia (NVD) đã công bố CVE-2024-53900 cho lỗ hổng này.
- Ngày 17 tháng 12 năm 2024: Khi phân tích bản vá 8.8.3 của Mongoose, Dat Phung đã tìm thấy một cách bỏ qua vẫn cho phép RCE (Thực thi mã từ xa). Một báo cáo bảo mật chi tiết đã được gửi tới Tidelift.
- Ngày 13 tháng 1 năm 2025: Mongoose phát hành phiên bản 8.9.5, giới thiệu bản vá nâng cao giúp giải quyết vấn đề bỏ qua một cách hiệu quả.
- Ngày 15 tháng 1 năm 2025: Cơ sở dữ liệu lỗ hổng quốc gia (NVD) chính thức công bố CVE-2025-23061, nhấn mạnh mức độ nghiêm trọng của lỗ hổng mới được xác định.
Phương pháp Populate() của Mongoose
Mongoose cũng cung cấp một tính năng hữu ích gọi là populate() giúp tăng cường khả năng làm việc với các mối quan hệ giữa các tài liệu. Trong khi các phiên bản MongoDB ≥ 3.2 có toán tử tổng hợp $lookup cho các phép nối, populate() của Mongoose cung cấp một giải pháp thay thế mạnh mẽ hơn để tự động thay thế một tham chiếu bằng dữ liệu tương ứng từ các tài liệu liên quan. Điều này đặc biệt hữu ích để quản lý các mối quan hệ giữa các bộ sưu tập MongoDB khác nhau, chẳng hạn như khi một tài liệu tham chiếu đến một tài liệu khác theo _id của nó. [2]
Khi định nghĩa một lược đồ trong Mongoose, một trường có thể được thiết lập để tham chiếu đến một mô hình khác bằng tùy chọn ref. Sau đó, phương thức populate() được sử dụng để thay thế trường được tham chiếu (ObjectId) bằng toàn bộ tài liệu từ mô hình liên quan. Ví dụ, trong ứng dụng cửa hàng sách, trường tác giả trong bookSchema tham chiếu đến tài liệu Tác giả và trường đánh giá tham chiếu đến tài liệu Đánh giá . Phương thức populate() cho phép các nhà phát triển thay thế trường tác giả (là ObjectId) bằng toàn bộ tài liệu Tác giả khi truy vấn mô hình sách.
Phương thức populate() cho phép các nhà phát triển thay thế trường tác giả (là một ObjectId) bằng toàn bộ tài liệu Tác giả khi truy vấn mô hình sách :
Hơn nữa, phương thức populate() của Mongoose hỗ trợ các truy vấn tùy chỉnh để xác định các tài liệu liên quan nào được truy xuất và cách chúng được truy xuất. Các thuộc tính như match và options cho phép các nhà phát triển lọc, sắp xếp, giới hạn và bỏ qua các tài liệu liên quan, cung cấp khả năng truy xuất dữ liệu linh hoạt.
Phân tích CVE-2024-53900
Là một phần của OPSWAT Chương trình Học bổng sau đại học về an ninh mạng, trong khi phân tích Mongoose để tái tạo các CVE đã biết, Dat Phung đã tiến hành đánh giá toàn diện về hoạt động bên trong của phương thức populate(), đóng vai trò quan trọng trong việc xử lý các mối quan hệ giữa các tài liệu MongoDB. Phương thức populate() hỗ trợ cả đối số chuỗi và đối tượng, và các nhà phát triển có thể sử dụng tùy chọn match để áp dụng các bộ lọc cụ thể trên dữ liệu đang được truy xuất:
Trong ví dụ trên, tùy chọn match là một đối tượng bộ lọc có thể bao gồm các toán tử truy vấn MongoDB, như được trình bày chi tiết trong Query and Projection Operators - MongoDB Manual v8.0 . Một toán tử đáng chú ý là $where , cho phép thực thi JavaScript trực tiếp trên máy chủ MongoDB. Tuy nhiên, việc thực thi này trên máy chủ MongoDB bị hạn chế, chỉ hỗ trợ các hoạt động và chức năng cơ bản.
Dat Phung đã tiến hành phân tích chuyên sâu mã nguồn Mongoose để hiểu quy trình làm việc của phương thức populate() . Anh xác định rằng sau khi ứng dụng gọi phương thức populate() trên mô hình, hàm populate() sẽ được kích hoạt. Trong hàm này, Mongoose gọi hàm _execPopulateQuery() , hàm này thực thi truy vấn với toán tử $where trên máy chủ MongoDB. Sau đó, tất cả các tài liệu từ bộ sưu tập nước ngoài được truy xuất để điền vào các bước tiếp theo.
Sau khi lấy dữ liệu từ MongoDB, Mongoose thực thi hàm gọi lại _done() , gọi _assign() để chuẩn bị dữ liệu trước khi "nối" hai mô hình bằng cách gọi hàm assignmentVals() .
Lỗ hổng có thể phát sinh khi dữ liệu được truy xuất được xử lý bởi hàm assignmentVals() của Mongoose. Hàm này kiểm tra xem tùy chọn match có phải là một mảng hay không và nếu có, sẽ truyền từng toán tử cho hàm sift() . Hàm sift() , được nhập từ một thư viện bên ngoài cùng tên, xử lý các truy vấn này cục bộ trên máy chủ ứng dụng. Quá trình xử lý cục bộ này gây ra rủi ro bảo mật, đặc biệt là khi xử lý dữ liệu đầu vào do người dùng kiểm soát.
Để điều tra sâu hơn về vấn đề này, Đạt Phụng đã sửa đổi các giá trị trong tùy chọn khớp để đảm bảo các điều kiện được đáp ứng, do đó sử dụng hàm sift() để phân tích thêm luồng dữ liệu.
Với điều kiện được đặt ra, toán tử $where sau đó được truyền cho hàm sift() .
Thư viện sift là một tiện ích JavaScript nhẹ được thiết kế để lọc và truy vấn các tập hợp dữ liệu như mảng hoặc đối tượng JSON bằng cú pháp giống MongoDB. Theo tài liệu chính thức, "Sift là một thư viện nhỏ để sử dụng các truy vấn MongoDB trong JavaScript." Hàm sift() đánh giá các hoạt động lọc giống MongoDB trên máy chủ ứng dụng thay vì máy chủ cơ sở dữ liệu, điều này có thể khiến hệ thống gặp rủi ro bảo mật đáng kể khi xử lý đầu vào không đáng tin cậy.
Tiếp tục phân tích của mình, Fellow của chúng tôi đã xác định một vấn đề trong hàm createDefaultQueryTester() của thư viện sift. Hàm này chuyển đổi từng thao tác trong mảng match thành các hàm JavaScript có thể thực thi, sau đó được sử dụng để lọc và xử lý dữ liệu tài liệu MongoDB cục bộ. Để đạt được điều này, createDefaultQueryTester() gọi hàm createNamedOperation() , truyền các thao tác như $where từ mảng match làm đối số.
Đối với mỗi thao tác trong mảng khớp , createNamedOperation sẽ kiểm tra xem thao tác đó có được hỗ trợ hay không, sau đó truyền thao tác đó cho hàm tương ứng.
Nếu phép toán là $where , một hàm JavaScript sẽ được tạo bằng cách sử dụng giá trị "params" thô, được lấy từ toán tử $where trong mảng match và có thể được kiểm soát bởi người dùng.
CVE-2024-53900: Chi tiết khai thác
Trong khi MongoDB giới hạn việc thực thi các hàm JavaScript thông qua thao tác $where , như đã phân tích trước đó, thì hàm sift() cho phép các hàm này được thực thi mà không có những hạn chế như vậy. Việc thiếu xác thực đầu vào và hạn chế này tạo ra một lỗ hổng bảo mật đáng kể, vì giá trị "params" - được kiểm soát trực tiếp bởi đầu vào của người dùng - có thể bị khai thác, có khả năng dẫn đến các cuộc tấn công tiêm mã. Để xem xét vấn đề này kỹ lưỡng hơn, Dat Phung đã xây dựng truy vấn sau:
Ban đầu, truy vấn không thực hiện được quy trình khác, dẫn đến lỗi sau:
Lỗi này cho biết Mongoose cố gắng thực hiện thao tác $where trên máy chủ MongoDB trước khi chuyển quyền điều khiển cho hàm sift(). Tuy nhiên, do các hạn chế về hàm JavaScript trong mệnh đề $where của MongoDB, một lỗi xảy ra, ngăn không cho truy vấn thực hiện. Do đó, Mongoose dừng quy trình trước khi nó có thể tiếp cận hàm sift() .
Để bỏ qua hạn chế này, Fellow của chúng tôi đã tận dụng biến "toàn cục" có trên máy chủ ứng dụng, biến này không tồn tại trên máy chủ MongoDB. Cách tiếp cận này cho phép anh ấy bỏ qua hạn chế trên máy chủ MongoDB và cho phép truy vấn tiếp cận hàm sift() :
Với giá trị này, khi Mongoose thực thi thao tác $where trên MongoDB, việc không có biến "global" sẽ khiến toán tử ba ngôi (typeof global != "undefined" ?global.process.mainModule.constructor._load("child_process").exec("calc") : 1) trả về 1, ngăn MongoDB báo lỗi. Do đó, truy vấn được thực thi trên máy chủ MongoDB mà không có vấn đề gì.
Tuy nhiên, khi giá trị tương tự đạt đến hàm sift() chạy trên máy chủ ứng dụng có biến "toàn cục", nó sẽ kích hoạt việc tạo hàm sau:
Bằng chứng khái niệm thực thi mã từ xa (RCE)
Trong ví dụ ứng dụng được cung cấp ở đầu blog, nếu kẻ tấn công gửi yêu cầu sau, chúng có thể thực hiện thành công cuộc tấn công Thực thi mã từ xa (RCE):
Video trình bày Bằng chứng khái niệm cho CVE-2024-53900 ảnh hưởng đến các phiên bản Mongoose trước 8.8.3, thiếu xác thực đầu vào phù hợp để ngăn chặn việc sử dụng sai toán tử $where cùng với thư viện sift .
Bản sửa lỗi chưa hoàn thiện và CVE-2025-23061
Dựa trên báo cáo bảo mật của Dat Phung, Mongoose đã giới thiệu một bản vá nhằm giải quyết lỗ hổng đã xác định trước đó (CVE-2024-53900) trước khi công khai. Bản vá có liên quan ( Automattic/mongoose@33679bc ) đã thêm một kiểm tra để không cho phép sử dụng $where trong thuộc tính match được truyền cho populate() .
Đoạn mã này kiểm tra xem thuộc tính match được truyền vào populate() có phải là một mảng không. Nếu có, mã sẽ lặp qua từng đối tượng trong mảng để xem nó có chứa toán tử $where không. Nếu phát hiện thấy $where , một lỗi sẽ được đưa ra, ngăn chặn payload độc hại lan truyền đến hàm sift() rủi ro.
Do đó, tải trọng khai thác CVE-2024-53900 không vượt qua được kiểm tra này vì một đối tượng trong mảng khớp chứa $where , do đó chặn không cho nó tiếp cận sift() .
Mặc dù bản cập nhật này chặn đúng cách việc sử dụng trực tiếp $where trong một cấp độ lồng nhau duy nhất, nhưng nó lại không phát hiện được $where khi nhúng bên trong toán tử $or - một cấu trúc mà cả MongoDB và thư viện sift đều hỗ trợ đầy đủ.
Kết quả là, kẻ tấn công có thể lồng $where bên dưới $or để tránh kiểm tra một cấp của bản vá. Vì Mongoose chỉ kiểm tra các thuộc tính cấp cao nhất của mỗi đối tượng trong mảng khớp, nên tải trọng bỏ qua vẫn không bị phát hiện và cuối cùng sẽ đến được thư viện sift, cho phép RCE độc hại.
Bằng chứng về khái niệm cho CVE-2025-23061
Để minh họa bản chất chưa hoàn thiện của bản sửa lỗi, Dat Phung đã xây dựng lại ứng dụng ví dụ bằng phiên bản Mongoose 8.9.4 (sau 8.8.3). Bằng cách lồng $wher e bên trong mệnh đề $or , kẻ tấn công có thể bỏ qua thành công việc kiểm tra và đạt được RCE.
Bản khai thác bằng chứng khái niệm cho thấy CVE-2025-23061 có thể được kích hoạt trong các phiên bản Mongoose trước 8.9.5, cho phép kẻ tấn công thực thi mã tùy ý trên máy chủ:
Giảm nhẹ và hướng dẫn
Để giảm thiểu các lỗ hổng mà chúng tôi đã thảo luận ở trên, vui lòng đảm bảo hệ thống của bạn được cập nhật lên phiên bản mới nhất của Mongoose.
MetaDefender Core Sử dụng SBOM Engine có thể phát hiện lỗ hổng này
OPSWAT MetaDefender Core , được trang bị SBOM tiên tiến ( Software Khả năng của Bill of Materials), cho phép các tổ chức có cách tiếp cận chủ động trong việc giải quyết các rủi ro bảo mật. Bằng cách quét các ứng dụng phần mềm và các phụ thuộc của chúng, MetaDefender Core xác định các lỗ hổng đã biết, chẳng hạn như CVE-2024-53900 và CVE-2025-23061, trong các thành phần được liệt kê. Điều này trao quyền cho các nhóm phát triển và bảo mật ưu tiên các nỗ lực vá lỗi, giảm thiểu các rủi ro bảo mật tiềm ẩn trước khi chúng có thể bị các tác nhân độc hại khai thác.
Dưới đây là ảnh chụp màn hình của CVE-2024-53900 và CVE-2025-23061, được phát hiện bởi MetaDefender Core với SBOM:
Ngoài ra, CVE cũng có thể được phát hiện bởi MetaDefender Software Supply Chain , tận dụng MetaDefender Core với SBOM để xác định những lỗ hổng này.