safe-buffer

更安全的Node.js缓冲API
使用新的Node.js Buffer api (Buffer.from, Buffer.alloc,
Buffer.allocUnsafe, Buffer.allocUnsafeSlow)在所有版本的Node.js中。
在可用时使用内置实现。
安装
npm install safe-buffer
用法
这个包的目标是为node.js提供一个安全的替代品 Buffer。
这是一个临时替代品 Buffer。加1就可以了 require 行
node.js模块的顶部:
var Buffer = require('safe-buffer').Buffer
// Existing buffer code will continue to work without issues:
new Buffer('hey', 'utf8')
new Buffer([1, 2, 3], 'utf8')
new Buffer(obj)
new Buffer(16) // create an uninitialized buffer (potentially unsafe)
// But you can use these new explicit APIs to make clear what you want:
Buffer.from('hey', 'utf8') // convert from many types to a Buffer
Buffer.alloc(16) // create a zero-filled buffer (safe)
Buffer.allocUnsafe(16) // create an uninitialized buffer (potentially unsafe)
api
类方法:Buffer.from(array)
array{数组}
分配一个新的 Buffer 使用一个 array 八位字节。
const buf = Buffer.from([0x62,0x75,0x66,0x66,0x65,0x72]);
// creates a new Buffer containing ASCII bytes
// ['b','u','f','f','e','r']
一个 TypeError 会被抛出 array 不是。 Array。
类方法:Buffer.from(arrayBuffer[, byteOffset[, length]])
arrayBuffer{ArrayBuffer}.buffera的性质TypedArray或 一个new ArrayBuffer()byteOffset{}数量默认值:0length{}数量默认值:arrayBuffer.length - byteOffset
的引用 .buffer a的性质 TypedArray 实例,
新创建的 Buffer 将共享与
TypedArray。
const arr = new Uint16Array(2);
arr[0] = 5000;
arr[1] = 4000;
const buf = Buffer.from(arr.buffer); // shares the memory with arr;
console.log(buf);
// Prints: <Buffer 88 13 a0 0f>
// changing the TypedArray changes the Buffer also
arr[1] = 6000;
console.log(buf);
// Prints: <Buffer 88 13 70 17>
可选 byteOffset 和 length 参数指定的内存范围
这个 arrayBuffer 这将由 Buffer。
const ab = new ArrayBuffer(10);
const buf = Buffer.from(ab, 0, 2);
console.log(buf.length);
// Prints: 2
一个 TypeError 会被抛出 arrayBuffer 不是。 ArrayBuffer。
类方法:buffer .from(buffer)
buffer{缓冲}
复制传递的 buffer 数据放到新的 Buffer 实例。
const buf1 = Buffer.from('buffer');
const buf2 = Buffer.from(buf1);
buf1[0] = 0x61;
console.log(buf1.toString());
// 'auffer'
console.log(buf2.toString());
// 'buffer' (copy is not changed)
一个 TypeError 会被抛出 buffer 不是。 Buffer。
类方法:Buffer.from(str[, encoding])
str{String}要编码的字符串。encoding{String}使用的编码,默认值:'utf8'
创建一个新的 Buffer 包含给定的JavaScript字符串 str。如果
提供, encoding 参数标识字符编码。
如未提供, encoding 默认为 'utf8'。
const buf1 = Buffer.from('this is a tést');
console.log(buf1.toString());
// prints: this is a tést
console.log(buf1.toString('ascii'));
// prints: this is a tC)st
const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex');
console.log(buf2.toString());
// prints: this is a tést
一个 TypeError 会被抛出 str 不是字符串。
类方法:Buffer。Alloc (size[, fill[, encoding]])
size{}数量fill{}的值默认值:undefinedencoding{}字符串默认值:utf8
分配一个新的 Buffer 的 size 字节。如果 fill 是 undefined,
Buffer 将 zero-filled。
const buf = Buffer.alloc(5);
console.log(buf);
// <Buffer 00 00 00 00 00>
这个 size 必须小于或等于
require('buffer').kMaxLength (在64位架构上, kMaxLength 是
(2^31)-1). 否则,a [RangeError[]被抛出。一个零长度的缓冲区可以
创建,如果 size 指定小于或等于0。
如果 fill ,则分配的 Buffer 将通过调用
buf.fill(fill)。参见[buf.fill()[]获取更多信息。
const buf = Buffer.alloc(5, 'a');
console.log(buf);
// <Buffer 61 61 61 61 61>
如果两个 fill 和 encoding 指定,分配 Buffer 将
通过调用初始化 buf.fill(fill, encoding)。例如:
const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
console.log(buf);
// <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
调用 Buffer.alloc(size) 会比另一种选择慢得多吗
Buffer.allocUnsafe(size) 但确保了新创建的 Buffer 实例
内容将 不要包含敏感数据。
一个 TypeError 会被抛出 size 不是一个数字。
类方法:Buffer.allocUnsafe(size)
size{}数量
分配一个新的 non-zero-filled Buffer 的 size 字节。这个 size 必须
的值小于或等于 require('buffer').kMaxLength (在64位
架构, kMaxLength 是 (2^31)-1). 否则,a [RangeError] []
抛出。将创建一个零长度缓冲区 size 小于或等于
指定0。
的底层内存 Buffer 以这种方式创建的实例是 不
初始化。新创建的内容 Buffer 是未知的
可能包含敏感数据。使用[buf.fill(0)[]初始化这样的
Buffer 实例归零。
const buf = Buffer.allocUnsafe(5);
console.log(buf);
// <Buffer 78 e0 82 02 01>
// (octets will be different, every time)
buf.fill(0);
console.log(buf);
// <Buffer 00 00 00 00 00>
一个 TypeError 会被抛出 size 不是一个数字。
请注意 Buffer 模块预分配一个内部的 Buffer 的实例
大小 Buffer.poolSize 它被用作快速分配new的池
Buffer 使用以下命令创建的实例 Buffer.allocUnsafe(size) (以及被弃用的
new Buffer(size) 构造函数)仅当 size 是小于还是等于
Buffer.poolSize >> 1 (楼 Buffer.poolSize 除以2)。默认的
的价值 Buffer.poolSize 是 8192 但是可以修改。
使用这种预分配的内部内存池是两者之间的关键区别
调用 Buffer.alloc(size, fill) vs。 Buffer.allocUnsafe(size).fill(fill)。
具体地说, Buffer.alloc(size, fill) 将 从来没有 使用内部缓冲区
池, Buffer.allocUnsafe(size).fill(fill) 将 使用内部
缓冲池 size 是小于还是等于1 / 2 Buffer.poolSize。这个
差异是微妙的,但当应用程序需要
额外的性能 Buffer.allocUnsafe(size) 提供了。
类方法:Buffer.allocUnsafeSlow(size)
size{}数量
分配一个新的 non-zero-filled 要,没有缓冲的、生命期 Buffer 的 size 字节。这个
size 必须小于或等于
require('buffer').kMaxLength (在64位架构上, kMaxLength 是
(2^31)-1). 否则,a [RangeError[]被抛出。一个零长度的缓冲区可以
创建,如果 size 指定小于或等于0。
的底层内存 Buffer 以这种方式创建的实例是 不
初始化。新创建的内容 Buffer 是未知的
可能包含敏感数据。使用[buf.fill(0)[]初始化这样的
Buffer 实例归零。
当使用 Buffer.allocUnsafe() 分配新的 Buffer 情况下,
默认情况下,4KB以下的分配是从单个预分配的分区中切片的
Buffer。这允许应用程序避免垃圾收集开销
创建许多单独分配的缓冲区。这种方法改进了
通过消除跟踪和清理的需要来提高性能和内存使用
许多 Persistent 对象。
然而,在开发人员可能需要保留一小块的情况下
从内存池中取出内存的时间不确定,这可能是合适的
来创建一个未池化的Buffer实例 Buffer.allocUnsafeSlow() 然后
把相关的部分抄下来。
// need to keep around a few small chunks of memory
const store = [];
socket.on('readable', () => {
const data = socket.read();
// allocate for retained data
const sb = Buffer.allocUnsafeSlow(10);
// copy the data into the new allocation
data.copy(sb, 0, 0, 10);
store.push(sb);
});
使用 Buffer.allocUnsafeSlow() 应该作为最后的手段吗 后
一个开发人员在他们的应用程序中发现了不适当的内存保留。
一个 TypeError 会被抛出 size 不是一个数字。
其他的
剩下的 Buffer API与node.js中的完全相同。
请参阅文档。
相关链接
为什么 Buffer 不安全吗?
今天,node.js Buffer 构造函数被重载以处理许多不同的参数
类型等 String, Array, Object, TypedArrayView (Uint8Array等),
ArrayBuffer,而且 Number。
该API是为了方便而优化的:您可以向它抛出任何类型,它将尝试这样做 你想要什么。
因为Buffer构造函数非常强大,你经常会看到这样的代码:
// Convert UTF-8 strings to hex
function toHex (str) {
return new Buffer(str).toString('hex')
}
但是如果 toHex 是用 Number 论点吗?
远程内存公开
如果攻击者可以使您的程序调用 Buffer 的构造函数 Number
参数,然后他们可以让它从node.js进程中分配未初始化的内存。
这可能会泄露TLS私钥、用户数据或数据库密码。
当 Buffer 构造函数传递给 Number 参数时,它返回一个
未初始化 指定的内存块 size。当你创建 Buffer 就像
这个,你 必须 在将内容返回给用户之前覆盖它。
从 node . js文件:
new Buffer(size)
size数量的底层内存
Buffer以这种方式创建的实例不会初始化。 新创建的内容Buffer是否未知且可能含有敏感物质 数据。 使用buf.fill(0)将Buffer初始化为0。
(强调我们自己的观点。)
每当程序员打算创建一个未初始化的 Buffer 你经常会看到代码
是这样的:
var buf = new Buffer(16)
// Immediately overwrite the uninitialized buffer with data from another buffer
for (var i = 0; i < buf.length; i++) {
buf[i] = otherBuf[i]
}
这在实际代码中会成为问题吗?
是。在a中忘记检查变量的类型是非常常见的 动态类型语言,如JavaScript。
通常,假设错误的类型的后果是您的程序崩溃
未捕获异常。但是忘记检查参数类型的失败模式
这个 Buffer 构造函数更具灾难性。
下面是一个易受攻击的服务示例,该服务接受JSON有效负载并将其转换为 十六进制:
// Take a JSON payload {str: "some string"} and convert it to hex
var server = http.createServer(function (req, res) {
var data = ''
req.setEncoding('utf8')
req.on('data', function (chunk) {
data += chunk
})
req.on('end', function () {
var body = JSON.parse(data)
res.end(new Buffer(body.str).toString('hex'))
})
})
server.listen(8080)
在这个例子中,http客户端只需要发送:
{
"str": 1000
}
它将从服务器获得1000字节未初始化的内存。
这是一个非常严重的错误。在严重程度上和 心脏出血bug 允许公开OpenSSL进程 内存被远程攻击者攻击。
哪些真实世界的软件包是易受攻击的?
bittorrent-dht
马赛厄斯我校 和我
(Feross Aboukhadijeh)在我们自己的一个软件包中发现了这个问题,
bittorrent-dht。这个漏洞将允许
任何人在互联网上发送一系列的消息给用户 bittorrent-dht 并获得
它们每次显示来自node.js进程的未初始化内存的20个字节。
这是 提交 这就解决了问题。我们发布了一个新的固定版本,创建了一个 节点安全项目披露,弃用一切 NPM上的易受攻击版本,因此用户将收到升级到新版本的警告。
ws
这让我们怀疑是否还有其他易受攻击的软件包。果然,在短时间内
一段时间以来,我们发现了同样的问题 ws,
在node.js中最流行的WebSocket实现。
如果调用了某些api Number 参数而不是 String 或 Buffer 作为
预期,那么未初始化的服务器内存将被公开给远程对等端。
这些是易受攻击的方法:
socket.send(number)
socket.ping(number)
socket.pong(number)
下面是一个带有回显功能的易受攻击的套接字服务器:
server.on('connection', function (socket) {
socket.on('message', function (message) {
message = JSON.parse(message)
if (message.type === 'echo') {
socket.send(message.data) // send back the user's message
}
})
})
socket.send(number) 在服务器上调用,将公开服务器内存。
这是 释放 问题在哪里? 是固定的,并有更详细的解释。道具 Arnout Kazemier 为了快速解决问题。这是 节点安全项目披露。
解决办法是什么?
重要的是,node.js提供了一种快速获取内存的方法,否则对性能至关重要 应用程序将不必要地变得慢很多。
但我们需要更好的方法 表明我们的意图 作为程序员。 当我们需要的时候 未初始化的内存,我们应该显式请求它。
敏感的功能不应该被打包到开发人员友好的API中 接受许多不同的类型。这种类型的API鼓励懒惰的传递实践 在没有仔细检查类型的情况下导入变量。
一个新的API: Buffer.allocUnsafe(number)
使用未初始化内存创建缓冲区的功能应该是另一个功能的一部分
API。我们建议 Buffer.allocUnsafe(number)。这样,它就不是API的一部分
经常获取传递给它的各种不同类型的用户输入。
var buf = Buffer.allocUnsafe(16) // careful, uninitialized memory!
// Immediately overwrite the uninitialized buffer with data from another buffer
for (var i = 0; i < buf.length; i++) {
buf[i] = otherBuf[i]
}
我们如何修复node.js核心?
我们发送 一个PR到node.js核心 (合并
semver-major),就一个案件进行辩护;
var str = 16
new Buffer(str, 'utf8')
在这种情况下,这意味着程序员希望第一个参数是a
字符串,因为它们传递了一个编码作为第二个参数。今天,node.js将分配
的情况下未初始化的内存 new Buffer(number, encoding),这可能不是
程序员的意图。
但这只是部分解决方案,因为如果程序员这样做 new Buffer(variable)
(没有一个 encoding 参数),没有办法知道他们的意图。如果 variable
有时是一个数字,那么有时会返回未初始化的内存。
真正的长期解决方案是什么?
我们可以弃用和删除 new Buffer(number) 和使用 Buffer.allocUnsafe(number) 当
我们需要未初始化的内存。但这将破坏数千个包。
我们认为最好的解决方案是:
1. 改变 new Buffer(number) 返回安全的、归零的内存
2. 创建一个用于创建未初始化缓冲区的新API。我们建议: Buffer.allocUnsafe(number)
更新
我们现在支持添加三个新的api:
Buffer.from(value)-从任何类型转换为缓冲区Buffer.alloc(size)-创建一个零填充缓冲区Buffer.allocUnsafe(size)-创建一个给定大小的未初始化缓冲区
这就解决了影响的核心问题 ws 和 bittorrent-dht 这是
Buffer(variable) 被骗接受一个数字参数。
通过这种方式,现有的代码可以继续工作,而对npm生态系统的影响将会减小
最小的。随着时间的推移,npm维护者可以将性能关键型代码迁移使用
Buffer.allocUnsafe(number) 而不是 new Buffer(number)。
结论
我们认为这是一个严重的设计问题 Buffer 今天的API。它
通过将高风险功能放入方便的API中来促进不安全的软件
友好的“开发者人体工程学”。
这不仅仅是一个理论练习,因为我们在一些 最流行的NPM包。
幸运的是,今天有一个简单的解决办法。使用 safe-buffer 代替
buffer。
var Buffer = require('safe-buffer').Buffer
最终,我们希望node.js核心可以切换到这种新的、更安全的行为。我们相信
对生态系统的影响将是最小的,因为这不是一个突破性的变化。
维护良好的、流行的软件包将被更新以使用 Buffer.alloc 很快,而
旧的、不安全的包将神奇地从这种攻击向量中变得安全。
链接
信贷
最初的问题 bittorrent-dht
(信息披露),
ws (信息披露)被
马赛厄斯我校 和
Feross Aboukhadijeh。
多亏了 亚当·鲍德温 帮助揭露这些问题 以及他的工作 节点安全项目。
多亏了 约翰Hiesey 校对本自述文件和 审核代码。
许可证
麻省理工学院。版权(C) Feross Aboukhadijeh