迁移到安全的 Buffer 构造函数
移植到 Buffer.from()/Buffer.alloc() API.
概述
本指南介绍了如何迁移到安全的 Buffer 构造函数方法, 迁移修复了以下弃用警告:
由于安全性和可用性问题, 不建议使用 Buffer()和 new Buffer()构造函数, 请改用 new Buffer.alloc(),
Buffer.allocUnsafe()
或 Buffer.from()构造方法.
变式 1: 放弃对 Node.JS ≤4.4.x 和 5.0.0 - 5.9.x 的支持(推荐).
变式 2: 使用 polyfill.
变式 3: 手动检测, 带有安全措施.
使用 grep 查找有问题的代码位
只需运行 grep -nrE '[^a-zA-Z](Slow)?Buffer\s*\(' --exclude-dir node_modules.
它会在你自己的代码中找到所有可能不安全的地方(有一些不太常见的例外).
使用 Node.JS 8 查找有问题的代码位
如果你使用的是 Node.JS ≥ 8.0.0(推荐使用),Node.JS 会公开多个选项, 以帮助你找到相关的代码片段:
--trace-warnings 将使 Node.JS 显示此警告的堆栈跟踪以及 Node.JS 打印的其他警告.
--trace-deprecation 执行相同的操作, 但仅适用于弃用警告.
--pending-deprecation
将显示更多类型的弃用警告, 特别是, 它也会显示 Buffer()弃用警告, 即使在 Node.JS 8 上.
你可以使用环境变量设置这些标志:
- $ export NODE_OPTIONS='--trace-warnings --pending-deprecation'
- $ cat example.JS
- 'use strict';
- const foo = new Buffer('foo');
- $ node example.JS
- (node:7147) [DEP0005] DeprecationWarning: The Buffer() and new Buffer() constructors are not recommended for use due to security and usability concerns. Please use the new Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() construction methods instead.
- at showFlaggedDeprecation (buffer.JS:127:13)
- at new Buffer (buffer.JS:148:3)
- at Object.<anonymous> (/path/to/example.JS:2:13)
- [... more stack trace lines ...]
使用 linters 查找有问题的代码位
ESLint 规则或 node/no-deprecated-API 也查找对不推荐使用的 Buffer() API 的调用, 这些规则包含在一些预设中.
但是有一个缺点, 当 Buffer 被重写时, 它并不总是正确工作, 例如使用 polyfill, 因此建议将此与上述其他方法结合使用.
变式 1: 放弃对 Node.JS ≤4.4.x 和 5.0.0 - 5.9.x 的支持
这是目前推荐的解决方案, 仅意味着最小的开销.
自 2016 年 7 月以来, Node.JS 5.x 版本系列一直未得到支持, 并且 Node.JS 4.x 版本系列在 2018 年 4 月达到其生命周期结束(→计划 https://github.com/nodejs/Release#release-schedule ).
这意味着即使出现安全问题, 这些版本的 Node.JS 也不会收到任何更新, 因此如果可能的话, 应该避免使用这些版本线.
在这种情况下, 你要做的是将所有 new Buffer()或 Buffer()调用转换为使用 Buffer.alloc()或 Buffer.from(), 方法如下:
对于 new Buffer(number), 将其替换为 Buffer.alloc(number).
对于 new Buffer(string)(或
new Buffer(string, encoding)
), 将其替换为 Buffer.from(string)(或
- Buffer.from(string, encoding)
- ).
对于所有其他参数组合(这些更为罕见), 还要用
Buffer.from(...arguments)
替换
- new Buffer(...arguments)
- .
请注意, Buffer.alloc()在当前 Node.JS 版本上的速度也比 new Buffer(size).fill(0)快, 这是你需要确保零填充的原因.
建议启用 ESLint 规则或 node/no-deprecated-API 以避免意外的不安全 Buffer API 使用.
还有一个 JSCodeshift codemod, 用于自动将 Buffer 构造函数迁移到 Buffer.alloc()或 Buffer.from(), 请注意, 它目前仅适用于参数为文字或使用两个参数调用构造函数的情况.
如果你当前支持那些较旧的 Node.JS 版本并且无法删除对它们的支持, 或者如果你支持包的旧分支, 考虑在较旧的分支上使用变式 2 或变式 3, 因此使用这些旧分支的人也将收到修复. 这样, 你将消除由不谨慎的 Buffer API 使用引起的潜在问题, 并且在 Node.JS 10 上运行代码时, 你的用户将不会观察到运行时弃用警告.
变式 2: 使用 polyfill
有三种不同的 polyfill 可用:
https://www.npmjs.com/package/safer-buffer 是整个 Buffer API 的替代品, 在使用 new Buffer()时会抛出.
你将采用与变式 1 完全相同的步骤, 但是在使用新的 Buffer API 的所有文件中都使用 polyfill const Buffer = require('safer-buffer').Buffer.
不要使用旧的 new Buffer() API, 在添加上面一行的任何文件中, 使用旧的 new Buffer() API 将抛出.
https://www.npmjs.com/package/buffer-from 和 / 或 https://www.npmjs.com/package/buffer-alloc 是 Buffer API 各自部分的 https://ponyfill.com/ , 你只需添加与你正在使用的 API 相对应的包.
你可以使用适当的名称导入所需的模块, 例如 const bufferFrom = require('buffer-from')然后使用它而不是调用 new Buffer(), 例如: new Buffer('test')变为 bufferFrom('test').
使用这种方法的一个缺点是从它们迁移出来的代码更改略多(因为你将使用不同名称下的 Buffer.from()).
https://www.npmjs.com/package/safe-buffer 也是整个 Buffer API 的替代品, 但使用 new Buffer()仍然可以像以前一样工作.
这种方法的缺点是它允许你在代码中使用较旧的 new Buffer() API, 这是有问题的, 因为它可能会导致代码中出现问题, 并将从 Node.JS 10 开始发出运行时弃用警告(在此处阅读更多内容).
请注意, 在任何一种情况下, 你还必须手动删除对旧 Buffer API 的所有调用, 只是投入 safe-buffer 本身并不能解决问题, 它只是为新 API 提供了一个 polyfill, 我看到有人犯了这个错误.
建议启用 ESLint 规则或 node/no-deprecated-API.
放弃对 Node.JS <4.5.0 的支持后, 不要忘记删除 polyfill 使用.
变式 3 - 手动检测, 带有安全措施
如果你只在几个地方 (例如一个) 创建 Buffer 实例, 或者你有自己的包装器, 这将非常有用.
Buffer(0)
这个用于创建空缓冲区的特殊情况可以安全地替换为 Buffer.concat([]), 它返回相同的结果一直到 Node.JS 0.8.x.
Buffer(notNumber)
之前:
const buf = new Buffer(notNumber, encoding);
以后:
- let buf;
- if (Buffer.from && Buffer.from !== Uint8Array.from) {
- buf = Buffer.from(notNumber, encoding);
- } else {
- if (typeof notNumber === 'number') {
- throw new Error('The"size"argument must be not of type number.');
- }
- buf = new Buffer(notNumber, encoding);
- }
encoding 是可选的.
请注意, typeof notNumber 必须在 new Buffer()之前,(对于 notNumber 参数未进行硬编码的情况)并且不是由 Buffer 构造函数的弃用引起的 - 这正是不推荐使用 Buffer 构造函数的原因. 缺乏此类型检查的生态系统包导致了许多安全问题 - 当未经过处理的用户输入可能最终出现在 Buffer(arg)中时, 会出现从 DoS 到从进程内存向攻击者泄漏敏感信息等问题.
当 notNumber 参数被硬编码时(例如文字 "abc" 或[0,1,2]), 可以省略 typeof 检查.
另请注意, 使用 TypeScript 不能解决此问题 - 当从 JS 中使用用 TypeScript 编写的库时, 或者当用户输入结束时 - 它的行为与纯 JS 一样, 因为所有类型检查只是转换时间, 并且不存在于 TS 编译的实际 JS 代码中.
Buffer(number)
对于 Node.JS 0.10.x(及以下)支持:
- var buf;
- if (Buffer.alloc) {
- buf = Buffer.alloc(number);
- } else {
- buf = new Buffer(number);
- buf.fill(0);
- }
否则(Node.JS ≥ 0.12.x):
const buf = Buffer.alloc ? Buffer.alloc(number) : new Buffer(number).fill(0);
关于
Buffer.allocUnsafe()
使用 Buffer.allocUnsafe()时要格外小心:
如果你没有充分的理由, 请不要使用它.
例如你可能永远不会看到小缓冲区的性能差异, 事实上, 使用 Buffer.alloc()可能会更快.
如果你的代码不在热代码路径中 - 你也可能不会注意到差异.
请记住, 零填充可以最大限度地降低潜在风险.
如果使用它, 请确保永远不会以部分填充状态返回 buffer.
如果你按顺序写它 - 总是将它截断为实际的书写长度.
处理使用 Buffer.allocUnsafe()分配的缓冲区中的错误可能会导致各种问题, 包括代码的未定义行为, 以及泄露给远程攻击者的敏感数据(用户输入, 密码, 证书).
请注意, 这同样适用于没有零填充的 new Buffer()用法, 具体取决于 Node.JS 版本(缺少类型检查也会将 DoS 添加到潜在问题列表中).
常问问题
Buffer 构造函数有什么问题?
Buffer 构造函数可用于以多种不同方式创建缓冲区:
new Buffer(42)创建一个 42 字节的 Buffer, 在 Node.JS 8 之前, 由于性能原因, 此缓冲区包含任意内存, 其中可能包括从程序源代码到密码和加密密钥的任何内容.
new Buffer('abc')创建一个 Buffer, 其中包含字符串'abc'的 UTF-8 编码版本, 第二个参数可以指定另一个编码: 例如, 可以使用
new Buffer(string, 'base64')
将 Base64 字符串转换为它所代表的原始字节序列.
还有其他几种参数组合.
这意味着在像 var buffer = new Buffer(foo); 这样的代码中, 在不知道 foo 类型的情况下, 无法确定生成的缓冲区的确切内容.
有时, foo 的值来自外部来源, 例如, 此函数可以作为 web 服务器上的服务公开, 将 UTF-8 字符串转换为其 Base64 格式:
- function stringToBase64(req, res) {
- // The request body should have the format of `{ string: 'foobar' }`.
- const rawBytes = new Buffer(req.body.string);
- const encoded = rawBytes.toString('base64');
- res.end({ encoded });
- }
请注意, 此代码不验证 req.body.string 的类型:
req.body.string 应该是一个字符串, 如果是这种情况, 一切顺利.
req.body.string 由发送请求的客户端控制.
如果 req.body.string 是数字 50, 则 rawBytes 将是 50 个字节:
在 Node.JS 8 之前, 内容将是未初始化的.
在 Node.JS 8 之后, 内容将是
50
个字节, 值为 0.
由于缺少类型检查, 攻击者可以故意发送一个号码作为请求的一部分, 使用它, 他们可以:
读取未初始化的内存, 这将泄露密码, 加密密钥和其他类型的敏感信息(信息泄露).
强制程序分配大量内存, 例如, 当指定
500000000
作为输入值时, 每个请求将分配 500MB 的内存, 这可以用来完全耗尽程序可用的内存并使其崩溃, 或者显着减慢程序速度(拒绝服务).
在现实世界的 Web 服务器环境中, 这两种情况都被认为是严重的安全问题.
当使用 Buffer.from(req.body.string)时, 传递一个数字将总是抛出一个异常, 提供可由程序始终处理的受控行为.
Buffer()构造函数已被弃用了一段时间, 这真的是一个问题吗?
NPM 生态系统中的代码调查表明, Buffer()构造函数仍然被广泛使用, 这包括新代码, 并且此类代码的总体使用实际上已经增加.
上一篇: Docker 化 Node.JS Web 应用程序
来源: https://segmentfault.com/a/1190000016989236