Casino (Medium) β From Source Map to Root π° | Hack Smarter World
Sometimes you start testing a login page expecting SQL injection, and end up finding something completely different. π
This is my walkthrough of Casino (Medium), a Web and Linux challenge on Hack Smarter World. The objective was to identify vulnerabilities in a resort's guest Wi-Fi portal and escalate privileges to root.
The journey involved a forgotten JavaScript source map, an exposed API, Server-Side Template Injection (SSTI), SSH key exposure, credentials in Bash history, and finally, a root password sitting in a log file.
Let's get into it!
1. Port Scanning with Nmap
I started with an Nmap scan to identify the open ports and running services.
nmap -sC -sV -p- <TARGET_IP>
The interesting results were:
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.18
80/tcp open http Werkzeug httpd 3.1.8 (Python 3.10.18)
2222/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u7
Three open ports, two SSH services, and a Python-based web application. Port 80 looked like the obvious starting point, so I opened the website and started exploring.
2. SQL Injection? Not Today.
The website presented a guest Wi-Fi login page asking for a room number and last name.
My first thought was SQL injection. I tried a few basic payloads against the last_name parameter:
curl -X POST "http://<TARGET_IP>/login" \
-d "room_number=101&last_name='" -i
curl -X POST "http://<TARGET_IP>/login" \
-d "room_number=101&last_name=xyz' OR '1'='1" -i
curl -X POST "http://<TARGET_IP>/login" \
-d "room_number=101&last_name=xyz' UNION SELECT 1,2,3-- -" -i
Every request returned the same authentication failure.
I also tried an injection payload against the room number, but the application returned an invalid-format message.
No useful behavior, no SQL errors, and no login bypass.
Lesson: Don't get emotionally attached to a vulnerability just because it looks promising. The login form wasn't cooperating, so I moved on.
3. Finding the JavaScript Source Map
While inspecting the login page's source, I noticed this line:
<script src="/static/js/app.min.js"></script>
I opened the JavaScript file and found:
function initPortal(){console.log("Hack Smarter World WiFi Gateway Active");}
document.addEventListener("DOMContentLoaded",initPortal);
//# sourceMappingURL=app.min.js.map
That last line caught my attention:
//# sourceMappingURL=app.min.js.map
A source map can reveal the original source code behind minified JavaScript. Naturally, I checked whether it was publicly accessible.
curl -s http://<TARGET_IP>/static/js/app.min.js.map
The response contained:
{
"version": 3,
"file": "app.min.js",
"sources": ["src/api/roomVerification.js"],
"sourcesContent": [
"// Front-Desk Kiosk API verification helper\nasync function checkRoomStatus(roomNum) {\n const res = await fetch('/api/v1/rooms/status?status=occupied');\n return await res.json();\n}"
]
}
And there it was:
/api/v1/rooms/status?status=occupied
A previously undiscovered API endpoint. Time to investigate. π
4. An Unauthenticated API
I visited the endpoint:
curl -s \
"http://<TARGET_IP>/api/v1/rooms/status?status=occupied"
The response contained multiple guest records, including room numbers, names, and checkout dates.
The interesting part? The endpoint didn't require authentication.
The login page was asking for guest details while another endpoint was exposing them. That's one way to make authentication unnecessarily difficult for legitimate users and unnecessarily easy for attackers. π
I examined the checkout dates to identify active reservations and used a valid room-number-and-last-name combination from the lab to log in.
The application issued a session cookie and redirected me to the dashboard.
Login bypass achieved. No SQL injection required.
5. SSTI β The Profile Field Was the Next Clue
Once inside the dashboard, I found an edit-profile option where I could change my preferred name.
The application was running Flask and Werkzeug, so I decided to test the field for Server-Side Template Injection.
I started with the classic payload:
{{7*7}}
The application returned:
49
Then I tried:
{{7*'7'}}
The result was:
7777777
This behavior is consistent with Jinja2, which uses Python expression semantics for these operations.
The field wasn't simply displaying my inputβit was evaluating it as a template expression.
A preferred-name field should display your name, not start doing your maths homework. π
6. From SSTI to a Shell
The successful template-expression tests suggested SSTI. Further testing in the lab confirmed that the vulnerability could be escalated to operating-system command execution.
I obtained a shell as:
www-data
This gave me an initial foothold on the system.
The important point is that SSTI can become a serious vulnerability when untrusted input is interpreted as template code. In this case, the impact went beyond evaluating arithmetic expressions.
7. Finding George's SSH Private Key
With access as www-data, I began examining the filesystem and looking for files that the compromised account could read.
Inside George's home directory, I found SSH key material:
/home/george/.ssh/
I checked the directory permissions and found that the private key, id_rsa, was readable by other users.
Its permissions were effectively 644:
-rw-r--r-- 1 george george ... id_rsa
A private key that everyone can read isn't very private anymore. π
I copied the key, restricted its permissions locally, and used it to connect through SSH on port 2222:
chmod 600 george_id_rsa
ssh -i george_id_rsa george@<TARGET_IP> -p 2222
The connection succeeded, giving me access as George.
One careless file-permission setting had turned a low-privileged web-server compromise into another user's shell.
8. George to David β Bash History to the Rescue
Next, I checked George's privileges:
sudo -l
No useful sudo access.
So I moved on to shell history:
cat ~/.bash_history
Among the commands, I found a sequence involving a switch to the david account and a database connection.
The problem was that a password had been typed directly into the shell instead of being entered at the interactive password prompt.
That meant the credential was sitting in Bash history in plaintext.
There was also an attempt to clear the history:
history -c
Unfortunately, clearing the current in-memory history doesn't guarantee that previously written history files or other session records are erased.
I used the recovered credential in the lab to switch to David.
Another account, another step forward.
Takeaway: Shell history is great for remembering commands, but terrible as a place to store passwords.
9. David to Root β The Provisioning Log
As David, I checked sudo privileges again:
sudo -l
No direct route to root.
However, I noticed that David belonged to the adm group. On many Linux systems, this group can read certain system logs.
I inspected /var/log and found:
/var/log/provisioning.log
The file permissions showed that it was owned by root:adm and readable by the group.
I opened it:
cat /var/log/provisioning.log
The log contained a root password in plaintext.
At this point, the system had essentially left the final key in a file that my current user was allowed to read. Not exactly Fort Knox. π
I used the recovered credential through the lab's local authentication mechanism and checked my privileges:
id
The output confirmed root access:
uid=0(root) gid=0(root) groups=0(root)
Root obtained! π―
10. Attack Chain Summary
Here's the entire path in one place:
- Nmap identified the available services.
- SQL injection attempts against the login form failed.
- The JavaScript source map revealed an undocumented API endpoint.
- The unauthenticated API exposed guest information.
- A valid guest record enabled login to the portal.
- SSTI in the profile field led to command execution as
www-data. - A world-readable SSH private key enabled access as George.
- George's Bash history exposed David's credentials.
- David's
admgroup membership allowed reading a provisioning log. - The log exposed a root credential, leading to root access.
11. What Could Have Prevented This?
- Source maps: Avoid exposing sensitive implementation details in production source maps.
- API security: Enforce authentication and authorization on every sensitive endpoint.
- SSTI: Never treat user-controlled input as template source code.
- SSH keys: Restrict private keys to their intended owners.
- Bash history: Avoid entering passwords directly into shell commands.
- Logs: Never write reusable credentials into application or provisioning logs.
- Least privilege: Restrict access to user directories and sensitive log files.
Final Thoughts
Casino was a good reminder that penetration testing isn't just about running tools and firing payloads at a login form.
My first instinct was SQL injection, but the real route started with a forgotten source map. That led to an exposed API, then SSTI, and eventually a chain of local privilege-escalation mistakes.
The same problem kept appearing in different forms: sensitive information was accessible where it shouldn't have been.
A source map exposed an API. A private key was readable by other users. A password survived in shell history. A root credential ended up in a log file.
Different mistakes, same result.
Sometimes you don't need a fancy exploit when the target is already leaking the keys to the kingdom. π
References
- SSTI Write-up β VulnMachines
- HackTricks β Server-Side Template Injection
- PayloadsAllTheThings β Server-Side Template Injection
- Flask/Jinja2 SSTI Example
Top comments (0)