Breaking Grad HackTheBox Write-up

d4rkstat1c
5 min readJun 19, 2021

--

A Node.Js CTF providing various bugs that require different methods to develop the correct payloads for exploitation. Hopefully this write-up can help others seeking to learn Node.Js exploitation techniques.

0x01: Digesting the code base.

Like ImageTok and MrBurns this challenge allows the CTF player to download the code-base for code-logic comprehension and exploit development. Diving right into the code-base reveals some interesting logic worth noting in the /challenge/routes/index.js file:

The web-application’s developer set up two routes for this web application:

GET: /debug:action

POST: /api/calculate

I decided to investigate the /debug route which ultimately calls the execute method located in DebugHelper.js:

DebugHelper.js

Via HTTP GET method if we supply the version URI parameter to the /debug route/endpoint we will reach the version code-block which executes a call to the child_process.fork function with the ‘VersionCheck.js’ string as its first argument, a empty array to meet the method’s signature as its second argument and for its final third argument a dictionary with stdio as its key with the values being the correct input/output pipes. Upon multi-process execution the web-application will redirect the stdout and stderr of the child process to the response sent to the client/user. Else if the URI parameter is ‘ram’ the web-application will send within the response the output of ‘free -m’ executed via execSync to the client/user.

In order to reach /api/calculate’s corresponding code-block in the ObjectHelper.js module/file we will need to send a POST request to the /api/calculate URI with JSON data supplied as the post-data and set the Content-Type header to ‘application/json’. This JSON post-data will be passed to the following method:

This method immediately stuck out to me giving off prototype pollution vibes due to the insecure implementation of the merge function. I.e. since an attacker/we can control the parsed JSON data passed to the source parameter via a POST request, it is possible to send JSON data with key-value pairs. If the key within the JSON data set to ‘__proto__’ the attacker can additionally set the corresponding value of ‘__proto__’ as another key-value pair containing an arbitrary property and then set arbitrary values to the target property or properties via another key value pair, for example the following JSON POST data:

{ ‘__proto__’: { ‘target_property’: ‘value’ } }

Will translate to:

source.__proto__.target_property = ‘value’;

This enables an attacker to set the target_property value for all existing objects of the application. This is due to the prototype object containing a constructor method which points to the function itself and the constructor of the constructor is the global function constructor. This means all objects will inherit the modified property values.

0x02: Exploit development.

In order to exploit prototype pollution three conditions must be met:

  1. Object recursive merge (insecure merge):

2. Property definition by path:

3. Object clone:

All three conditions are met within this web-application indicating that it does in fact contain a prototype pollution vulnerability. Now how do we exploit it? In order to exploit the prototype pollution vulnerability we must bypass the following WAF checks called within the insecure merge on source (the JSON POST request data):

This indicates that we cannot supply ‘__proto__’ as the key however if we supply ‘constructor’ and simply nest a key-value pair as constructor’s value with ‘prototype’ as the key and finally a nested key-value pair as the property we wish to modify as the key and the value of the property as the value within the JSON data like so:

{ ‘constructor’: { ‘prototype’: { ‘target_property’: ‘value’ } }

Will translate to:

source.constructor.prototype.target_property = ‘value’;

We will effectively bypass the WAF using this JSON payload.

So we now have established we can set values within the constructor’s prototype property’s properties. How can we leverage this method to gain RCE? We can utilize the constructor’s prototype property’s env property to set environment variables for the web application. We can then utilize the ‘ — require’ feature of NODE_OPTIONS property of the constructor’s prototype property to gain Node.Js RCE when a process is spawned. Since we can utilize the version method discussed in 0x01triggered via GET requests to /debug/version we are capable of spawning a new child process via child_process.fork.

Raw Node.Js POC:

source.constructor.prototype.env = { ‘x’:’console.log(require(‘child_process’).execSync(‘whoami’).toString())//’}
source.constructor.prototype.NODE_OPTIONS = ‘— require /proc/self/environ’

The flag is ‘flag_’ plus a randomly generated string:

So we will first need to read the files within the current working directory next read the flag file via the cat binary.

JSON payload POC to list files in current directory:

{“constructor”: {“prototype”: {“env”: {“x”: “console.log(require(\”child_process\”).execSync(\”ls\”).toString())//”}, “NODE_OPTIONS”: “ — require /proc/self/environ”}}}

JSON payload POC to read the flag:

{“constructor”: {“prototype”: {“env”: {“x”: “console.log(require(\”child_process\”).execSync(\”cat flag_xxxxx\”).toString())//”}, “NODE_OPTIONS”: “ — require /proc/self/environ”}}}

I wrote a python3 script to automate the exploitation process:

https://github.com/d4rk007/ctfs/blob/main/BreakingGrad/exploit.py

--

--

No responses yet