Một lỗi thiếu kiểm tra bảo mật trên một endpoint GET collection đã cho phép bất kỳ ai có tài khoản CoopCycle cơ bản có thể lấy toàn bộ danh bạ địa chỉ của mọi cửa hàng trong một instance dùng chung, làm lộ tên, địa chỉ đường phố và mã bưu điện của vô số khách hàng. Lỗ hổng đã được vá trong vòng hai ngày, và người dùng được khuyến cáo nâng cấp lên phiên bản mới nhất vừa phát hành.

Cách thức rò rỉ xảy ra

CoopCycle – một nền tảng logistics mã nguồn mở được các hợp tác xã giao đồ ăn sử dụng – định nghĩa API của mình bằng framework PHP API Platform. Trong framework này, mỗi thao tác (POST, GET, v.v.) phải đi kèm với một biểu thức bảo mật; nếu biểu thức này bị bỏ sót, framework sẽ thực thi mã mà không có bất kỳ bước kiểm tra ủy quyền nào.

Các nhà phát triển đã bảo vệ yêu cầu POST (dùng để tạo hoặc cập nhật danh sách địa chỉ của cửa hàng) bằng biểu thức tiêu chuẩn is_granted('edit', object). Điều này hoạt động hiệu quả vì yêu cầu này nhắm vào một thực thể cửa hàng duy nhất, cung cấp cho framework một "đối tượng" (object) cụ thể để đánh giá.

Tuy nhiên, yêu cầu GET dùng để đọc cùng tài nguyên đó lại nhắm vào một tập hợp (collection): /api/stores/{id}/addresses. Một tập hợp không có một đối tượng duy nhất, vì vậy biểu thức is_granted('edit', object) tương tự không thể áp dụng được. Do các nhà phát triển đã bỏ sót dòng bảo mật này, framework đã cung cấp dữ liệu địa chỉ cho bất kỳ người dùng nào đã được xác thực, bất kể họ thuộc tenant nào.

Trên một instance CoopCycle dùng chung, một người dùng có ý đồ xấu chỉ cần lặp qua các ID cửa hàng, gửi các yêu cầu GET đến endpoint đó và thu thập địa chỉ nhà của mọi khách hàng được lưu trữ trong hệ thống. Không cần thêm bất kỳ đặc quyền nào ngoài một tài khoản thông thường.

Tại sao lỗi này vẫn tồn tại

Vấn đề này không đơn thuần là một sự sơ suất nhỏ. Mô hình bảo mật khai báo (declarative security model) của API Platform thiếu một cách trực tiếp để diễn đạt rằng: "người dùng phải thuộc cùng một tenant với mỗi đối tượng trong tập hợp". Dòng mã bị thiếu nằm chính xác ở nơi mà framework khiến việc ủy quyền trở nên phức tạp.

Làm trầm trọng thêm vấn đề là bộ kiểm thử (test suite) của dự án thực tế lại khẳng định rằng phản hồi GET chứa tất cả địa chỉ là hành vi được mong đợi. Nói cách khác, các bài kiểm thử tự động đã vượt qua vì các dữ liệu mẫu (fixtures) được sử dụng trong quá trình kiểm thử cho phép truy cập chéo tenant, vô tình che giấu lỗ hổng. Trong trường hợp này, một bộ kiểm thử "đạt" (màu xanh) đã tạo ra một cảm giác an toàn giả tạo.

Ai được lợi và ai chịu thiệt

  • Khách hàng: Thông tin nhận dạng cá nhân (PII) của họ – bao gồm họ tên đầy đủ và địa chỉ nhà – đã bị lộ cho bất kỳ ai trên nền tảng. Mặc dù dữ liệu không được đăng công khai, nhưng vụ vi phạm đã xâm phạm quyền riêng tư của nhiều hợp tác xã khác nhau.
  • Các hợp tác xã sử dụng CoopCycle: Niềm tin vào khả năng bảo vệ dữ liệu của tenant trên nền tảng đã bị lung lay. Bất kỳ hợp tác xã nào chưa nâng cấp đều phải đối mặt với rủi ro bị lộ dữ liệu liên tục.
  • Những người duy trì CoopCycle: Phản ứng nhanh chóng của họ – đưa ra bản vá trong vòng hai ngày và bổ sung các bài kiểm thử hồi quy (regression tests) – đã hạn chế cửa sổ khai thác lỗ hổng và thể hiện trách nhiệm quản lý mã nguồn mở. Tuy nhiên, sự cố này nhấn mạnh nhu cầu về các quy trình đánh giá bảo mật chặt chẽ hơn, đặc biệt là xung quanh các thiết lập mặc định do framework điều khiển.

Những gì nhà phát triển và kiểm toán viên nên lưu ý

  • Sự bất đối xứng giữa các thao tác: Nếu một thao tác POST (hoặc bất kỳ thao tác thay đổi dữ liệu nào) trên một đường dẫn được bảo vệ nhưng lệnh GET tương ứng lại để mở, thì sự khác biệt này là một dấu hiệu cảnh báo. Lệnh POST cho thấy ý định của nhà phát triển là bảo vệ tài nguyên đó.
  • Các endpoint tập hợp (collection endpoints): Bất cứ thứ gì trả về một danh sách thay vì một mục đơn lẻ thường nằm ngoài các mô hình bảo mật thông thường. Hãy xác minh rằng các kiểm tra ủy quyền đã được thêm vào một cách rõ ràng cho các thao tác đọc hàng loạt.
  • Tính thực tế của bộ kiểm thử: Đảm bảo rằng các dữ liệu mẫu (fixtures) phản ánh đúng ranh giới giữa các tenant. Một bài kiểm thử vượt qua nhưng lại xác nhận việc rò rỉ dữ liệu chéo tenant là một dấu hiệu cảnh báo, chứ không phải là một tín hiệu an toàn.

Cách khắc phục và các bước tiếp theo

Sau khi lỗ hổng được báo cáo, đội ngũ nòng cốt của CoopCycle đã thêm biểu thức bảo mật còn thiếu vào thao tác GET collection và giới thiệu các bài kiểm thử hồi quy nhằm thực thi việc cô lập tenant cho cả các endpoint đơn mục và endpoint tập hợp. Bản vá đã được phát hành trong phiên bản tiếp theo của phần mềm.

Người dùng CoopCycle nên:

  1. Xác minh rằng họ đang chạy phiên bản phần mềm gần đây nhất.
  2. Xem xét bất kỳ tiện ích mở rộng hoặc plugin tùy chỉnh nào có thể tạo ra các lỗ hổng tương tự ở cấp độ tập hợp.
  3. Chạy lại các bản quét bảo mật, tập trung vào sự bất đối xứng giữa đọc/ghi trên tất cả các tuyến API.

Bài học rút ra

Các framework cho phép thiết lập bảo mật theo kiểu khai báo (declarative) có thể che giấu những lỗ hổng nguy hiểm khi các nhà phát triển dựa vào các mẫu (patterns) chỉ hoạt động với các đối tượng đơn lẻ. Một bước kiểm tra đơn giản—liệu phía đọc (read side) của một endpoint có cùng cơ chế bảo vệ (guard) với phía ghi (write side) hay không?—có thể làm lộ ra một nhóm các lỗi rò rỉ dữ liệu xuyên tenant (cross-tenant leaks) mà nếu không, chúng sẽ vẫn ẩn mình sau các bộ kiểm thử (test suites) đều đạt kết quả xanh.