On my YouTube channel, I’m trying to teach about computer security by showing how to solve a capture the flag challenges. Lately, I’ve published a new video showing how to exploit Prototype Pollution. I’ve tried and solved a task from this year’s edition of TJCTF named Fruit Store. This article is a companion to that video focused on explaining what is Prototype Pollution and how to use this vulnerability in Node.js to change the application flow and bypass security mechanisms. You can find the video on my YouTube channel: 👉 https://youtu.be/MzlZIJjqsVE Let’s get started… What are prototypes? JavaScript got an unusual way of handling inheritance. Back in the old days unlike other object-oriented languages, it didn’t use classes and related mechanisms to cover it. There was no base class that we could inherit properties from. But any object could have a thing called a prototype. And what’s that? Creating an object instance in JavaScript, developers can set a “parent” object. An object that can contain methods or fields that, if not overwritten will also be accessible in the “child” object. An example of how it can be done is to use method passing any other object that will act as a parent. It will become its prototype. Object.create An object is kind of linked with its prototype and the prototype object can have a prototype of its own making a tree of a linked objects that we can call a prototype chain. Let’s consider the following example: The code on the left is responsible for creating a prototype chain (visualised on the right). A generic MacBook object stating that every MacBook is produced by Apple is the top-level parent, then we have a specific model - MacBook Air and at the end, we create an object representing the unit I’m writing this article on - my Mac. All these are connected as one became a parent of the other. In the case presented above, when the field is called JavaScript looks for it in first, but when it’s not found then it checks its prototype and its prototype… until it’s found. myMac.manufacturer myMac This mechanism, although being a pretty elegant alternative to more traditional inheritance have one huge flaw that allows Prototype Pollution to be exploited. What’s that? Let’s make a mess A prototype can be accessed with the built-in field that is part of every object. A thing worth noting is that all the objects created with the curly braces syntax have the same prototype by default. __proto__ {}.__proto__ === {}.__proto__ //true This makes the prototype chain a bit more interesting as most of the objects living in the Node.js application have the same ancestor. What does it mean for us? If we will be able to access the prototype of a single object created this way and place there a malicious field or method then every single object in the application will have it present as well. Figuring it out the right way can help us to change application execution flow in a very dangerous way… Let’s consider an example of an app we can exploit and try to use what we know so far to use that vulnerability. Becoming an admin The example app that I’m showing in my YouTube video got an endpoint that allows adding an additional amount of money to our account, but only if the field is set to true. Normally to become an admin we have to come from the local network which is not possible in that case. req.session.admin app.post('/api/v1/money', (req, res) => { if (req.session.admin) { req.session.money += req.body.money; res.send('Money added'); } else { res.status(403).send('Not admin'); } }); Hopefully, the session object is created in the way making it a part of the global prototype chain, because in this case polluting the base prototype with the field would also be reflected here. admin = true How would it work for us? As we are not an admin, the conditional expression in the second line of the code above would normally get us to the else statement saying “Not admin”. But if we pollute the app then reading would not stop on the session object, but move up the prototype chain until it will find the field in the base prototype changing the execution flow and allowing us to add money to our session. req.session.admin admin = true The application is parsing user input sent as JSON. While decoding it, it creates an object using the keys provided in the request. Will this part be vulnerable? What if we send a request like this? { "__proto__": { "admin": true } } Unfortunately, not much… method that is used in this case got it figured out and has appropriate mechanisms in place to prevent assigning the admin field to the prototype. In fact, it will just create a field in our request object instead of polluting its parent. JSON.parse __proto__.admin So? Are we doomed? Not yet. We just need to find a place in the app that is vulnerable. Parsing JSON is not that place, but if we only can find a loop going through all the request keys, blindly rewriting them to another object then we are home! Final exploit Looking into the rest of the application source code we can find the endpoint with the following code /api/v1/sell app.post('/api/v1/sell', (req, res) => { for (const [key, value] of Object.entries(req.body)) { if (key === 'grass' && !req.session.admin) { continue; } if (!fruits[key]) { fruits[key] = JSON.parse(JSON.stringify(fruit)); } for (const [k, v] of Object.entries(value)) { if (k === 'quantity') { fruits[key][k] += v; } else { fruits[key][k] = v; } } } res.send('Sell successful'); }); That’s more like it. This one goes through every field in our request body and if only the key is not “quantity”, then it just rewrites it. The line is the one we are looking for. Passing there field set to true will poison the global prototype making every object in the app have an admin field set to true. Including our session object. fruits[key][k] = v __proto__.admin So the exploit seems to be simple. Just a few steps… Become an admin using Prototype Pollution at endpoint /api/v1/sell Add money to your session using as it will now allow doing that /api/v1/money Then use the money to buy the flag (which was the goal for the TJCTF) If you want to see how I’ve solved this challenge using prototype pollution check out the full video on my YouTube channel: 👉 https://youtu.be/MzlZIJjqsVE Thanks for reading. Feel free to comment. Cheers!