Try โ€‚โ€‰HackMD

SECCON 2020 Online CTF - Capsule & Beginner's Capsule - Author's Writeup

Author

@hakatashi

Challenge Summary

The task is quite obvious from the appearence of the given website.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

You can arbitrarily change the code below the given header and the task is to get the value stored in ESNext's Private Class Field. Decapsulation.

const fs = require('fs');
const {enableSeccompFilter} = require('./lib.js');

class Flag {
  #flag;
  constructor(flag) {
    this.#flag = flag;
  }
}

const flag = new Flag(fs.readFileSync('flag.txt').toString());
fs.unlinkSync('flag.txt');

enableSeccompFilter();

// input goes here

enableSeccompFilter function is just for preventing dumb solution to read out /proc/self/mem so you don't have to care about it.

Beginner version

Almost the same, but with TypeScript.

import * as fs from 'fs';
// @ts-ignore
import {enableSeccompFilter} from './lib.js';

class Flag {
  #flag: string;
  constructor(flag: string) {
    this.#flag = flag;
  }
}

const flag = new Flag(fs.readFileSync('flag.txt').toString());
fs.unlinkSync('flag.txt');

enableSeccompFilter();

// input goes here

Beginner's Capsule

The private field in TypeScript is implemented with WeakMap. If you transpiled the given code to JavaScript, the result will be like the following.

var _flag;
class Flag {
    constructor(flag) {
        _flag.set(this, void 0);
        __classPrivateFieldSet(this, _flag, flag);
    }
}
_flag = new WeakMap();

_flag is easily accessible from the same scope, so you can get the flag by console.log(eval('_flag.get(flag)'));

Capsule: Intended Solution

You can access V8's inspector API from Node.js and get hidden property like this.

Note that you must put flag variable to the global scope to inspect.

global.flag = flag;
const inspector = require('inspector');
const session = new inspector.Session();
session.connect();
session.post('Runtime.evaluate', {expression: 'flag'}, (e, d) => {
  session.post('Runtime.getProperties', {objectId: d.result.objectId}, (e, d) => {
    console.log(d.privateProperties[0].value.value);
  });
});

(I didn't know it, but this is mentioned here)

Capsule: Unintended Solution

You can inject fake require function using function hoisting and read flag.txt before it is removed.

function require() {
  const fs = process.mainModule.require('fs');
  console.log(fs.readFileSync('flag.txt').toString());
}

So basicโ€ฆ

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More โ†’

Capsule: Unintended Solution

We expected Node.js cannot directly read memory without /proc/self/mem, but actually it is possible using v8.getHeapSnapshot.

const v8 = require('v8');
const memory = v8.getHeapSnapshot().read();
const index = memory.indexOf('SEC' + 'CON');
const len = memory.slice(index).indexOf('}');
const flagBuffer = memory.slice(index, index + len + 1);
console.log(flagBuffer.toString());