TornadoService
HackTheBox
Target: http://154.57.164.69:31599
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
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); });
...
$ grep --similar
Similar writeups
- [pentest][free]Interpreter (Mirth Connect → f-string Injection)— hackthebox
- [web][free]Browsed— hackthebox
- [web][free]AgriWeb— hackthebox
- [web][Pro]Pavel— alfactf
- [pwn][Pro]echo counter— spbctf