當前位置:
首頁 > 最新 > 為 Node.js 應用建立一個更安全的沙箱環境

為 Node.js 應用建立一個更安全的沙箱環境

雲棲君導讀:作者分享了有哪些動態執行腳本的場景?怎樣安全的執行動態腳本?在 Node.js 中呢,有沒有其它選擇?有哪些做了進一步工作的社區模塊?如何建立一個更安全一些的沙箱等內容。

有哪些動態執行腳本的場景?

在一些應用中,我們希望給用戶提供插入自定義邏輯的能力,比如 Microsoft 的 Office 中的 VBA,比如一些遊戲中的 lua 腳本,FireFox 的「油猴腳本」,能夠讓用戶發在可控的範圍和許可權內發揮想像做一些好玩、有用的事情,擴展了能力,滿足用戶的個性化需求。

大多數都是一些客戶端程序,在一些在線的系統和產品中也常常也有類似的需求,事實上,在線的應用中也有不少提供了自定義腳本的能力,比如 Google Docs 中的 Apps Script,它可以讓你使用 JavaScript 做一些非常有用的事情,比如運行代碼來響應文檔打開事件或單元格更改事件,為公式製作自定義電子表格函數等等。

與運行在「用戶電腦中」的客戶端應用不同,用戶的自定義腳本通常只能影響用戶自已,而對於在線的應用或服務來講,有一些情況就變得更為重要,比如「安全」,用戶的「自定義腳本」必須嚴格受到限制和隔離,即不能影響到宿主程序,也不能影響到其它用戶。

而 Safeify 就是一個針對 Nodejs 應用,用於安全執行用戶自定義的非信任腳本的模塊。

怎樣安全的執行動態腳本?

我們先看看通常都能如何在 JavaScript 程序中動態執行一段代碼?比如大名頂頂的 eval

上述代碼沒有問題順利執行了,eval 是全局對象的一個函數屬性,執行的代碼擁有著和應程中其它正常代碼一樣的的許可權,它能訪問「執行上下文」中的局部變數,也能訪問所有「全局變數」,在這個場景下,它是一個非常危險的函數。

再來看看 Functon,通過 Function 構造器,我們可以動態的創建一個函數,然後執行它

它也一樣的順利執行了,使用 Function 構造器生成的函數,並不會在創建它的上下文中創建閉包,一般在全局作用域中被創建。當運行函數的時候,只能訪問自己的本地變數和全局變數,不能訪問 Function 構造器被調用生成的上下文的作用域。如同一個站在地上、一個站在一張薄薄的紙上一樣,在這個場景下,幾乎沒有高下之分。

結合 ES6 的新特性 Proxy 便能更安全一些

我們知道無論 eval 還是 function,執行時都會把作用域一層一層向上查找,如果找不到會一直到 global,那麼利用 Proxy 的原理就是,讓執行了代碼在 sandobx 中找的到,以達到「防逃逸」的目的。

在瀏覽器中,還可以利用 iframe,創建一個再發安全的一些隔離環境,本文也著眼於 Node.js,在這裡不做過多討論。

在 Node.js 中呢,有沒有其它選擇?

或許沒看到這兒之前你就已經想到了 VM,它是 Node.js 默認就提供的一個內建模塊,VM 模塊提供了一系列 API 用於在 V8 虛擬機環境中編譯和運行代碼。JavaScript 代碼可以被編譯並立即運行,或編譯、保存然後再運行。

執行上這的代碼就能拿到結果 3,同時,通過 vm.Script 還能指定代碼執行了「最大毫秒數」,超過指定的時長將終止執行並拋出一個異常

上面的腳本執行將會失敗,被檢測到超時並拋出異常,然後被 Try Cache 捕獲到並打出 log,但同時需要注意的是 vm.Script 的 timeout 選項「只針對同步代有效」,而不包括是非同步調用的時間,比如

上述代碼,並不是會在 50ms 後拋出異常,因為 50ms 上邊的代碼同步執行肯定完了,而 setTimeout 所用的時間並不算在內,也就是說 vm 模塊沒有辦法對非同步代碼直接限制執行時間。我們也不能額外通過一個 timer 去檢查超時,因為檢查了執行中的 vm 也沒有方法去中止掉。

另外,在 Node.js 通過 vm.runInContext 看起來似乎隔離了代碼執行環境,但實際上卻很容易「逃逸」出去。

執行上邊的代碼,宿主程序立即就會「退出」,sandbox 是在 VM 之外的環境創建的,需 VM 中的代碼的 this 指向的也是 sandbox,那麼

沒有人願意用戶一段腳本就能讓應用掛掉吧。除了退出進程序之外,實際上還能幹更多的事情。

有個簡單的方法就能避免通過 this.constructor 拿到 process,如下:

但還是有風險的,由於 JavaScript 本身的動態的特點,各種黑魔法防不勝防。事實 Node.js 的官方文檔中也提到 VM 當做一個安全的沙箱去執行任意非信任的代碼。

有哪些做了進一步工作的社區模塊?

在社區中有一些開源的模塊用於運行不信任代碼,例如 sandbox、vm2、jailed 等。相比較而言 vm2 對各方面做了更多的安全工作,相對安全些。

從 vm2 的官方 READM 中可以看到,它基於 Node.js 內建的 VM 模塊,來建立基礎的沙箱環境,然後同時使用上了文介紹過的 ES6 的 Proxy 技術來防止沙箱腳本逃逸。

用同樣的測試代碼來試試 vm2

如上代碼,並沒有成功結束掉宿主程序,vm2 官方 REAME 中說「vm2 是一個沙盒,可以在 Node.js 中按全的執行不受信任的代碼」。

然而,事實上我們還是可以干一些「壞」事情,比如:

上邊的代碼將永遠不會執行結束,如同 Node.js 內建模塊一樣 vm2 的 timeout 對非同步操作是無效的。同時,vm2 也不能額外通過一個 timer 去檢查超時,因為它也沒有辦法將執行中的 vm 終止掉。這會一點點耗費完伺服器的資源,讓你的應用掛掉。

那麼或許你會想,我們能不能在上邊的 sandbox 中放一個假的 Promise 從而禁掉 Promise 呢?答案是能提供一個「假」的 Promise,但卻沒有辦法完成禁掉 Promise,比如

可以看到通過一行 Promise = (async function(){})().constructor 就可以輕鬆再次拿到 Promise了。從另一個層面來看,況且或許有時我們還想讓自定義腳本支持非同步處理呢。

如何建立一個更安全一些的沙箱?

通過上文的探究,我們並沒有找到一個完美的方案在 Node.js 建立安全的隔離的沙箱。其中 vm2 做了不少處理,相對來講算是較安全的方案了,但問題也很明顯,比如非同步不能檢查超時的問題、和宿主程序在相同進程的問題。

沒有進程隔離時,通過 VM 創建的 sanbox 大體是這樣的

那麼,我們是不是可以嘗試,將非受信代碼,通過 vm2 這個模塊隔離在一個獨立的進程中執行呢?然後,執行超時時,直接將隔離的進程幹掉,但這裡我們需要考慮如下幾個問題

通過進程池統調度管理沙箱進程

如果來一個執行任務,創建一個進程,用完銷毀,僅處理進程的開銷就已經稍大了,並且也不能不設限的開新進程和宿主應用搶資源,那麼,需要建一個進程池,所有任務到來會創建一個 Script實例,先進入一個 pending 隊列,然後直接將 script 實例的 defer 對象返回,調用處就能 await 執行結果了,然後由 sandbox master 根據工程進程的空閑程序來調度執行,master 會將 script的執行信息,包括重要的 ScriptId,發送給空閑的 worker,worker 執行完成後會將「結果 + script 信息」回傳給 master,master 通過 ScriptId 識別是哪個腳本執行完畢了,就是結果進行 resolve或 reject 處理。

這樣,通過「進程池」即能降低「進程來回創建和銷毀的開銷」,也能確保不過度搶佔宿主資源,同時,在非同步操作超時,還能將工程進程直接殺掉,同時,master 將發現一個工程進程掛掉,會立即創建替補進程。

處理的數據和結果,還有公開給沙箱的方法

進程間如何通訊,需要「動態代碼」處理數據可以直接序列化後通過 IPC 發送給隔離 Sandbox 進程,執行結果一樣經過序列化通過 IPC 傳輸。

其中,如果想法公開一個方法給 sandbox,因為不在一個進程,並不能方便的將一個方案的引用傳遞給 sandbox。我們可以將宿主的方法,在傳遞給 sandbox worker 之類做一下處理,轉換為一個「描述對象」,包括了允許 sandbox 調用的方法信息,然後將信息,如同其它數據一樣發送給 worker 進程,worker 收到數據後,識出來所「方法描述對象」,然後在 worker 進程中的 sandbox 對象上建立代理方法,代理方法同樣通過 IPC 和 master 通訊。

最終,我們建立了一個大約這樣的「沙箱環境」

如此這般處理起來是不是感覺很麻煩?但我們就有了一個更加安全一些的沙箱環境了,這些處理。筆者已經基於 TypeScript 編寫,並封裝為一個獨立的模塊 Safeify。

GitHub: https://github.com/Houfeng/safeify ,歡迎 Star & Issues

最後,簡單介紹一下 Safeify 如何使用,通過如下命令安裝

在應用中使用,還是比較簡單的,如下代碼(TypeScript 中類似)

關於安全的問題,沒有最安全,只有更安全,Safeify 已在一個項目中使用,但自定義腳本的功能是僅針對內網用戶,有不少動態執行代碼的場景其實是可以避免的,繞不開或實在需要提供這個功能時,希望本文或 Safeify 能對大家有所幫助就行了。

end


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 雲棲社區 的精彩文章:

強化學習在錦囊位置調控上的探索和實踐
阿里巴巴陳博興:單天翻譯詞量超過千億的秘密

TAG:雲棲社區 |