冻结原型对象是保障继承安全的底层手段,需在框架初始化最早阶段冻结object.prototype、function.prototype、array/string/number.prototype及自定义基类prototype,并配合object.setprototypeof(..., null)切断原型链。

冻结原型对象是保障核心框架基础类继承安全的底层手段,关键不在于“锁住某个类”,而在于切断所有可能被污染的原型链入口。只要原生构造函数的 prototype 或自定义基类的原型未被冻结,攻击者或误操作就可能通过修改其方法、添加恶意属性,甚至覆盖 toStringvalueOf 等基础行为,导致整个继承体系失稳。
必须冻结的原型对象清单
以下原型应在框架初始化最早阶段(早于任何业务模块加载)一次性冻结,顺序无关,但不可遗漏:
-
Object.prototype—— 所有对象的终极源头,冻结后可阻断原型链污染的横向传播 -
Function.prototype—— 防止篡改callapplybind等关键方法,避免继承逻辑被劫持 -
Array.prototype、String.prototype、Number.prototype—— 框架常依赖这些方法做类型判断、数据处理,一旦被重写,子类实例行为将不可预测 - 自定义基类的
prototype,例如BaseComponent.prototype或Service.prototype—— 冻结后确保所有子类共享的公共方法和访问器不可被覆盖
冻结必须配合切断原型链
仅调用 Object.freeze(BaseComponent.prototype) 不够安全。若该原型本身还继承自其他对象(比如 BaseComponent.prototype.__proto__ === EventEmitter.prototype),而后者未冻结,污染仍可沿原型链向上渗透。
稳妥做法是两步并行:
- 对目标原型执行
Object.freeze() - 立即执行
Object.setPrototypeOf(BaseComponent.prototype, null),彻底断开向上继承,使其成为“纯原型对象”——既无父级可污染,也无法被__proto__动态替换
冻结时机比写法更重要
常见失效场景:在框架入口文件中,先 import { BaseComponent } from './base.js',再冻结其原型。此时模块已执行完毕,其他模块可能早已缓存了未冻结前的引用。
正确姿势是:在框架最顶层的初始化脚本(如 bootstrap.js 或 ESM 的入口模块顶部)直接操作全局构造函数或导出前的原始对象:
- Node.js 环境:在
require任何框架模块前,先运行冻结代码 - 浏览器 ESM:在
main.js第一行就冻结,且确保该文件是打包入口或<script type="module"></script>的首个加载项 - 若使用 TypeScript,需注意
declare class不生成运行时原型,真正要冻结的是编译后实际挂载到BaseComponent.prototype的那个对象
验证冻结是否生效
冻结后应主动测试三类典型绕过方式,任一成功即说明防护未到位:
- 尝试添加属性:
BaseComponent.prototype.newMethod = function(){}→ 应静默失败(非严格模式)或抛TypeError(严格模式) - 尝试修改已有方法:
BaseComponent.prototype.render = () => 'hacked'→ 同样应失败 - 尝试篡改原型指向:
Object.setPrototypeOf(instance, EvilPrototype)→ 若实例本身也被Object.freeze(),则失败;否则说明实例级保护缺失
真正的安全性来自“原型冻结 + 原型链切断 + 初始化前置执行”的组合,单点防护形同虚设。










