I wanted to write up A Very Small Locker, a web challenge from Pwnsec CTF, because it had two very different solve paths.
The unintended solution is a quick NoSQL injection. The intended route is much nicer: a business logic bug, then XSS, then an IDOR-style privilege bypass to recover the leaked flag.
Unintended solve
Starting from the source code, the first important clue was that the flag lived at /master/confedential.

That page was supposed to be accessible only to masters. Since the backend used Mongoose, my first thought was NoSQL injection.
MongoDB supports operators like $regex, so if the login endpoint accepts unsanitized input, it is possible to match credentials without knowing the exact value.

After logging in as the master account, visiting /master/confedential gives the flag immediately.
That solves the challenge, but it is not the intended path.
Intended solve
The intended route was more interesting. The goal was to make the bot visit /master/confedential and leak the result somewhere I could later read.
The useful sink was /transactions/search?searchTerm=..., but before I could talk to the bot through /master, I needed more than 10000000000000 in my balance.
Step 1: Infinite money with a negative transfer
Looking at the transfer logic, the application did not block negative values.


So instead of losing money, I could transfer a negative amount and increase my own balance.
What happens if we transfer a negative number?

That gave me effectively infinite money.

Now /master was available.

Step 2: Turning the message feature into XSS
At this point I could send a message to the bot, but the page had CSP protections, so the obvious payloads failed.

I ran the challenge locally to understand exactly how the input was rendered.

The key detail was that userMessage ended up inside a JavaScript template expression like ${"test"}.
So the problem became: how do we break out of that expression and append our own one? In other words, how do we turn it into something like ${"test"}${alert()}?
After some trial and error, I got a working alert.


Once I had XSS, I used it to fetch /master/confedential and push the result into the transaction search flow with this payload:
\"}${fetch(`/master/confedential`).then(function(r){return r.text()}).then(function(d){return fetch(`/transactions/search?searchTerm=${btoa(d)}`)})}
The idea is simple:
- make the bot request
/master/confedential - read the response body
- base64-encode the content
- send it to
/transactions/search?searchTerm=...
Step 3: Reaching the leaked data
I could access the search page here:

In the transaction history, I found the master’s id.


That led to the last part of the challenge.
Step 4: Business logic bug to read the history
Taking a look at the history-checking function:

The application decided whether someone was a bank master by checking the account holder’s last name. If the last name matched the expected “Bank Master” pattern, the server treated that user as privileged.
My own bank master was named:


test's Bank Master
So I changed my last name to:
test's Bank Master's Bank Master
That way, the application treated me as the bank master of my bank master, which let me access the privileged transaction history.

Inside the history was the base64-encoded data from the XSS step.

After decoding it, I finally got the flag.