JavaScript中能否彻底阻止对象及全局变量的删除与篡改?
如何彻底保护全局对象不被删除、覆盖及属性篡改
核心结论
可以通过锁定全局属性的可配置性+递归冻结整个对象树,在所有标准宿主环境(Node.js、浏览器、Deno)中实现对目标对象的全面保护,包括阻止:
- 全局变量被删除/覆盖
- 对象属性(含Symbol键)被添加、删除、修改
- 原型链被篡改
- 子对象(包括无原型对象)的属性篡改
分步实现方案
1. 锁定全局属性,阻止删除与覆盖
全局作用域中直接声明的变量默认是configurable: true的,可被delete删除或直接赋值覆盖。要阻止这种行为,需用Object.defineProperty将其定义为globalThis的不可配置、不可写属性:
// 先构建并冻结对象树,再绑定到全局 const secretData = deepFreeze(buildSecretTree()); // 定义不可删除、不可覆盖的全局属性 Object.defineProperty(globalThis, 'secretTree', { value: secretData, configurable: false, // 阻止删除属性、修改属性描述符 writable: false, // 阻止直接赋值覆盖(如secretTree = {}) enumerable: true // 可选:控制是否能被for...in遍历 });
2. 递归冻结对象树,保护所有子属性
Object.freeze是浅冻结,仅作用于当前对象的直接属性。要保护嵌套对象(包括无原型对象),需递归遍历所有自有属性(含Symbol键)并冻结:
// 带循环引用处理的深度冻结函数 function deepFreeze(obj, frozenCache = new WeakSet()) { // 基本类型直接返回 if (obj === null || typeof obj !== 'object') return obj; // 避免循环引用导致无限递归 if (frozenCache.has(obj)) return obj; frozenCache.add(obj); // 冻结当前对象:阻止添加/删除属性、修改属性值与描述符 Object.freeze(obj); // 获取所有自有属性(字符串键 + Symbol键) const allPropertyKeys = [ ...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj) ]; // 递归冻结每个子属性 allPropertyKeys.forEach(key => { deepFreeze(obj[key], frozenCache); }); return obj; } // 示例:构建包含无原型对象、Symbol属性的秘密对象树 function buildSecretTree() { const secretSymbol = Symbol('hiddenKey'); const nullProtoObj = Object.create(null); // 无原型对象 nullProtoObj.confidential = 'top-secret-data'; return { user: { id: 123, password: 'encrypted-pass', [secretSymbol]: 'symbol-only-secret' }, system: nullProtoObj }; }
3. 额外防护:锁定原型链
如果对象不是无原型的,可通过以下方式阻止原型链篡改:
- 创建对象时直接使用
Object.create(null)(无原型,彻底避免原型链攻击) - 若必须保留原型,冻结原型对象并锁定
__proto__属性:const obj = { a: 1 }; // 冻结原型 Object.freeze(Object.getPrototypeOf(obj)); // 锁定__proto__属性,阻止修改原型 Object.defineProperty(obj, '__proto__', { configurable: false, writable: false });
验证防护效果
执行以下操作均会失败(严格模式下抛出错误,非严格模式下静默失败):
// 尝试删除全局属性 delete globalThis.secretTree; // 无效 // 尝试覆盖全局变量 globalThis.secretTree = {}; // 无效 // 尝试修改属性值 secretTree.user.password = 'hacked'; // 无效 // 尝试添加新属性 secretTree.user.newKey = 'test'; // 无效 // 尝试删除属性 delete secretTree.user.id; // 无效 // 尝试修改无原型对象的属性 secretTree.system.confidential = 'hacked'; // 无效 // 尝试修改Symbol属性 secretTree.user[Symbol('hiddenKey')] = 'hacked'; // 无效(注意:这里是新Symbol,原Symbol属性无法修改)
局限性说明
- 无法阻止读取属性值(这是正常需求,若需隐藏敏感数据需额外加密)
- 若对象包含函数,冻结后函数本身不可被替换,但函数内部依赖的外部变量仍可能被修改(需确保函数内部无外部依赖或依赖也被保护)
- 循环引用需通过缓存集合处理,否则会导致无限递归
内容的提问来源于stack exchange,提问作者Cocktail
相关产品推荐
相关产品推荐

