- 文档
- 网络安全
- 教程
【免费下载链接】Learn-Web-Hacking
Study Notes For Web Hacking / Web安全学习笔记
严格模式(strict mode)是 ECMAScript 5 于 2009 年引入的一套更严格的代码语义子集,它通过抛错、收紧语法和限制危险特性,使代码显式地脱离「马虎模式/懒散模式」(sloppy mode)。本文以 Learn-Web-Hacking 仓库中 source/language/javascript/strict.rst 的完整内容为骨架,系统讲解严格模式的开启方式、八大类行为变更及其设计动机,并结合仓库中 作用域、原型链、沙箱逃逸 等姊妹笔记,说明严格模式在 Web 安全分析与代码审计中的实际价值。读完本文,你将能准确判断一段代码是否运行于严格模式、理解每条严格模式限制的具体触发条件与报错行为,并在审计或加固 JavaScript 代码时快速定位相关的安全边界。
严格模式是什么:ES5 引入的更严格语义子集
严格模式并不是一门独立的语言,也不是 JavaScript 的新版本,而是正常(sloppy)模式的一个严格子集。正如原文档所言:严格模式不仅仅是一个子集,它的产生是为了形成与正常代码不同的语义。
从版本沿革看,ECMAScript.rst 记录:2009 年 12 月第五版(ES5)发布时,新增了严格模式(strict mode),并澄清了第三版规范中的许多模糊之处,使语言行为与真实世界的实现趋于一致。也就是说,严格模式首先是对历史遗留怪异行为的一次系统性修正。
原文档总结了引入严格模式的五个目的,可展开理解如下:
- 通过抛出错误消除静默错误:正常模式下许多错误只会默默失败(赋值失败、删除失败等都不报错),严格模式将这些场景统一升级为异常,让问题尽早暴露;
- 消除语法中的不合理、不严谨之处,减少怪异行为:如隐式全局变量、
with语句的属性归属歧义、arguments与命名参数的联动等; - 消除代码运行的不安全之处,保证运行安全:如普通函数调用时
this被强制指向全局对象、允许在函数内遍历调用栈等; - 修复阻碍 JavaScript 引擎优化执行的缺陷,提高编译器效率与运行速度:典型如
with语句使属性归属无法在编译期确定,导致静态优化失效; - 禁用 ECMAScript 未来版本可能定义的语法,为后续版本铺路:如将
class、enum、export、extends、import、super等设为保留字,这些标识符随后在 ES6 中果真成为正式的语法关键字。
如何开启严格模式:脚本级与函数级
严格模式通过一条特殊的字符串语句"use strict";开启。这条语句本质上是「指令序言」(Directive Prologue)中的一条字符串字面量表达式语句,必须位于目标作用域的最前面才能生效。
脚本级开启
对整个脚本文件而言,将"use strict";放在脚本文件的第一行(前面允许出现注释,注释不属于语句),整个脚本即按严格模式运行:
// 这是脚本的第一条语句 "use strict"; var x = 1;关键约束:如果这行语句不在第一行,即前面还有其它语句,则不会生效,后续代码仍以正常模式运行。这是因为指令序言在遇到非字符串语句时即宣告结束,此后的"use strict";只是一条被求值后丢弃的无意义表达式语句。这也是拼接多段脚本时容易踩的坑——把"use strict";放在拼接结果的中间位置并不会让整段代码进入严格模式。
函数级开启
对单个函数而言,将"use strict";放在函数体的第一行,则该函数(含其嵌套函数)以严格模式运行,函数外的代码不受影响:
function strictFn() { "use strict"; undeclared = 1; // ReferenceError: undeclared is not defined } function sloppyFn() { undeclared = 1; // 正常模式:隐式创建全局变量,不报错 }需要补充的两个「天然严格」场景
- ES 模块(Module):通过
import/export组织的模块代码默认处于严格模式,无需显式声明"use strict"; - class 类体:ES6 的类声明与类表达式内部同样天然运行于严格模式。
这两点在审计现代前端代码时非常重要:很多人误以为没有写"use strict"就是 sloppy 模式,实际上模块与类代码已经默默处于严格语义之下。
行为变更一:全局变量必须显式声明
在正常模式中,如果一个变量没有声明就直接赋值,它会被隐式地创建为全局变量。这正是 scope.rst 中列举的全局作用域三种来源之一:「未定义直接赋值的变量自动声明为拥有全局作用域」。
严格模式禁止这种用法,全局变量必须显式声明,否则直接抛出ReferenceError。原文档给出的例子如下:
"use strict"; for(i = 0; i < 2; i++) { // ReferenceError: i is not defined }对比 sloppy 模式下,同一段循环中的i会被隐式提升为全局变量,循环照常执行,但全局命名空间被悄悄污染。
从 Web 安全角度看,隐式全局变量是代码注入场景中的常见隐患:攻击者注入的赋值语句(如x = payload)在 sloppy 模式下会直接写入全局对象,成为全局可访问的"后门通道"。启用严格模式后,这类赋值立即抛错,可作为安全审计中定位注入点、或在加固环境中阻断注入副作用的有效手段。
行为变更二:禁止 with 语句
with语句可以把一个对象临时推入作用域链前端,从而以简短的形式访问其属性:
var o = {a: 1}; with (o) { console.log(a); }但问题在于:with块内部对标识符的解析既可能是对象属性,也可能是外层作用域的变量,属性归属在编译期无法确定,这会严重阻碍引擎的静态分析与优化,同时极易引发歧义和难以排查的 Bug。因此在严格模式下,with语句被直接禁止,一旦出现即为语法错误:
"use strict"; var o = {a: 1}; with (o) { // SyntaxError: Strict mode code may not include a with statement console.log(a); }这也是原文档所述"修复阻碍引擎优化执行的缺陷、提高编译效率"的典型例证。
行为变更三:eval 拥有独立作用域
正常模式下,eval语句的作用域取决于它处于全局作用域还是函数作用域:在函数内执行eval("var x = 1"),变量x会泄漏到该函数作用域;在全局执行则会生成全局变量。
严格模式下,eval语句本身就是一个独立的作用域,它不再能够生成全局变量,其中用var声明的变量只能存在于eval内部:
"use strict"; eval("var secret = 42;"); console.log(typeof secret); // "undefined",变量未泄漏到外部对比 sloppy 模式,上面的secret会泄漏到eval所在的作用域(此处即全局),造成命名空间污染。
需要注意两点边界:
- 严格模式下被求值的
eval代码本身也以严格模式执行; eval的作用域隔离只能阻止变量泄漏,并不构成完整的安全沙箱。仓库 vm.rst 中展示的 Node.js 原生vm沙箱逃逸即表明:仅靠"隔离上下文"远远不够,攻击者仍可通过this.constructor链条回溯到宿主环境。因此在设计在线 JS 执行环境、混淆代码分析等场景时,严格模式的 eval 规则只是基础认知,不能当作安全边界。
行为变更四:禁止删除变量
严格模式下无法删除变量。对标识符(变量、函数名、函数参数)执行delete属于语法错误:
"use strict"; var x = 1; delete x; // SyntaxError: Delete of an unqualified identifier in strict mode.而对象属性的删除则取决于该属性的configurable特性:只有configurable为true的属性才能被delete删除;对configurable为false的属性执行删除,在严格模式下会抛出TypeError:
"use strict"; var o = {}; Object.defineProperty(o, "p", { value: 1, configurable: false }); delete o.p; // TypeError: Cannot delete property 'p' of #<Object>这条规则的意义在于:sloppy 模式下删除失败只是静默地返回false,而严格模式让"删不掉"这件事变成明确可见的异常,避免代码在错误的假设下继续运行。
行为变更五:静默失败变显式报错
正常模式下一些错误只会默默地失败,但严格模式下会显式抛错。原文档列举了四类场景,均对应明确的异常类型:
| 场景 | 严格模式行为 |
|---|---|
| 对一个对象的只读属性进行赋值 | TypeError: Cannot assign to read only property ... |
| 对一个使用 getter 方法读取的属性进行赋值 | TypeError: Cannot set property ... which has only a getter |
| 对禁止扩展的对象添加新属性 | TypeError: object is not extensible |
| 删除一个不可删除的属性 | TypeError: Cannot delete property ... |
对应的可运行示例:
"use strict"; // 1. 只读属性赋值 var ro = {}; Object.defineProperty(ro, "x", { value: 1, writable: false }); ro.x = 2; // TypeError // 2. 仅含 getter 的属性赋值 var go = { get x() { return 1; } }; go.x = 2; // TypeError // 3. 向不可扩展对象添加属性 var ne = Object.preventExtensions({}); ne.y = 1; // TypeError // 4. 删除不可删除的属性 var nd = {}; Object.defineProperty(nd, "z", { value: 1, configurable: false }); delete nd.z; // TypeError从安全视角看,第 3 条与 JavaScript 原型链污染防护直接相关。prototype.rst 指出:JavaScript 是动态继承的,通过__proto__修改自身对象会影响到拥有相同原型的对象,当键值对用户可控时就可能出现原型链污染。在加固场景中,常对Object.prototype等关键对象调用Object.preventExtensions/Object.seal/Object.freeze阻止扩展——此时若攻击载荷试图向这些不可扩展对象写入属性,严格模式会立即抛出TypeError而不是静默失败,使污染尝试被快速发现和阻断。
行为变更六:新增语法错误
严格模式新增了一批语法错误,一旦代码中出现以下写法,解析阶段即失败,脚本根本不会执行:
1. 对象不能有重名的属性
"use strict"; var o = { a: 1, a: 2 }; // SyntaxError: Duplicate data property in object literal not allowed in strict mode这是 ES5 严格模式的规则;ES2015 起对一般重名属性放宽(后者覆盖前者),但__proto__的重名仍被禁止。分析旧版代码或兼容性要求严格的场景时仍应留意该限制。
2. 函数不能有重名的参数
"use strict"; function f(a, a) { // SyntaxError: Duplicate parameter name not allowed in this context return a; }3. 禁止八进制表示法
"use strict"; var x = 010; // SyntaxError: Octal literals are not allowed in strict mode.严格模式同时禁止以0开头的八进制整数字面量,以及字符串中的八进制转义序列(如"\1")。ES6 引入的0o10显式八进制写法在两种模式下均合法,不受影响。
4. 函数必须声明在顶层
在 ES5 严格模式下,函数声明只能出现在脚本或函数体的顶层,不允许出现在if、for等块结构中:
"use strict"; if (true) { function f() {} // ES5 严格模式:SyntaxError }ES2015 起块级函数声明被恢复,但其语义已改为块级词法绑定,与旧式"函数提升到块外"的行为不同。
5. 新增保留字
严格模式将一批标识符设为保留字,禁止用作变量名、函数名或属性名。原文档列出的六项为:
classenumexportextendsimportsuper
此外,ES5 规范在严格模式下还额外保留了implements、interface、let、package、private、protected、public、static、yield等标识符。这些词中相当一部分在 ES6 及后续版本中果然变成了正式语法关键字(如class、export、import、super),这正是"为未来版本做铺垫"的直接体现。
"use strict"; var implements = 1; // SyntaxError: Unexpected strict mode reserved word行为变更七:this 绑定与调用栈访问受限(安全增强)
严格模式在安全层面做了两条关键收紧,原文档将其单列为"安全增强"。
1. 禁止 this 关键字指向全局对象
正常模式下,普通函数调用时函数体内的this会被强制指向全局对象(浏览器中为window,Node.js 中为global)。严格模式下不再做这种强制转换:普通函数调用(f())的this为undefined,call/apply传入的this也不再被自动包装为对象:
"use strict"; function f() { return this; } console.log(f()); // undefined(sloppy 模式下返回全局对象)这条规则杜绝了"函数被当作构造函数之外的方式调用却意外操作全局对象"的风险,也让依赖隐式this指向全局的混淆/载荷写法在严格模式下失效。
2. 禁止在函数内部遍历调用栈
严格模式禁止通过arguments.callee、arguments.caller等方式在函数内部向上回溯调用栈,同时Function.prototype.caller与Function.prototype.arguments这两个属性的访问器在被调用时也会抛出TypeError:
"use strict"; function f() { return arguments.callee; // TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed ... }调用栈访问的收紧削弱了攻击载荷通过栈回溯获取敏感上下文的能力,同时也会影响某些依赖arguments.callee的反调试与自引用技巧——仓库 misc.rst 中列举的反调试手段(函数重定义、定时断点等)在分析此类代码时都需要先确认其运行模式。
行为变更八:arguments 对象行为收紧
严格模式对arguments对象施加了三项限制:
1. 不允许对 arguments 赋值
将arguments当作赋值目标是语法错误:
"use strict"; function f() { arguments = 42; // SyntaxError: Unexpected eval or arguments in strict mode }2. arguments 不再追踪参数的变化
sloppy 模式下,命名参数与arguments[i]之间存在"联动":修改其中一个,另一个会同步变化。严格模式下这种联动被切断,二者各自独立:
"use strict"; function f(a) { a = 100; return arguments[0]; // 返回调用时的实参,而不是 100 } console.log(f(1)); // 1sloppy 模式下同样的代码会返回100。这一行为变化消除了大量依赖"参数联动"的怪癖写法,使arguments语义更加可预测。
3. 禁止使用 arguments.callee
在严格模式函数或调用该函数的arguments对象上访问callee会抛出TypeError(见行为变更七中的示例)。arguments.callee本是匿名函数自引用的唯一手段,但在严格模式下被禁用,取而代之的是为函数命名或使用具名函数表达式。
行为变更速查表
为便于查阅,将严格模式的主要行为变更汇总如下:
| 行为变更 | 触发方式 | 严格模式表现 |
|---|---|---|
| 全局变量显式声明 | 未声明即赋值 | ReferenceError |
禁止with语句 | 使用with | SyntaxError |
eval独立作用域 | eval内var声明 | 变量不再泄漏到外部作用域 |
| 禁止删除变量 | delete标识符 | SyntaxError |
| 显式报错 | 只读属性赋值 / getter 属性赋值 / 不可扩展对象加属性 / 删除不可删属性 | 分别抛TypeError |
| 语法错误 | 对象重名属性、函数重名参数、八进制字面量、块级函数声明、保留字作标识符 | SyntaxError |
| this 绑定 | 普通函数调用 /call、apply传参 | this不再强制为全局对象 |
| 调用栈访问 | 访问arguments.callee、Function.caller等 | TypeError |
| arguments 赋值 | 对arguments赋值 | SyntaxError |
| 参数联动 | 修改命名参数后读arguments[i] | arguments保持原值 |
| arguments.callee | 访问callee属性 | TypeError |
严格模式的 Web 安全视角
结合 Learn-Web-Hacking 仓库的定位(Web 安全学习笔记),严格模式在安全分析中至少有四层价值:
- 全局命名空间防护:严格模式禁止隐式全局变量,可阻断注入代码借助"未声明赋值"在全局对象上落地的路径,是审计注入点与加固执行环境的有效抓手(关联 作用域笔记 中的全局作用域规则);
- eval 隔离认知:严格模式下
eval自带作用域,是设计在线代码执行、混淆代码分析等隔离场景时必须掌握的基础语义,但正如 沙箱逃逸笔记 所示,它远不足以构成安全边界; - 原型链污染加固:对关键对象施加
Object.preventExtensions/seal/freeze后,严格模式的显式报错能让污染尝试立即现形(关联 原型链污染笔记); - 载荷与混淆分析前提:
this绑定、arguments行为、调用栈访问限制等语义差异,决定了同一段载荷在严格模式与 sloppy 模式下行为截然不同;审计工具、WebShell 与反调试代码时,先判断目标作用域的运行模式是前提。
小结与进一步阅读
严格模式是 ES5 为 JavaScript 打下的"更严谨语义"基础:它用显式报错替代静默失败,用收紧语法消除怪异行为,用限制危险特性提升安全边界,并为 ES6 及后续版本的语言演进预留了空间。理解每一类行为变更的触发条件,既是编写健壮代码的必修课,也是 JavaScript 代码审计与安全分析的基本功。
想深入本主题在仓库中的完整上下文,可继续阅读以下姊妹文档:
- ECMAScript 版本沿革:严格模式的引入背景与 ES6 之后的语言演进;
- 作用域与闭包:全局作用域、变量提升与作用域链,是理解隐式全局变量问题的基础;
- 原型链与原型链污染:动态继承、
__proto__与污染原理,对应严格模式对不可扩展对象的行为变化; - 沙箱逃逸:
eval隔离与真实沙箱之间的差距,以及原生vm模块的逃逸示例; - 其他(命令执行与反调试):Node.js 命令执行入口与反调试技巧,分析时需结合运行模式判断。
- 文档
- 网络安全
- 教程
【免费下载链接】Learn-Web-Hacking
Study Notes For Web Hacking / Web安全学习笔记
相关推荐
SSL/TLS 协议基础与 Web 安全实战解析(Learn-Web-Hacking 学习笔记)
SSL/TLS 协议基础与 Web 安全实战解析(Learn Web Hacking 学习笔记) 本文是 Learn Web Hacking https://l
文档网络安全教程ES5 严格模式(strict mode)详解:JavaScript 从松散到严谨的关键一步
ES5 严格模式(strict mode)详解:JavaScript 从松散到严谨的关键一步 导读 本文围绕前端入门到进阶知识库中 02 ES5中的严格模式 h
文档教程前端在 Vue 单文件组件中管理多语言文案:vue-i18n `<i18n>` 自定义块完整指南
在 Vue 单文件组件中管理多语言文案:vue i18n <i18n 自定义块完整指南 导读 在 Vue.js 项目中, .vue 单文件组件(Single F
文档网络安全教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考