重要:這僅與執行 JavaScript 和 TypeScript 程式碼有關。話雖如此,寫作也可能是用其他語言運行其他程式碼的方向。
允許用戶在您的應用程式中執行他們的程式碼開啟了一個自訂和功能的世界,但它也使您的平檯面臨重大的安全威脅。
鑑於它是用戶程式碼,一切都在預料之中,從停止伺服器(可能是無限循環)到竊取敏感資訊。
本文將探討各種緩解執行使用者程式碼的策略,包括 Web Workers、靜態程式碼分析等等…
有很多場景需要運行使用者提供的程式碼,從 CodeSandbox 和 StackBiltz 這樣的協作開發環境到像 January 這樣的可自訂 API 平台。即使是代碼遊樂場也容易受到風險。
也就是說,安全運行使用者提供的程式碼的兩個基本優勢是:
運行使用者代碼並沒有什麼害處,除非您擔心這可能會導致某些資料被盜。無論您關心什麼數據,都將被視為敏感資訊。例如,在大多數情況下,JWT 是敏感資訊(也許用作身份驗證機制時)
考慮 JWT 儲存在隨每個請求發送的 cookie 中的潛在風險。使用者可能會無意中觸發將 JWT 發送到惡意伺服器的請求,並且...
最簡單,但風險最大。
當您執行此程式碼時,它會記錄該訊息。本質上,eval 是一個能夠存取全域/視窗範圍的 JS 解譯器。
此程式碼使用了在全域範圍內定義的 fetch。解釋器不知道這一點,但由於 eval 可以訪問窗口,所以它知道。這意味著在瀏覽器中運行 eval 與在伺服器環境或工作執行緒中運行不同。
這個怎麼樣...
此程式碼將停止瀏覽器標籤。您可能會問為什麼使用者會對自己這樣做。好吧,他們可能正在從互聯網上複製代碼。這就是為什麼最好使用/或限制執行時間來進行靜態分析。
您可能想查看有關 eval 的 MDN 文件
時間框執行可以透過在 Web Worker 中執行程式碼並使用 setTimeout 來限制執行時間來完成。
這與 eval 類似,但它更安全一些,因為它無法存取封閉的範圍。
此程式碼將記錄 2.
注意:第二個參數是函數體。
函數構造函數無法存取封閉範圍,因此以下程式碼將拋出錯誤。
但它可以存取全域範圍,因此上面的 fetch 範例可以工作。
您可以在 WebWorker 上運行“函數構造函數和 eval”,由於沒有 DOM 訪問,這會更安全。
要實施更多限制,請考慮禁止使用全域對象,例如 fetch、XMLHttpRequest、sendBeacon 查看本文以了解如何做到這一點。
Isolated-VM 是一個函式庫,可讓您在單獨的 VM 中執行程式碼(v8 的 Isolate 介面)
此程式碼將記錄 hello world
這是一個令人興奮的選項,因為它提供了運行程式碼的沙盒環境。需要注意的是,您需要一個具有 Javascript 綁定的環境。然而,一個名為 Extism 的有趣項目促進了這一點。您可能想遵循他們的教程。
它的迷人之處在於,您將使用eval 來運行程式碼,但考慮到WebAssembly 的性質,DOM、網路、檔案系統和對主機環境的存取都是不可能的(儘管它們可能根據wasm 的不同而有所不同)運行時).
function evaluate() { const { code, input } = JSON.parse(Host.inputString()); const func = eval(code); const result = func(input).toString(); Host.outputString(result); } module.exports = { evaluate };
You'll have to compile the above code first using Extism, which will output a Wasm file that can be run in an environment that has Wasm-runtime (browser or node.js).
const message = { input: '1,2,3,4,5', code: ` const sum = (str) => str .split(',') .reduce((acc, curr) => acc + parseInt(curr), 0); module.exports = sum; `, }; // continue running the wasm file
We're now moving to the server-side, Docker is a great option to run code in an isolation from the host machine. (Beware of container escape)
You can use dockerode to run the code in a container.
import Docker from 'dockerode'; const docker = new Docker(); const code = `console.log("hello world")`; const container = await docker.createContainer({ Image: 'node:lts', Cmd: ['node', '-e', code], User: 'node', WorkingDir: '/app', AttachStdout: true, AttachStderr: true, OpenStdin: false, AttachStdin: false, Tty: true, NetworkDisabled: true, HostConfig: { AutoRemove: true, ReadonlyPaths: ['/'], ReadonlyRootfs: true, CapDrop: ['ALL'], Memory: 8 * 1024 * 1024, SecurityOpt: ['no-new-privileges'], }, });
Keep in mind that you need to make sure the server has docker installed and running. I'd recommend having a separate server dedicated only to this that acts as a pure-function server.
Moreover, you might benefit from taking a look at sysbox, a VM-like container runtime that provides a more secure environment. Sysbox is worth it, especially if the main app is running in a container, which means that you'll be running Docker in Docker.
This was the method of choice at January but soon enough, the language capabilities mandated more than passing the code through the container shell. Besides, for some reason, the server memory spikes frequently; we run the code inside self-removable containers on every 1s debounced keystroke. (You can do better!)
I'm particularly fond of Firecracker, but it’s a bit of work to set up, so if you cannot afford the time yet, you want to be on the safe side, do a combination of static analysis and time-boxing execution. You can use esprima to parse the code and check for any malicious act.
Well, same story with one (could be optional) extra step: Transpile the code to JavaScript before running it. Simply put, you can use esbuild or typescript compiler, then continue with the above methods.
async function build(userCode: string) { const result = await esbuild.build({ stdin: { contents: `${userCode}`, loader: 'ts', resolveDir: __dirname, }, inject: [ // In case you want to inject some code ], platform: 'node', write: false, treeShaking: false, sourcemap: false, minify: false, drop: ['debugger', 'console'], keepNames: true, format: 'cjs', bundle: true, target: 'es2022', plugins: [ nodeExternalsPlugin(), // make all the non-native modules external ], }); return result.outputFiles![0].text; }
Notes:
Additionally, you can avoid transpiling altogether by running the code usingDenoorBunin a docker container since they support TypeScript out of the box.
Running user code is a double-edged sword. It can provide a lot of functionality and customization to your platform, but it also exposes you to significant security risks. It’s essential to understand the risks and take appropriate measures to mitigate them and remember that the more isolated the environment, the safer it is.
以上是運行不受信任的 JavaScript 程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!