Quick note Spring Boot Fat Jar file writing to RCE (writeup)

Lâu rồi mình cũng chưa viết bài blog nào mới, do là dạo gần đây khá tụt mood, cuộc sống cứ như vòng lặp vậy, thật nhàm chán, chẳng nghiên cứu được gì nhiều. Dù vậy mình vẫn có thói quen note lại những suy nghĩ - mấy cái học được, bài dưới đây mình cũng đã viết từ trước, thấy hay nên mình lôi ra publish chống cháy.

Đây là đề mình ra trong đợt KMA-CTF 2024 ở trường, một trick khá hay trong java spring và vì muốn nó thực tế một tí nên không quá đánh đố, độ khó medium, hướng exploit cũng khá straight-forward.

Quick review

  • Challenge cho app spring boot tồn tại lỗi read/write file tùy ý.
  • Phần write file chặn upload vào 1 số file/directory như etc, proc,…
  • docker-compose.yml chặn outbound app spring
  • File flag được để trong root folder, tuy nhiên tên file bị đổi thành random, không có cách nào list được file trong folder ==> phải RCE
  • Ngoài ra app spring chạy trên docker image openjdk:8-jdk-alpine

Ý tưởng

Intended của bài là dùng trick Spring Boot Fat Jar file writing vulnerability to RCE, kỹ thuật này đã được phân tích khá chi tiết tại đây, có cả PoC:

Ý tưởng cơ bản là lợi dụng cơ chế lazy loading trong java, cơ chế này cho phép java mở các file lib trong jdk ở trạng thái Opened nhưng chưa load vội, chỉ khi thực sự dùng đến mới thực hiện load lib này vào JVM.

Ví dụ, class loading xảy ra khi ta gọi Class.forName() - lúc này class sẽ nằm trong JVM nhưng không thực thi đoạn code nào trong class. Trong khi class instantiate xảy ra khi ta tạo object mới với keyword như new hay .newInstance(), lúc này code trong constructor hay static block của class sẽ được thực thi.

Kết hợp với spring thực hiện class loading/instantiate charset từ lib/charsets.jar khi ta pass header như Accept: text/html;charset=Big5 trong request.

Trong PoC trên, class cụ thể được load sau đó instantiate là sun.nio.cs.ext.ExtendedCharsets: ExtendedCharsets

Bằng cách tạo malicious class ExtendedCharsets extends AbstractCharsetProvider -> pack thành charsets.jar -> overwrite lib này lên server -> trigger qua header Accept -> RCE

Tuy nhiên cần lưu ý do sau khi thực hiện gửi Accept header thì lib/charsets.jar sẽ được load vào JVM cho nên nếu lần đầu exploit thất bại thì không thể tiếp tục overwrite để load class lại nữa.

Ngoài class ExtendedCharsets thì vẫn còn những class khác có thể lợi dụng, khuyến khích người đọc có thể nghiên cứu thêm.

Exploit

  1. Tạo malicious jar file, ở đây do challenge không có outbound nên ý tưởng output command sẽ được viết vào file rồi dùng bug file read để đọc: cmd

  2. Lấy đường dẫn thư viện jdk: environ

  3. Verify đọc đúng: verify

  4. Upload overwrite charsets.jar, do spring sẽ xử lý Accept header ở luồng trả về http response, có thể gửi luôn header này ở request upload để tiện trigger ngay sau khi request overwrite xong: upload_and_accept

  5. Đọc output command thành công, ở đây chỉ cần filename của flag là được: out_ls

  6. Flag: flag

Look back

Tricks này khá hay ho, tuy nhiên:

  1. App chạy quyền root thì mới overwrite được jdk lib
  2. App phải chạy jdk <= 8, nguyên nhân do từ JEP 220 jdk được tái cấu trúc -> module hóa -> không còn các file thư viện riêng lẻ như trước để có thể overwrite nữa, ref.
  3. Như đã đề cập, khi gửi header Accept, lib/charsets.jar sẽ được load vào JVM và không thể load lại lần 2, trong thực tế trường header này cũng khá phổ biến nên trên các server medium/high-traffic, khả năng cao thư viện cũng đã được load nên nếu thế cũng không thể exploit ăn ngay