The task is quite obvious from the appearence of the given website.
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.
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
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)'))
;
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)
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โฆ
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());