Phân tích lỗ hổng Deserialization trong Bitbucket CVE-2022-26133
Bài đăng này đã không được cập nhật trong 2 năm
Về desialization là gì thì anh em có thể tham khảo thêm ở đây.
1. Tìm hiểu về lỗi
Trên Jira của Atlassian đã mô tả về lỗi như sau:
Lỗ hổng này là lỗ hổng Deserialization của Hazelcast nhúng trong Bitbucket, tương tự CVE-2016-10750 của phiên bản Hazelcast cũ nhưng ở lỗ hổng mới này thì chỉ ảnh hưởng đến Bitbucket mà thôi.
Bitbucket sử dụng thằng Hazelcast để lưu trữ dữ liệu vì nó là một nền tảng tính toán và lưu trữ dữ liệu trong bộ nhớ phân tán. Chi tiết như nào thì mình cũng chịu.
Theo mô tả trên NVD, lỗ hổng xảy ra ở chức năng SharedSecretClusterAuthenticator của Bitbucket, nó nằm ở bitbucket-service-impl/com/atlassian/stash/internal/cluster/SharedSecretClusterAuthenticator.class. Hàm runMutualChallengeResponse sẽ nhận vào 1 ObjectDataInput từ Object ClusterJoinRequest và đưa vào hàm receivceObject:
Hàm receiveObject này lại có 1 phương thức readObject, vì vậy theo lý thuyết nếu ta kiểm soát được giá trị in thì có thể chèn vào 1 đối tượng nguy hiểm để deserialize -> RCE.
Tuy nhiên để đi được vào hàm readObject này ta phải bypass qua được câu lệnh if, hàm verifyGroupName:
Hàm này kiểm tra xem groupName của input có giống với groupName của hệ thống hay không, với groupName là:
GroupName là groupName của hazelcast. Do vậy ta sẽ đi vào trong HazelcastClusterInformation.class để xem cấu hình Hazelcast Cluster. HazelcastCluster lấy config từ 1 file NetworkConfig:
Port mặc định của Hazelcast là 5701, nên ta thử netcat đến port này xem sao, và kết quả là:
Ra bkcs là tên của user chạy bitbucket. Ban đầu mình không hiểu tại sao, mãi sau mới nhận ra trong hàm verifyGroupName có câu lệnh out.writeUTF(this.groupName) để in ra cửa sổ tên groupName =)) cứ như là dev của Bitbucket cố tình để lại cái này hòng lấy được CVE vậy.
Như vậy là đã biết được tên của groupName, việc bypass câu lệnh if đầu tiên là rất đơn giản, chỉ cần truyền vào 1 giá trị là groupName là được. Vì vậy, ban đầu mình đã thực hiện tạo file tên là groupName.txt với giá trị là bkcs để gửi tới server, tuy nhiên kết quả mình nhận lại không khả quan:
Breakpoint dừng lại ở đây, và nếu chạy tiếp nó sẽ không nhảy sang BP tiếp theo. Tức là hàm readUTF này đã bị lỗi chăng? Hoặc là Hazelcast đọc dữ liệu vào kiểu khác hay như nào mình không rõ. Nhưng mình đã thử nc đến rồi lưu thằng groupName nhận được vào file:
Đọc file txt này bằng VSCode, và kết quả là đây:
Đúng là tên groupName không chỉ đơn giản là bkcs thông thường như mình nghĩ =)) Và vì vậy mình đã truyền toàn bộ giá trị groupName này vào server và kết quả nhận được là:
Nó đã nhận bkcs là remoteGroup của mình, và đã bypass verifyGroupName thành công. Note: sau vài ngày tìm hiểu thì mình đã biết được những byte đằng trước là mang độ dài của groupName đi sau. bkcs dài 4 nên những byte đằng trước sẽ là \x00\x00\x00\x04.
Việc tiếp theo là phải làm sao để chèn 1 Object vào cho nó deserialize. Để tạo object, ta sử dụng tool Ysoserial.
Điều cần quan tâm là ta sẽ sử dụng chain nào để khai thác. Lại quay trở lại advisory của bitbucket, họ nói lỗ hổng này tương tự CVE-2016-10750 của phiên bản Hazelcast cũ. Như vậy phải xem phiên bản đó sử dụng được chain nào.
Lượn lờ trên mạng về cái CVE cũ này, mình đã tìm được 1 cái ảnh poc như sau:
Hazelcast có thể bị khai thác thông qua chain CommonsBeanutils1. Hơn nữa, phân tách giữa groupName (trong ảnh là Administrator) với Object là 1 đoạn byte \xff\xff\xff\x9c là độ dài chuỗi của chain.
Tóm lại, kết quả RCE thành công.
nc 127.0.0.1 5701 > groupName.txt
java -jar ysoserial-all.jar CommonsBeanutils1 "curl localhost:8000" > object.bin
printf "\xff\xff\xff\x9c" | cat groupName.txt - object.bin > finish_payload
nc 127.0.0.1 5701 < finish_payload
All rights reserved