October 13, 2026
Osuvox Forums โ XSS EXPLOIT | CYBERCHESS2026
Platform: Hack The Box Category: Web Security Difficulty: Learning through practical testing Tools Used: Kali Linux, curl, Python 3, unzipโฆ

By Jitu Sah
7 min read
Platform: Hack The Box Category: Web Security Difficulty: Learning through practical testing Tools Used: Kali Linux, curl, Python 3, unzip, grep, Browser, Webhook.site
Introduction
In this lab, I worked on a challenge called Osuvox Forums. At first, it looked like a normal forum website with posts and a report feature.
I started by checking the website and testing its available routes. After that, I wanted to understand what happened when a post was reported. This led me to inspect the challenge source code and follow how the application handled user input.
This write-up covers my approach, the commands I used, and the things I learned. I have intentionally left out the final flag and the exact answer.
1. Setting Up the Lab
I used Kali Linux as my working environment. The first thing I did was store the target URL in a variable.
export TARGET="http://TARGET_IP:TARGET_PORT"export TARGET="http://TARGET_IP:TARGET_PORT"Replace TARGET_IP and TARGET_PORT with the values provided by your active lab instance.
Why did I use this command?
exportcreates an environment variable.TARGETis the variable name.- The URL is stored as its value.
This saved me from typing the complete URL every time.
Checking the website
curl -i "$TARGET/"curl -i "$TARGET/"The curl command sends an HTTP request to a server.
Here is what the options mean:
curlโ sends the request.-iโ displays the HTTP response headers and body."$TARGET/"โ specifies the website URL.
I also checked an individual post:
curl -i "$TARGET/posts/1"curl -i "$TARGET/posts/1"This request helped me check whether the post route was responding.
A successful HTTP response only tells us that the server returned a response. It does not automatically mean that the application is secure or vulnerable.
2. Testing the Report Feature
The website had a report feature, so I wanted to understand how it behaved.
I sent a normal report request using curl.
curl -sS -X POST "$TARGET/api/report" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'curl -sS -X POST "$TARGET/api/report" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'Understanding the command
PartExplanation-sSHides the progress meter but still displays errors.-X POSTSpecifies the HTTP POST method.-HAdds an HTTP header.Content-Type: application/jsonTells the server that the request body contains JSON.-dSupplies the request body.
The body contains a post ID:
{"id":"1"}{"id":"1"}The server returned a message saying that the report had been received successfully.
One important thing I learned was that an API success message does not necessarily prove that every operation behind the scenes completed successfully. I needed to understand what the server did with the submitted ID.
3. Tools Used During the Lab
Kali Linux
Kali Linux was my main working environment. I used the terminal to run commands, inspect files, and send HTTP requests.
curl
I used curl to check website routes and test the report API. It was useful because I could see the HTTP response directly in the terminal.
Python 3
Python helped me prepare JSON data and encode special characters when working with URL parameters.
unzip
I used unzip to list and extract the challenge source archive so I could inspect the application's files.
grep
grep helped me search through the source code for important keywords and functions.
Browser
I used a browser to open the challenge website and inspect the Webhook.site dashboard.
Webhook.site
I used Webhook.site to observe a harmless marker request. This helped me verify whether the browser-side test was reaching the endpoint.
Each tool had a different purpose. Using them together helped me understand the application more clearly.
4. Inspecting the Challenge Source Code
The source archive was useful because I could investigate how the application worked instead of guessing.
First, I listed the files inside the archive:
unzip -l challenge.zipunzip -l challenge.zipThe -l option lists the archive contents without extracting them.
Next, I extracted the files:
unzip -q challenge.zip -d ~/Downloads/osuvox-sourceunzip -q challenge.zip -d ~/Downloads/osuvox-source-qreduces extraction output.-dspecifies the destination directory.
I then moved into the extracted challenge folder:
cd ~/Downloads/osuvox-source/web_osuvox_forums/challengecd ~/Downloads/osuvox-source/web_osuvox_forums/challengeThe exact path can vary depending on where the archive was extracted.
Searching for important functions
grep -RniE 'report|visitPost|innerHTML|parseParams|setCookie' .grep -RniE 'report|visitPost|innerHTML|parseParams|setCookie' .This command searches files in the current directory and its subdirectories.
-Rsearches recursively.-ndisplays line numbers.-iignores uppercase and lowercase differences.-Eenables extended pattern matching.- . means the current directory.
I searched for these keywords because they were related to the report feature, browser automation, URL parsing, and page rendering.
Files I focused on
routes/index.jsโ report endpoint and bot call.bot.jsโ automated browser behavior.static/js/forum.jsโ search parameter handling.static/js/parseParams.jsโ query-string parsing.views/post.htmlโ individual post template.database.jsโ seeded posts and comments.
Reading a file with line numbers
nl -ba routes/index.js | sed -n '1,220p'nl -ba routes/index.js | sed -n '1,220p'The nl -ba command numbers every line, while sed -n '1,220p' displays lines 1 through 220.
The pipe symbol (|) passes the output of one command to another.
This made it easier to read the code and refer to particular lines.
5. Understanding the Application Flow
After inspecting the source code, I focused on how the report feature worked.
The application received a report request and passed the submitted ID to a browser bot. The bot then opened a page using a URL constructed from that ID.
This was an important detail because the application was not simply storing the report. An automated browser was also visiting a page.
I then looked at how the front-end JavaScript handled the search parameter.
The relevant operation was similar to this:
$('#search-msg').innerHTML =
`Search results for "${params.search}" :`;$('#search-msg').innerHTML =
`Search results for "${params.search}" :`;Why did this matter?
The value params.search comes from the URL query parameter.
The important part is innerHTML. It interprets a string as HTML rather than displaying it only as plain text.
When untrusted input reaches this operation without safe handling, it can lead to DOM-based cross-site scripting (DOM XSS).
This helped me understand why it is important to follow data from the URL into the page instead of examining each component separately.
A safer way to display text
For plain text, developers can use:
element.textContent = userInput;element.textContent = userInput;Unlike innerHTML, textContent treats the assigned value as text rather than parsing it as HTML.
This is one of the defensive lessons I took away from the challenge.
6. Testing Observations with Webhook.site
After studying the application flow, I needed evidence that my test was reaching the expected page.
I used Webhook.site to observe a harmless marker request.
I checked the following details:
- The time the request arrived.
- The query parameter containing the marker.
- The Referer header.
- Whether the request matched the expected test.
The Referer header can help identify the page that initiated a request.
This step taught me that it is better to verify behavior with evidence rather than rely only on a message returned by an API.
A marker request is useful because it tests whether the browser-side action occurred without immediately collecting sensitive information.
7. Python, JSON and URL Encoding
Python was useful for preparing request data.
For example:
import json
from urllib.parse import quote
value = "example text & more"
encoded = quote(value, safe="")
data = {"search": encoded}
print(json.dumps(data))import json
from urllib.parse import quote
value = "example text & more"
encoded = quote(value, safe="")
data = {"search": encoded}
print(json.dumps(data))This is a small example of URL encoding and JSON formatting.
What do these functions do?
quote()encodes special characters so they can be represented safely in a URL parameter.json.dumps()converts a Python object into a JSON string.datais a Python dictionary containing the value to be encoded.
These operations are useful when testing applications that accept JSON bodies or URL parameters.
The important thing is to understand how data is represented before sending it to a server.
8. What I Learned
This challenge helped me understand several web security concepts.
DOM-based XSS: User-controlled data can become dangerous when JavaScript interprets it as HTML.
Source-code review: Reading multiple files helped me connect the report endpoint, automated browser, and front-end JavaScript.
HTTP requests: curl made it easy to test routes and inspect responses.
Automated browsers: A bot can interact with a website differently from a normal visitor. Its behavior and permissions can affect the security of the application.
Evidence collection: An API success response, evidence of browser-side execution, and an accepted challenge result are different things.
URL encoding: Special characters need to be handled correctly when data is placed in a URL.
9. How Developers Can Prevent This Type of Issue
There are several ways to reduce the risk of similar vulnerabilities:
- Avoid inserting untrusted input directly into
innerHTML. - Use
textContentwhen displaying plain text. - Validate report IDs before using them to construct URLs.
- Restrict automated browsers to expected destinations.
- Protect sensitive cookies with suitable security attributes, including
HttpOnlyandSecurewhere applicable. - Test both normal and unexpected input.
- Keep useful logs and verify security-sensitive operations.
These measures help prevent browser-side injection and reduce the impact of unsafe application behavior.
Conclusion
Osuvox Forums was a useful practical exercise because I had to look beyond the visible website.
I started by checking the routes, tested the report feature, inspected the source code, and followed how the application handled user input. Each step helped me understand another part of the challenge.
My biggest takeaway was to avoid random guessing. Understanding the code path and verifying observations with evidence made the investigation much clearer.
Quick Command Cheat Sheet
Use this section for revision when practising in your authorized lab.
1. Set the target
export TARGET="http://TARGET_IP:TARGET_PORT"export TARGET="http://TARGET_IP:TARGET_PORT"2. Check the website
curl -i "$TARGET/"
curl -i "$TARGET/posts/1"curl -i "$TARGET/"
curl -i "$TARGET/posts/1"3. Send a normal report request
curl -sS -X POST "$TARGET/api/report" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'curl -sS -X POST "$TARGET/api/report" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'4. List the challenge archive
unzip -l challenge.zipunzip -l challenge.zip5. Extract the archive
unzip -q challenge.zip -d ~/Downloads/osuvox-sourceunzip -q challenge.zip -d ~/Downloads/osuvox-source6. Search the source code
grep -RniE 'report|visitPost|innerHTML|parseParams|setCookie' .grep -RniE 'report|visitPost|innerHTML|parseParams|setCookie' .7. Read source code with line numbers
nl -ba routes/index.js | sed -n '1,220p'
nl -ba bot.js | sed -n '1,180p'
nl -ba static/js/forum.js | sed -n '1,220p'nl -ba routes/index.js | sed -n '1,220p'
nl -ba bot.js | sed -n '1,180p'
nl -ba static/js/forum.js | sed -n '1,220p'8. Validate and format JSON
python3 -m json.tool request.jsonpython3 -m json.tool request.jsonCommand reminders
curl -iโ sends a request and displays headers.curl -sSโ hides the progress meter but shows errors.-X POSTโ specifies the HTTP method.-Hโ adds an HTTP header.-dโ supplies request body data.unzip -lโ lists archive contents.grep -RniEโ searches source files recursively.nl -baโ numbers every line.sed -nโ displays a selected range of lines.python3 -m json.toolโ formats and validates JSON.
Final revision checklist
- Can I explain how the report endpoint works?
- Can I explain why
innerHTMLcan be dangerous? - Can I explain what each tool was used for?
- Can I distinguish an API response from actual evidence?
- Can I describe the defensive fixes?
This article shares the learning process without publishing the final flag or exact answer.
Thank You.