#AshenVault Writeup
#TL;DR
AshenVault là một Web CTF Challenge giải HTB Human Only, challenge này khai thác lỗ hổng CVE-2025-24813 trên Apache Tomcat. Attacker lợi dụng tính năng Partial PUT (sử dụng header Content-Range) để ghi file, tạo file session độc hại trên server. Kết hợp với cấu hình PersistentManager xử lý session, việc này dẫn đến Insecure Deserialization và RCE.
#Phân tích
Đây là một ứng dụng web được xây dựng trên nền tảng Apache Tomcat, sử dụng Java Servlet để xử lý các request:
Sau khi thử tương tác với challenge, ta thấy chức năng filter nội dung chỉ thực hiện ở frontend, và backend cũng không có API gì đặc biệt.
Partial PUT để ghi file session độc hại
Tại Dockerfile, ta thấy được ứng dụng sử dụng Apache Tomcat 9.0.98:
Sau khi search về CVE trên Tomcat 9.0.98, ta có thể thấy được có vẻ có 2 CVE:
Để đọc được flag, tôi nghĩ cần phải RCE để có thể gọi đến /readflag, vậy nên CVE-2025-24813 là thứ ta cần quan tâm
Qua đọc bài phân tích khá chi tiết về CVE này, tôi có thể diễn giải theo ý hiểu của bản thân là:
- Lợi dụng Partial PUT: Apache Tomcat hỗ trợ tính năng
Partial PUTcho phép client ghi file bằng cách sử dụng headerContent-Range,Partial PUTcó thể bị tận dụng để khai thác ghi đè file cụ thể hoặc bypass một số kiểm tra khi không được xử lý đúng cách
-
Giờ tôi cần kiểm tra xem liệu challenge này có thể sử dụng
Partial PUTkhông, với PUT bình thường, ta có thể confirm file đã được ghi lên server -
Giờ ta sẽ kiểm tra partial PUT
-
Và khi sử dụng
partial PUT, lỗi xử lý path equivalence với ký tự/và., sau các hàm được gọi (chi tiết trong blog trên) file session sẽ được ghi tạm trong cache tại$TOMCAT_HOME/work/Catalina/<host>/<context>/executePartialPutxử lý/thành.và kết quả ta thấy fileabc.txtvừa được ghi trênwebappsvừa được ghi trong cache của Catalina
- Sử dụng PersistentManager không an toàn: Ứng dụng sử dụng
PersistentManagerlà mộtSession Managervới cấu hình không an toàn, ta có thể xem trong filecontext.xml, cho phép lưu session ra file Quá trình load session từ file sẽ trigger đếnreadObject(), cụ thể: VàPersistentManagerkhông dùng fileSESSIONS.sernhưStandardManager, nó persist/swap session thông quaFileStore, mỗi session một file trong directory. Nếu không cấu hình, thư mục mặc định là temporary work directory của webapp, thường sẽ là$TOMCAT_HOME/work/Catalina/<host>/<context>/
Với các preconditions để khai thác:
- Readonly của
DefaultServletlàfalsetrong$TOMCAT_HOME/conf/web.xml - Hỗ trợ Partial PUT (Đã confirm)
- Session persistence sử dụng
PersistentManagervớiFileStore(Cũng đã confirm)
Vậy ta có đủ các điều kiện để khai thác, và cũng như thấy được sự liên kết giữa việc ghi file ta kiểm soát dựa vào Partial PUT và load file để trigger readObject() qua việc gọi đến file session đó qua HTTP request, giờ ta chỉ cần một object độc hại để có thể RCE
RCE tận dụng parseClass()
Trong mã nguồn lib ta thấy ứng dụng sử dụng groovy 3.0.25
Và class Testing có sử dụng GroovyClassLoader để xử lý thuộc tính groovyScript của nó, mà người dùng có thể control được thuộc tính này thông qua việc khởi tạo class hoặc thông qua java reflection
Dựa vào ý tưởng của bài blog này của tác giả Orange
Ta có thể tận dụng parseClass() trong class Testing để thay đổi AST trong compile time dùng @ASTTest hoặc custom transform
Ta có thể thấy rằng method parseClass của GroovyClassLoader có nhiều overload, và như trong ứng dụng là một String thì sẽ gọi đến overload
parseClass(String text)nó sẽ compile đoạn Groovy code trong text thành một class Java và trả về chính đối tượng của "main" được định nghĩa trong code đó, tức ta có thể sửa AST trong compile time và thêm bất kì class hay thuộc tính nào ta muốn, tất nhiên ta sẽ cần RCE.
#Khai thác
Sau khi tôi phân tích 2 vấn đề trên, tôi nghĩ đã khá rõ ràng về một luồng khai thác:
Đầu tiên ta sẽ build một payload để có thể RCE
Để build payload, ta cần sử dụng groovy-3.0.25.jar
Command:
javac -cp .:com/example/groovy-3.0.25.jar com/example/Testing.java com/example/ExploitBuilder.javajava -cp .:com/example/groovy-3.0.25.jar com/example/ExploitBuilderTa thấy payload build trong file session đã thành công
Giờ ta sẽ ghi file lên server bằng partial PUT với payload này, ta sẽ base64 để tránh lỗi ở những ký tự đặc biệt
Sau đó ta gọi đến file đó bằng cách sửa header Cookie thành JSESSIONID=.pwn.session để trigger readObject() payload
Gọi đến file mà ta đã dùng payload để ghi ra và lấy flag Ta đã khai thác challenge này thành công
#Tổng kết
Một challenge khá thú vị, học được thêm kỹ thuật mới về Partial PUT và hành vi của GroovyClassLoader.parseClass() được tận dụng để khai thác, cũng như làm việc với các directory của Tomcat, enjoy~
Hi vọng bài blog này hữu ích cho các bạn đọc, cảm ơn vì đã ghé thăm :D
H4ppy H4ck1ng!