J

JavaScript 安全 101

2025-06-08

JavaScript 安全 101

该博客文章最初发表于 Tes Engineering博客

我最近完成了Marcin Hoppe《JavaScript 安全:最佳实践》课程,想分享一些我学到的关于如何编写更安全的 JavaScript 代码的关键实践经验。 除了阅读这篇博客,我也强烈推荐大家完成这门课程。它简洁易懂,而且易于实践!

JavaScript 威胁环境

值得注意的是,存在两种不同的威胁环境:客户端 JavaScript 和服务器端 JavaScript。对于客户端 JavaScript,浏览器在低信任度和高度受限的基础上运行,这是必然的,因为它会通过用户浏览网页来处理来自不受控制来源的 JavaScript。
相比之下,对于服务器端 JavaScript,Node.js 在高信任度和特权的基础上运行,因为它是受控来源(即由工程团队编写的代码),并且在运行时不会更改。Node.js安全路线图
中对这些不同的威胁环境进行了更详细的总结,在编写 JavaScript 时务必牢记这一差异。

JavaScript 的动态特性一方面使其用途极其广泛,另一方面也带来了许多安全隐患。以下是 JavaScript 中的三个主要隐患以及如何避免它们。

1. 滥用比较和转换

简而言之:
JavaScript 拥有动态类型系统,这可能会带来一些危险但可以避免的后果。使用 JavaScript严格模式可以帮助避免诸如松散比较之类的陷阱。

一些例子...

NaN、Null 和 undefined

自动转换可能会导致执行意外的代码:

console.log(typeof NaN) // number
console.log(typeof null) // object
console.log(typeof undefined) // undefined
Enter fullscreen mode Exit fullscreen mode

例如,此calculatingStuff函数依赖于输入为数字。没有任何验证来防止输入为NaN,该函数仍然运行,因为NaN被归类为数字。

const calculatingStuff = (num) => {
  return num * 3;
};

console.log(calculatingStuff(NaN)) // NaN
Enter fullscreen mode Exit fullscreen mode

为了避免自动转换中出现意外行为,设置保护条款和错误处理机制至关重要。例如,在这个版本的 中,calculatingStuffv2如果输入为 ,我们会抛出错误NaN

const calculatingStuffv2 = (num) => {
if (isNaN(num)) {
  return new Error('Not a number!')
}
  return num * 3;
};

console.log(calculatingStuffv2(NaN)) // Error: Not a number!
console.log(calculatingStuffv2(undefined)) // Error: Not a number!
console.log(calculatingStuffv2(null)) // 0
console.log(calculatingStuffv2(2)) // 6
Enter fullscreen mode Exit fullscreen mode

isNaN()能防范 undefined,但无法防范null。与 JavaScript 中的所有内容一样,有很多方法可以编写检查来防范NaNnullundefined
一个更可靠的“全部捕获”方法是检查真值,因为所有这些值都是假值,它们总是会返回错误:

const calculatingStuffv2 = (num) => {
if (!num) {
  return new Error('Not a number!')
}
  return num * 3;
};

console.log(calculatingStuffv2(NaN)) // Error: Not a number!
console.log(calculatingStuffv2(undefined)) // Error: Not a number!
console.log(calculatingStuffv2(null)) // // Error: Not a number!
console.log(calculatingStuffv2(2)) // 6
Enter fullscreen mode Exit fullscreen mode

宽松的比较

松散比较是代码可能被意外执行的另一种方式:

const num = 0;
const obj = new String('0');
const str = '0';

console.log(num == obj); // true
console.log(num == str); // true
console.log(obj == str); // true
Enter fullscreen mode Exit fullscreen mode

使用严格比较===可以排除意外副作用的可能性,因为它总是认为不同类型的操作数是不同的。

const num = 0;
const obj = new String('0');
const str = '0';

console.log(num === obj); // false
console.log(num === str); // false
console.log(obj === str); // false
Enter fullscreen mode Exit fullscreen mode

2. 动态执行代码的注入攻击

TLDR;
务必在应用程序中使用数据之前始终验证数据,并避免将字符串作为参数传递给可以动态执行代码的 JavaScript 函数。

一些例子...

评估()

正如mdn 文档中所述,eval“以调用者的权限执行传递的代码”。

例如,如果向 eval 传递了未经验证的用户输入(其中包含恶意代码),这可能会变得非常危险。

eval('(' + '<script type='text/javascript'>some malicious code</script>' + '(');
Enter fullscreen mode Exit fullscreen mode

浏览器 API 的不安全变体

setTimeoutsetInterval都有一个可选语法,可以传递字符串而不是函数。

window.setTimeout('<script type='text/javascript'>some malicious code</script>', 2*1000);
Enter fullscreen mode Exit fullscreen mode

正如eval()示例所示,这将导致在运行时执行恶意代码。可以通过始终使用传递函数作为参数的语法来避免这种情况。

3.原型污染攻击

简而言之:
每个 JavaScript 对象都有一个原型链,它是可变的,可以在运行时更改。可以通过以下方式防止这种情况:

  1. 冻结原型以防止添加或修改新属性
  2. 创建没有原型的对象
  3. 优先使用Map而不是普通{}对象

一些例子...

toString下面是一个通过改变原型中的函数值来执行恶意脚本的例子。

let cutePuppy = {name: "Barny", breed: "Beagle"}
cutePuppy.__proto__.toString = ()=>{<script type='text/javascript'>some malicious code</script>}
Enter fullscreen mode Exit fullscreen mode

减轻这种风险的几种方法是在启动新对象时要小心,要么创建它们并删除原型,要么冻结原型,要么使用Map 对象

// remove
let cutePuppyNoPrototype = Object.create(null, {name: "Barny", breed: "Beagle"})

// freeze
const proto = cutePuppyNoPrototype.prototype;
Object.freeze(proto);

// Map
let puppyMap = new Map()
cutePuppyNoPrototype.set({name: "Barny", breed: "Beagle"})
Enter fullscreen mode Exit fullscreen mode

原型继承是一个被低估的威胁,因此绝对值得考虑这一点,以防止 JavaScript 以各种方式被利用。

工具

最后,除了意识到 JavaScript 的这些缺陷之外,您还可以使用一些工具在开发过程中获取早期反馈。务必考虑您编写的 JavaScript 以及通过依赖项引入的第三方 JavaScript 的安全问题。

以下是Awesome Node.js securityGuidesmiths Cyber​​security 手册中列出的一些出色的静态代码分析 (SAST) 工具的亮点

在你的代码中

"rules": {
  "no-eval": "error",
  "no-implied-eval": "error",
  "no-new-func": "error",
}
Enter fullscreen mode Exit fullscreen mode

在你的 JavaScript 依赖项代码中

  • 使用npm audit检查已知漏洞
  • 使用lockfile lint检查package-lock.json通常不被审查的更改
  • 使用trust but verify将 npm 包与其源存储库进行比较,以确保生成的工件相同
鏂囩珷鏉ユ簮锛�https://dev.to/charlottebrf_99/javascript-security-101-2lag
PREV
了解 QuillJS - 第 1 部分(Parchment、Blots 和生命周期)
NEXT
设置 Axios 拦截器 (React.js + TypeScript)