Phân tích lỗ hổng CVE-2025-24813 Apache Tomcat RCE
I. Giới thiệu
- Apache Tomcat là phần mềm miễn phí mã nguồn mở (open-source) được viết bằng Java và cung cấp người dùng một webserver sử dụng Java như back-end language (https://en.wikipedia.org/wiki/Apache_Tomcat)
- Vulnhub là một platform miễn phí mã nguồn mở được sử dụng cho security training. Vulnhub cung cấp các lỗ hổng bảo mật dưới dạng Image-container nhỏ gọn để người dùng có thể pull về và hosting các lỗ hổng mà ko cần phải tốn resource / time để setup môi trường.
- Công cụ sử dụng cho bài viết:
- Ubuntu (Máy chính)
- IntelliJ (IDE dùng để remote debug code Java từ máy ngoài vào docker)
- Docker (Vulnhub)
- Burpsuite (Intercept gói tin)
II. Nội dung
1) Setup Enviroment
- Đầu tiên, ta cài đặt OS + Docker (có thể tham khảo bài viết https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-20-04)
- Tiếp theo sử dụng git để clone repo vulnhub về
git clone https://github.com/vulhub/vulhub
- Sau đó cd vào folder tomcat/CVE-2025-24813 chạy pull + chạy image với lệnh
docker compose up -d
- Sử dụng
docker ps
để xem các docker đang chạy
- Ta có thể access được web apache tomcat bằng http://localhost:8080/ . Tuy nhiên, khi check các listen port ta thấy chỉ có port 8080 được mở nhưng ko mở port 5005 (Port 5005 thường được dùng để debug remote JVM khi phân tích các lỗ hổng. Vulnhub docker-compose.yml thông thường cũng sẽ mở + NAT port 5005 cùng với port service web. Tuy nhiên với image CVE-2025-24813 của tomcat ta xem file docker-compose.yml thì thiếu đi port debug
- Check file docker-compose.yml cũng ko thấy port 5005 ở phần NAT port
services:
tomcat:
build: .
ports:
- "8080:8080"
- Với lệnh docker ps ta có được ID của image sau đó ta có thể access trực tiếp vào trong docker image bằng lệnh
docker exec -it <ID-Image> bash
(hoặc thay bash bằng sh) - Sau khi access vào docker image, ta tiếp tục sử dụng lệnh
netstat -tulnp
(nếu net-tools ko có, có thể cài đặtapt install net-tools
- Trong docker ko có port 5050 --> Ta phải enable debug trong image docker sau đó NAT port ra ngoài --> Chỉnh port file docker-compose.yaml để NAT port 5005 từ trong docker ra interface bên ngoài:
services:
tomcat:
build: .
ports:
- "8080:8080"
- "5005:5005"
- Check ta thấy được command catalina.sh run
docker ps
--> Để debug remote JVM, ta phải add -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 vào command khi chạy java - Tại line 427 catalina.sh, thêm vào line debug (sử dụng docker cp để copy file ra / vào giữa container và máy chính)
- Chạy shutdown.sh trong /tomcat/bin và sử dụng docker compose up -d để start lại docker (Ko sử dụng docker compose down và docker compose up -d lại vì vulnhub sẽ pull lại image trên github --> file script sẽ bị xóa)
- Copy source từ container ra máy chính sau đó mở bằng IntelliJ. Chọn Run -> Edit Configuration sau đó Add New Configuration -> Remote JVM Debug
2) Remote Debug
- File web.xml với url pattern / sẽ mapping với servlet default. Check servlet default gọi đến DefaultServlet.
- Tiếp theo, khi chạy poc https://github.com/absholi7ly/POC-CVE-2025-24813 , ta thấy được breakpoint trigger ở hàm service (class DefaultServlet) (Có thể sử dụng proxychains để set proxy qua Burpsuite)
- Ở super.service sẽ gọi class cha của DefaultServlet -> HttpServlet với method là PUT
this.doPut sẽ tiếp tục gọi lại hàm doPut của DefaultServlet. Tại function doPut sẽ có 2 case với readOnly true sẽ trả về sendNotAllowed. Tuy nhiên ở web.xml phía trên param-value của readonly được set là false (đây là nguyên nhân chính gây ra poc RCE). Ko chỉ với PUT method (write file, attacker còn có thể delete file với method DELETE
Trở lại với function doPut, path file temp (file tạm) được ghi sẽ được xử lý bằng function executePartialPut. F7 vào function ta có được
Output file path ghi ra sẽ được lấy từ attribute javax.servlet.context.tempdir. Khi F7 vào hàm getAttribute ta có được value của javax.servlet.context.tempdir = ROOT path
Check usage của hàm setAttribute ko thấy được sử dụng để set attribute trong code request. Sau 1 lúc debug + search tìm được document của tomcat về phần javax.servlet.context.tempdir và default giá trị của nó sẽ là $CATALINA_HOME/work
Tuy nhiên, ta có thể set value của javax.servlet.context.tempdir bằng context.xml (tham khảo tại https://stackoverflow.com/questions/4750351/how-do-i-set-javax-servlet-context-tempdir-in-tomcat) Sau khi write temp file, this.resources.write sẽ tiếp tục write từ temp file vào path với path được lấy giá trị function getRelativePath ở line 320 sẽ có giá trị là URI access trực tiếp từ request
Tiếp tục access vào method write của object this.resources (Đây là method chính để ghi file từ file temp sang file payload upload lên (có thể là webshell hoặc write session deserialize -> rce)
Flow sẽ gọi tiếp this.main.write, F7 để Step Into sẽ vào DirResourceSet class, tại đây sẽ gọi method this.file, tiếp tục F7 tại function này...
Function file sẽ tạo File object với params là property fileBase và path được truyền vào với params là name.
--> File được tạo ra sẽ là ROOT path + URI
III. Kết luận
- CVE-2025-24813 Apache upload file -> RCE chỉ xảy ra khi config web.xml ở params readonly bị thay đổi từ false thành true, ở trường hợp bị thay đổi này ko riêng upload RCE, attacker có thể phá server bằng cách method DELETE và delete các file quan trọng ở trên server.
- Tuy nhiên flag readonly mặc định sẽ bị disable nên nếu developer vô tình enable hoặc params bị enable như một "Foothold" (Attacker sẽ enable flag và upload webshell + do something.... sau đó covertrack webshell và để lại 1 flag như 1 lỗ hổng để quay lại) thì server mới dính lỗ hổng.
- Đây là bài viết mình tự setup + debug nên khó tránh sai sót, mong mọi người thông cảm, cảm ơn mọi người đã đọc.
Nguồn tham khảo:
All rights reserved