For the complete documentation index, see llms.txt. This page is also available as Markdown.
WEB

Fools Mate, Revenge

Do you have what it takes to defeat me and claim your prize? - by DrGonz0

The following post by 0xb0b is licensed under CC BY 4.0


Scenario

I see my client-side defences were no match for you, well done, my apprentice! Let's see if you have what it takes to claim your prize.

You can access the web app from your AttackBox's browser via: http://IP:3000

Summary

Summary

In Fools Mate, Revenge, we almost face the same app but this time the reward gate checks a session.config.unlocked flag before releasing the flag. Since session.config is a plain object, any property it lacks is inherited through the prototype chain. Testing reveals that /api/settings merges user-supplied JSON without sanitizing dangerous keys, allowing a constructor.prototype payload to pollute Object.prototype globally. By injecting unlocked: true this way and then submitting the same checkmate move (a1a8) within the same session, session.config.unlocked resolves to true via the polluted prototype revealing the flag.

Exploitation

We'll start right away by visiting the page as specified in the scenario. Here we have a chess game like in Fools Mate infront of us. We are able to reset postions, make our moves and also save some preferences.

We're testing the application and examining each request in BurpSuite. We try to change settings...

And repeat our step like in Fools Mate to catch a benign move and make it a checkmate move afterwards. We are able to make our checkmate move, but we do not receive a reward this time. The server explicitly states that the reward is blocked by a not set session.config.unlocked object.

Before we continue, we reset our positions. As the game has ended.

Next, we test for prototype pollution on other endpoints, hypothesizing that if any part of the app merges user-supplied JSON into an object without sanitizing dangerous keys (__proto__, constructor, prototype), we could inject properties directly onto Object.prototype. Since session.config is a plain object, any property it doesn't already define would be inherited from the prototype chain. Meaning we don't need to write to session.config directly, only to Object.prototype itself.

We find that /api/settings is vulnerable. Sending a normal-looking settings update alongside a constructor.prototype payload succeeds in polluting the global prototype as we get the reward after making our checkmate move.

After we make our checkmate move with the same session we retrieve the flag.

We recreate the steps taken in BurpSuite using cURL.

First we reset the game session on the server while using a shared cookie file (-c to save, -b to send) so the session persists into your next curl call.

Next, we send a POST request to /api/settings including the prototype-pollution payload constructor.prototype.unlocked: true, reusing the same cookie jar so this request lands in the session we just reset. The goal is to pollute Object.prototype so that any object without its own unlocked property like session.config will inherit unlocked: true through the prototype chain.

Finally, we send the checkmate move (a1 to a8) to /api/move using the same cookie jar, so the server checks session.config.unlocked on the reward gate. Like tested before in BurpSuite the prototype pollution from the previous step lands and config.unlocked now resolves to true via the polluted Object.prototype. We are now able to make our move and it gets evaluated as unlocked, revealing the flag.

Last updated