Các nhà nghiên cứu bảo mật tại depthfirst đã công bố mã khai thác (exploit) hoạt động vào ngày 24 tháng 7 cho một lỗ hổng GitLab mà hãng đã vá sáu tuần trước đó, vào ngày 10 tháng 6. Mã này cho phép thực thi các lệnh dưới quyền git trên bất kỳ máy chủ tự quản lý (self-managed) phiên bản 18.11.3 nào chưa thực hiện cập nhật.
Bất kỳ người dùng đã xác thực nào có quyền đẩy mã nguồn (push) vào một dự án đều có thể thực hiện khai thác này. Kẻ tấn công thực hiện commit một tệp Jupyter notebook được tạo thủ công và mở phần so sánh thay đổi (commit diff) của nó, từ đó làm rò rỉ một con trỏ heap. Với đủ các lần thực hiện như vậy, một công cụ dò tìm tự động có thể xác định vị trí của các thư viện trong bộ nhớ. Sau đó, hai tệp notebook khác sẽ kích hoạt payload. Quá trình này không cần quyền quản trị viên, không cần quyền truy cập CI hoặc runner, không cần tương tác của nạn nhân và không cần truy cập vào dự án của bất kỳ ai khác.
Đáng chú ý, GitLab đã không phân loại bản vá này là một bản vá bảo mật. Một bản đánh giá của The Hacker News cho thấy việc nâng cấp Oj 3.17.3 được liệt kê trong mục sửa lỗi (bug fixes) trong bản phát hành vá lỗi ngày 10 tháng 6, chứ không nằm trong bảng sửa lỗi bảo mật. Không có mã hiệu CVE, không có điểm số CVSS và không có đề cập nào đến chuỗi tấn công qua notebook-diff. Các quản trị viên hệ thống khi phân loại bản phát hành đó dựa trên bảng bảo mật đã không có lý do gì để coi đó là việc khẩn cấp.
Chuỗi tấn công này hoạt động nhờ vào hai lỗi tham chiếu bộ nhớ trong Oj, một trình phân tích cú pháp JSON cho Ruby được triển khai phần lớn bằng ngôn ngữ C thuần túy. depthfirst cho biết hệ thống của họ đã tự động phát hiện ra các lỗi này và các nhà nghiên cứu đã liên kết chúng lại với nhau bằng tay.
Cơ chế kỹ thuật của lỗ hổng
Trình kết xuất notebook của GitLab, một gem nội bộ có tên là ipynbdiff, chuyển dữ liệu JSON của tệp .ipynb do kho lưu trữ kiểm soát sang hàm Oj::Parser.usual.parse bên trong một tiến trình Puma worker chạy lâu dài. Do đó, các byte do kẻ tấn công kiểm soát sẽ tiếp cận được bộ nhớ C được quản lý thủ công của Oj bên trong tiến trình ứng dụng.
Một lỗi cho phép ghi đè quá mức ngăn xếp lồng nhau (nesting stack) cố định 1.024 byte cho đến khi kiểm soát được callback start của trình phân tích cú pháp. Lỗi còn lại cắt ngắn một khóa đối tượng 65.565 byte xuống còn 29 trong một trường số nguyên 16 bit có dấu và trả về một con trỏ heap đang hoạt động, sau đó GitLab sẽ hiển thị vào phần diff. Sự rò rỉ này giúp xác định vị trí của libc, và việc ghi đè sẽ trỏ callback tới hàm system().
Các thành phần bị ảnh hưởng
| Thành phần | Phiên bản bị ảnh hưởng | Phiên bản đã vá |
|---|---|---|
| GitLab CE/EE | 15.2.0 đến 18.10.7 | 18.10.8 |
| GitLab CE/EE | 18.11.0 đến 18.11.4 | 18.11.5 |
| GitLab CE/EE | 19.0.0 đến 19.0.1 | 19.0.2 |
| Oj gem | 3.13.0 đến 3.17.1 | 3.17.3 |
Tất cả các cấp độ dịch vụ đều bị ảnh hưởng, bao gồm CE và EE, từ bản Free đến Ultimate. Bản thân ngôn ngữ Ruby không bị ảnh hưởng. Phiên bản Oj 3.17.2 đã bao gồm các bản sửa lỗi khác từ cùng một đợt đánh giá nhưng không có hai lỗi này.
Người dùng được khuyến cáo nâng cấp lên các phiên bản 18.10.8, 18.11.5, hoặc 19.0.2. Cả GitLab và depthfirst đều không cung cấp giải pháp thay thế (workaround) cho những người không thể nâng cấp.
Lưu ý cho triển khai Helm và Operator
Một cái bẫy đối với người dùng Helm và Operator: hãy kiểm tra phiên bản GitLab bên trong Webservice image đang chạy Puma, chứ không phải phiên bản chart hay Operator. Bất kỳ phiên bản nào từ 15.2 đến 18.9 đều không có bản vá ngược (backport) vì những dòng này nằm ngoài chuỗi vá lỗi được duy trì bảo mật của GitLab, do đó các bản cài đặt này buộc phải chuyển sang một bản phát hành được hỗ trợ.
Các lệnh sẽ thực thi dưới quyền git, tài khoản đứng sau Puma. Mức độ nguy hiểm phụ thuộc vào cách bản cài đặt được cách ly. Các dữ liệu có thể bị tiếp cận bao gồm: mã nguồn, các bí mật của Rails (secrets), thông tin đăng nhập dịch vụ, dữ liệu CI/CD và các dịch vụ nội bộ mà ứng dụng có thể giao tiếp.
Mã khai thác công khai được xây dựng cho GitLab phiên bản 18.11.3 trên kiến trúc x86-64. Các giá trị offset của gadget, trạng thái thanh ghi và hành vi của jemalloc đều được lấy từ hình ảnh đó, và địa chỉ cơ sở của thư viện được khôi phục chỉ giữ nguyên cho đến khi tiến trình Puma master khởi động lại, vì vậy mã này không thể áp dụng ngay lập tức đối với mọi mục tiêu tùy ý.
Các lỗi trong Oj mang tính chất tổng quát; việc chuyển đổi mã khai thác sang các môi trường khác đòi hỏi nỗ lực thực sự. depthfirst ước tính mất khoảng 5 đến 10 phút để tìm kiếm bộ nhớ trên một bản cài đặt mới có hai worker và dự kiến mất từ 1 đến 2 giờ trên các bản cài đặt đã chạy lâu hơn.
depthfirst đã báo cáo các lỗi của Oj vào ngày 21 tháng 5, người duy trì đã gộp các bản sửa lỗi vào ngày 27 tháng 5 và Oj 3.17.3 đã được phát hành vào ngày 4 tháng 6. Chuỗi tấn công trên GitLab đã được gửi tới GitLab vào ngày 5 tháng 6, được xác nhận vào ngày 8 tháng 6 và được vá vào ngày 10 tháng 6. depthfirst cho biết họ chưa ghi nhận hành vi khai thác nào trong thực tế và GitLab đã tái tạo được lỗ hổng RCE một cách độc lập.