1. 程式人生 > >Node.js 指南(遷移到安全的Buffer建構函式)

Node.js 指南(遷移到安全的Buffer建構函式)

遷移到安全的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規則no-buffer-constructornode/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月達到其生命週期結束(→計劃)。

這意味著即使出現安全問題,這些版本的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規則no-buffer-constructornode/no-deprecated-api以避免意外的不安全Buffer API使用。

還有一個JSCodeshift codemod,用於自動將Buffer建構函式遷移到Buffer.alloc()Buffer.from(),請注意,它目前僅適用於引數為文字或使用兩個引數呼叫建構函式的情況。

如果你當前支援那些較舊的Node.js版本並且無法刪除對它們的支援,或者如果你支援包的舊分支,考慮在較舊的分支上使用變式2或變式3,因此使用這些舊分支的人也將收到修復。這樣,你將消除由不謹慎的Buffer API使用引起的潛在問題,並且在Node.js 10上執行程式碼時,你的使用者將不會觀察到執行時棄用警告。

變式2:使用polyfill

有三種不同的polyfill可用:

  • safer-buffer是整個Buffer API的替代品,在使用new Buffer()時會丟擲。

    你將採用與變式1完全相同的步驟,但是在使用新的Buffer API的所有檔案中都使用polyfill const Buffer = require('safer-buffer').Buffer
    不要使用舊的new Buffer() API,在新增上面一行的任何檔案中,使用舊的new Buffer() API將丟擲。

  • buffer-from和/或buffer-allocBuffer API各自部分的ponyfill,你只需新增與你正在使用的API相對應的包。

    你可以使用適當的名稱匯入所需的模組,例如const bufferFrom = require('buffer-from')然後使用它而不是呼叫new Buffer(),例如:new Buffer('test')變為bufferFrom('test')

    使用這種方法的一個缺點是從它們遷移出來的程式碼更改略多(因為你將使用不同名稱下的Buffer.from())。

  • safe-buffer也是整個Buffer API的替代品,但使用new Buffer()仍然可以像以前一樣工作。

    這種方法的缺點是它允許你在程式碼中使用較舊的new Buffer() API,這是有問題的,因為它可能會導致程式碼中出現問題,並將從Node.js 10開始發出執行時棄用警告(在此處閱讀更多內容)。

請注意,在任何一種情況下,你還必須手動刪除對舊Buffer API的所有呼叫,只是投入safe-buffer本身並不能解決問題,它只是為新API提供了一個polyfill,我看到有人犯了這個錯誤。

建議啟用ESLint規則no-buffer-constructornode/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 &amp;&amp; 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