添加sandbox.js、security.js;并维护ts语法检查_(:з」∠)_
This commit is contained in:
parent
f8ae99fac4
commit
340cf27bd0
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,642 @@
|
|||
import { Sandbox, Domain, Marshal, Monitor, AccessAction, Rule, SANDBOX_ENABLED } from "./sandbox.js";
|
||||
|
||||
// 是否强制所有模式下使用沙盒
|
||||
const SANDBOX_FORCED = true;
|
||||
|
||||
let initialized = false;
|
||||
|
||||
/** @type {Array<Sandbox>} */
|
||||
const sandboxStack = [];
|
||||
|
||||
/** @type {WeakMap<Sandbox, Array<typeof Function>>} */
|
||||
const isolatedsMap = new WeakMap();
|
||||
|
||||
const topVariables = {
|
||||
lib: null,
|
||||
game: null,
|
||||
ui: null,
|
||||
get: null,
|
||||
ai: null,
|
||||
_status: null,
|
||||
gnc: null,
|
||||
};
|
||||
const defaultEval = window.eval;
|
||||
|
||||
let sandBoxRequired = SANDBOX_FORCED;
|
||||
|
||||
// 可能的垫片函数
|
||||
const pfPrototypes = ["Object", "Array", "String", "Map"]; // 传递的实例垫片
|
||||
const pfNamespaces = ["Object", "Array", "Reflect", "Math", "Promise"]; // 传递的静态垫片
|
||||
// 可能还要补充喵?
|
||||
const nativePattern = /^function \w*\(\) \{ \[native code\] \}$/;
|
||||
|
||||
// 垫片备份
|
||||
const polyfills = {
|
||||
prototypes: {},
|
||||
namespaces: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 将一个沙盒作为当前联网传输的运行沙盒
|
||||
* ```
|
||||
*
|
||||
* @param {Sandbox} box
|
||||
*/
|
||||
function enterSandbox(box) {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return;
|
||||
|
||||
if (!Domain.isBelievable(Domain.topDomain))
|
||||
throw "无法在沙盒里面访问";
|
||||
|
||||
sandboxStack.push(box);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 退出当前联网传输的运行沙盒
|
||||
* ```
|
||||
*/
|
||||
function exitSandbox() {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return;
|
||||
|
||||
if (!Domain.isBelievable(Domain.topDomain))
|
||||
throw "无法在沙盒里面访问";
|
||||
if (!sandboxStack.length)
|
||||
return;
|
||||
|
||||
sandboxStack.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 判断对象是否是安全对象
|
||||
* ```
|
||||
*
|
||||
* @param {Object?} obj 要检查的对象
|
||||
* @param {string?} prop 指定要检查的属性描述符
|
||||
*/
|
||||
function isUnsafeObject(obj, prop = null) {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return true;
|
||||
|
||||
if (prop != null) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
|
||||
|
||||
if (descriptor) {
|
||||
if (descriptor.get
|
||||
&& isUnsafeObject(descriptor.get))
|
||||
return true;
|
||||
if (descriptor.set
|
||||
&& isUnsafeObject(descriptor.set))
|
||||
return true;
|
||||
if (isUnsafeObject(descriptor.value))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPrimitive(obj))
|
||||
return false;
|
||||
|
||||
return !Domain.topDomain.isFrom(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 确保对象是安全对象
|
||||
* ```
|
||||
*
|
||||
* @param {Object?} obj 要检查的对象
|
||||
* @param {string?} prop 指定要检查的属性描述符
|
||||
*/
|
||||
function assertSafeObject(obj, prop = null) {
|
||||
if (isUnsafeObject(obj, prop))
|
||||
throw "unsafe object denied";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object?} obj
|
||||
*/
|
||||
function isPrimitive(obj) {
|
||||
return Object(obj) !== obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 获取当前指定的联网传输运行沙盒
|
||||
* ```
|
||||
*
|
||||
* @returns {Sandbox?}
|
||||
*/
|
||||
function currentSandbox() {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return null;
|
||||
|
||||
return sandboxStack[sandboxStack.length - 1] || defaultSandbox;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 进入沙盒运行模式
|
||||
* ```
|
||||
*/
|
||||
function requireSandbox() {
|
||||
sandBoxRequired = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 判断是否是沙盒运行模式
|
||||
* ```
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isSandboxRequired() {
|
||||
return SANDBOX_ENABLED && sandBoxRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 简单的、不带上下文的模拟eval函数
|
||||
*
|
||||
* 自动根据沙盒的启用状态使用不同的实现
|
||||
* ```
|
||||
*
|
||||
* @param {any} x
|
||||
* @returns {any}
|
||||
*/
|
||||
function _eval(x) {
|
||||
if (!SANDBOX_ENABLED || !sandBoxRequired)
|
||||
return new Function(x)();
|
||||
|
||||
// @ts-ignore
|
||||
return defaultSandbox.exec(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 携带简单上下文的eval函数
|
||||
*
|
||||
* 自动根据沙盒的启用状态使用不同的实现
|
||||
* ```
|
||||
*
|
||||
* @param {any} x
|
||||
* @param {Object} scope
|
||||
* @returns {any}
|
||||
*/
|
||||
function _exec(x, scope) {
|
||||
if (isPrimitive(scope))
|
||||
scope = {};
|
||||
|
||||
if (!SANDBOX_ENABLED || !sandBoxRequired) {
|
||||
new Function(x);
|
||||
const name = "_" + Math.random().toString(36).slice(2);
|
||||
return new Function(name, `with(${name}){return(()=>{"use strict";${x}})()}`)(scope);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
return defaultSandbox.exec(x, scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 携带简单上下文的eval函数,并返回scope
|
||||
* eval代码的返回值将覆盖 `scope.return` 这个属性
|
||||
* 另外任意因对未定义变量赋值导致全局变量赋值的行为将被转移到scope里面
|
||||
*
|
||||
* 自动根据沙盒的启用状态使用不同的实现
|
||||
* ```
|
||||
*
|
||||
* @param {any} x
|
||||
* @param {Object|"window"} scope 传入一个对象作为上下文,或者传入 "window" 来生成一个包含指向自身的 `window` 属性的对象
|
||||
* @returns {Object}
|
||||
*/
|
||||
function _exec2(x, scope = {}) {
|
||||
if (scope == "window") {
|
||||
scope = {};
|
||||
scope.window = scope;
|
||||
} else if (isPrimitive(scope))
|
||||
scope = {};
|
||||
|
||||
if (!SANDBOX_ENABLED || !sandBoxRequired) {
|
||||
new Function(x);
|
||||
|
||||
const intercepter = new Proxy(scope, {
|
||||
get(target, prop, receiver) {
|
||||
if (prop === Symbol.unscopables)
|
||||
return undefined;
|
||||
|
||||
if (!Reflect.has(target, prop)
|
||||
&& !Reflect.has(window, prop))
|
||||
throw new ReferenceError(`"${String(prop)}" is not defined`);
|
||||
|
||||
return Reflect.get(target, prop, receiver) || window[prop];
|
||||
},
|
||||
has(target, prop) {
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
const result = new Function("_", `with(_){return(()=>{"use strict";\n${x}})()}`)(intercepter);
|
||||
scope.return = result;
|
||||
return scope;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
const [result] = defaultSandbox.exec2(x, scope);
|
||||
scope.return = result;
|
||||
return scope;
|
||||
}
|
||||
|
||||
function initSecurity({
|
||||
lib,
|
||||
game,
|
||||
ui,
|
||||
get,
|
||||
ai,
|
||||
_status,
|
||||
gnc,
|
||||
}) {
|
||||
if (initialized)
|
||||
throw "security 已经被初始化过了";
|
||||
if (!SANDBOX_ENABLED)
|
||||
return;
|
||||
|
||||
topVariables.lib = lib;
|
||||
topVariables.game = game;
|
||||
topVariables.ui = ui;
|
||||
topVariables.get = get;
|
||||
topVariables.ai = ai;
|
||||
topVariables._status = _status;
|
||||
topVariables.gnc = gnc;
|
||||
|
||||
loadPolyfills();
|
||||
|
||||
// @ts-ignore
|
||||
Object.assign(defaultSandbox.scope, topVariables);
|
||||
// @ts-ignore
|
||||
setupPolyfills(defaultSandbox);
|
||||
|
||||
// 不允许被远程代码访问的game函数
|
||||
const ioFuncs = [
|
||||
"download",
|
||||
"readFile",
|
||||
"readFileAsText",
|
||||
"writeFile",
|
||||
"removeFile",
|
||||
"getFileList",
|
||||
"ensureDirectory",
|
||||
"createDir",
|
||||
"removeDir",
|
||||
"checkForUpdate",
|
||||
"checkForAssetUpdate",
|
||||
"importExtension",
|
||||
"export",
|
||||
"multiDownload2",
|
||||
"multiDownload",
|
||||
"fetch",
|
||||
];
|
||||
|
||||
const accessDenieds = [
|
||||
...ioFuncs.map(n => game[n]).filter(Boolean),
|
||||
...Object.values(game.promises),
|
||||
defaultEval,
|
||||
window.require,
|
||||
window,
|
||||
];
|
||||
|
||||
const callRule = new Rule();
|
||||
callRule.canMarshal = false; // 禁止获取函数
|
||||
callRule.setGranted(AccessAction.CALL, false); // 禁止函数调用
|
||||
callRule.setGranted(AccessAction.NEW, false); // 禁止函数new调用
|
||||
|
||||
accessDenieds.filter(Boolean).forEach(o => {
|
||||
Marshal.setRule(o, callRule);
|
||||
});
|
||||
|
||||
const bannedRule = new Rule();
|
||||
bannedRule.canMarshal = false; // 禁止获取
|
||||
bannedRule.setGranted(AccessAction.READ, false); // 禁止读取属性
|
||||
bannedRule.setGranted(AccessAction.WRITE, false); // 禁止读取属性
|
||||
|
||||
// 禁止访问关键对象
|
||||
[
|
||||
lib.cheat,
|
||||
lib.node,
|
||||
lib.message,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.forEach(o => Marshal.setRule(o, bannedRule));
|
||||
|
||||
const writeRule = new Rule();
|
||||
writeRule.setGranted(AccessAction.WRITE, false); // 禁止写入属性
|
||||
writeRule.setGranted(AccessAction.DEFINE, false); // 禁止重定义属性
|
||||
Marshal.setRule(game.promises, writeRule);
|
||||
|
||||
new Monitor()
|
||||
.action(AccessAction.WRITE)
|
||||
.action(AccessAction.DEFINE)
|
||||
.require("target", game)
|
||||
.require("property", ...ioFuncs)
|
||||
.then(() => {
|
||||
throw "禁止修改关键函数";
|
||||
})
|
||||
.start(); // 差点忘记启动了喵
|
||||
|
||||
// 监听原型、toStringTag的更改
|
||||
const toStringTag = Symbol.toStringTag;
|
||||
new Monitor()
|
||||
.action(AccessAction.WRITE)
|
||||
.action(AccessAction.DEFINE)
|
||||
.action(AccessAction.META)
|
||||
.require("property", toStringTag)
|
||||
.then((access, nameds, control) => {
|
||||
// 阻止原型、toStringTag的更改
|
||||
control.preventDefault();
|
||||
control.stopPropagation();
|
||||
control.setReturnValue(false);
|
||||
})
|
||||
.start();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 创建一个新的沙盒
|
||||
* ```
|
||||
*
|
||||
* @returns {Sandbox?}
|
||||
*/
|
||||
function createSandbox() {
|
||||
if (!SANDBOX_ENABLED)
|
||||
return null;
|
||||
|
||||
const box = new Sandbox();
|
||||
box.freeAccess = true;
|
||||
box.initBuiltins();
|
||||
|
||||
// 向沙盒提供顶级运行域的文档对象
|
||||
// TODO: 仅提供必要的document函数(?)
|
||||
box.document = document;
|
||||
|
||||
// 传递七个变量
|
||||
Object.assign(box.scope, topVariables);
|
||||
// 复制垫片函数
|
||||
setupPolyfills(box);
|
||||
|
||||
box.pushScope();
|
||||
return box;
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 导出当前沙盒的Function类型
|
||||
* ```
|
||||
*
|
||||
* @param {Sandbox} sandbox
|
||||
* @returns {Array<typeof Function>}
|
||||
*/
|
||||
function getIsolateds(sandbox) {
|
||||
let isolateds = isolatedsMap.get(sandbox);
|
||||
|
||||
if (isolateds)
|
||||
return isolateds.slice();
|
||||
|
||||
isolateds = Array.from(sandbox.exec(`
|
||||
return [
|
||||
(function(){}).constructor,
|
||||
(function*(){}).constructor,
|
||||
(async function(){}).constructor,
|
||||
(async function*(){}).constructor,
|
||||
];
|
||||
`));
|
||||
|
||||
isolatedsMap.set(sandbox, isolateds);
|
||||
return isolateds.slice();
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 加载当前的垫片函数
|
||||
* ```
|
||||
*/
|
||||
function loadPolyfills() {
|
||||
function copyDescriptors(top, box) {
|
||||
for (const key of Reflect.ownKeys(top)) {
|
||||
const descriptor = Reflect.getOwnPropertyDescriptor(top, key);
|
||||
|
||||
if (!descriptor
|
||||
|| typeof descriptor.value !== "function")
|
||||
continue;
|
||||
|
||||
const body = descriptor.value.toString();
|
||||
|
||||
if (nativePattern.test(body))
|
||||
continue;
|
||||
|
||||
box[key] = descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of pfPrototypes) {
|
||||
const top = window[key];
|
||||
|
||||
if (!top || !top.prototype)
|
||||
continue;
|
||||
|
||||
copyDescriptors(top.prototype, polyfills.prototypes[key] = {});
|
||||
}
|
||||
|
||||
for (const key of pfNamespaces) {
|
||||
const top = window[key];
|
||||
|
||||
if (!top)
|
||||
continue;
|
||||
|
||||
copyDescriptors(top, polyfills.namespaces[key] = {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ```plain
|
||||
* 初始化沙盒的垫片
|
||||
* ```
|
||||
*
|
||||
* @param {Sandbox} sandbox
|
||||
*/
|
||||
function setupPolyfills(sandbox) {
|
||||
const context = {
|
||||
pfPrototypes,
|
||||
pfNamespaces,
|
||||
prototypes: polyfills.prototypes,
|
||||
namespaces: polyfills.namespaces,
|
||||
};
|
||||
|
||||
sandbox.exec(`
|
||||
function definePolyfills(top, box) {
|
||||
for (const key in top)
|
||||
Reflect.defineProperty(box, key, top[key]);
|
||||
}
|
||||
|
||||
for (const key of pfPrototypes) {
|
||||
if (key in prototypes)
|
||||
definePolyfills(
|
||||
prototypes[key],
|
||||
window[key].prototype
|
||||
);
|
||||
}
|
||||
|
||||
for (const key of pfNamespaces) {
|
||||
if (key in namespaces)
|
||||
definePolyfills(
|
||||
namespaces[key],
|
||||
window[key]
|
||||
);
|
||||
}
|
||||
`, context);
|
||||
}
|
||||
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultFunction = function () { }.constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultGeneratorFunction = function* () { }.constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultAsyncFunction = async function () { }.constructor;
|
||||
/** @type {typeof Function} */
|
||||
// @ts-ignore
|
||||
const defaultAsyncGeneratorFunction = async function* () { }.constructor;
|
||||
|
||||
const defaultSandbox = createSandbox(); // 所有 eval、parsex 代码全部丢进去喵
|
||||
|
||||
if (SANDBOX_ENABLED) {
|
||||
// @ts-ignore
|
||||
// 对于 defaultSandbox 我们要补充一些东西喵
|
||||
defaultSandbox.scope.localStorage = localStorage;
|
||||
|
||||
// 对Function类型进行包裹
|
||||
/** @type {Array<typeof Function>} */
|
||||
const [
|
||||
IsolatedFunction,
|
||||
IsolatedGeneratorFunction,
|
||||
IsolatedAsyncFunction,
|
||||
IsolatedAsyncGeneratorFunction,
|
||||
]
|
||||
// @ts-ignore
|
||||
= getIsolateds(defaultSandbox);
|
||||
|
||||
/** @type {typeof Function} */
|
||||
let ModFunction;
|
||||
/** @type {typeof Function} */
|
||||
let ModGeneratorFunction;
|
||||
/** @type {typeof Function} */
|
||||
let ModAsyncFunction;
|
||||
/** @type {typeof Function} */
|
||||
let ModAsyncGeneratorFunction;
|
||||
|
||||
ModFunction = new Proxy(defaultFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {typeof Function} */
|
||||
ModGeneratorFunction = new Proxy(defaultGeneratorFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedGeneratorFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedGeneratorFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {typeof Function} */
|
||||
ModAsyncFunction = new Proxy(defaultAsyncFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
/** @type {typeof Function} */
|
||||
ModAsyncGeneratorFunction = new Proxy(defaultAsyncGeneratorFunction, {
|
||||
apply(target, thisArg, argumentsList) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncGeneratorFunction(...argumentsList);
|
||||
},
|
||||
construct(target, argumentsList, newTarget) {
|
||||
if (!sandBoxRequired)
|
||||
return new target(...argumentsList);
|
||||
|
||||
return new IsolatedAsyncGeneratorFunction(...argumentsList);
|
||||
},
|
||||
});
|
||||
|
||||
function rewriteCtor(prototype, newCtor) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, 'constructor')
|
||||
|| { configurable: true, writable: true, enumerable: false };
|
||||
if (!descriptor.configurable) throw new TypeError("无法覆盖不可配置的构造函数");
|
||||
descriptor.value = newCtor;
|
||||
Reflect.defineProperty(prototype, 'constructor', descriptor);
|
||||
}
|
||||
|
||||
// 覆盖所有的Function类型构造函数
|
||||
window.Function = ModFunction;
|
||||
rewriteCtor(defaultFunction.prototype, ModFunction);
|
||||
rewriteCtor(defaultGeneratorFunction.prototype, ModGeneratorFunction);
|
||||
rewriteCtor(defaultAsyncFunction.prototype, ModAsyncFunction);
|
||||
rewriteCtor(defaultAsyncGeneratorFunction.prototype, ModAsyncGeneratorFunction);
|
||||
}
|
||||
|
||||
// 测试暴露喵
|
||||
// window.sandbox = defaultSandbox;
|
||||
|
||||
const exports = {
|
||||
enterSandbox,
|
||||
exitSandbox,
|
||||
currentSandbox,
|
||||
createSandbox,
|
||||
isUnsafeObject,
|
||||
assertSafeObject,
|
||||
getIsolateds,
|
||||
requireSandbox,
|
||||
isSandboxRequired,
|
||||
initSecurity,
|
||||
eval: _eval,
|
||||
exec: _exec,
|
||||
exec2: _exec2,
|
||||
SANDBOX_ENABLED,
|
||||
};
|
||||
|
||||
Object.freeze(exports);
|
||||
export default exports;
|
Loading…
Reference in New Issue