eval() 的風險
JavaScript 安全與防護(Security)單元
簡介
在前端開發中,eval() 常被視為「萬能」的函式:只要把字串丟進去,就能即時執行任意 JavaScript 程式碼。這種彈性在某些快速原型或工具腳本裡看起來很方便,但同時也埋下了 嚴重的安全隱憂。
如果不慎將使用者提供的資料直接傳入 eval(),攻擊者可以注入惡意程式碼,執行跨站腳本(XSS)、竊取敏感資訊,甚至在 Node.js 環境下取得系統權限。
因此,了解 eval() 的運作原理、可能的攻擊向量,以及如何安全地取代它,是每位 JavaScript 開發者必備的基礎知識。本篇文章將從概念說明、實作範例、常見陷阱到最佳實踐,全面剖析 eval() 的風險,並提供可行的替代方案,協助你寫出更安全、更可維護的程式碼。
核心概念
1. eval() 的基本行為
eval() 會把傳入的字串 當成程式碼,在當前執行環境(執行上下文)中編譯並立即執行。簡單的例子如下:
const code = "2 + 3";
console.log(eval(code)); // 5
看似無害,但若字串來源不受信任,問題就會出現。
2. 執行上下文與作用域污染
eval() 在 呼叫它的作用域 中執行,這意味著它可以讀寫同一作用域內的變數。
let secret = "mySecret";
function run(userInput) {
eval(userInput); // 只要 userInput 包含 `secret = "hacked"`,就能改變變數
}
run('secret = "hacked"');
console.log(secret); // "hacked"
這種 作用域污染 讓攻擊者可以直接操作程式內部狀態,導致資料外洩或邏輯錯誤。
3. eval() 與 CSP(Content Security Policy)
現代瀏覽器支援 CSP 來限制腳本的執行來源。若網站設定了 script-src 'self',eval()(屬於 'unsafe-eval')會被直接阻擋,導致程式碼無法執行。這是 瀏覽器層面的防護,但仍不代表可以安全使用 eval()。
程式碼範例
以下示範 4 個常見的 eval() 用法,並說明為何它們會產生風險,以及如何改寫。
範例 1:動態算式計算
// ❌ 直接使用 eval 來計算使用者輸入的算式
function calculate(expr) {
return eval(expr); // 風險:使用者可以注入任意程式碼
}
console.log(calculate("2 * (3 + 4)")); // 14
問題:如果使用者輸入 "alert('XSS')",會直接彈出警告,或更嚴重的代碼。
安全寫法:使用 Function 建構子或第三方算式解析器(如 mathjs)。
// ✅ 使用 Function 建構子,限制只能返回表達式結果
function safeCalculate(expr) {
// 只允許數字、運算子與小括號
if (!/^[0-9+\-*/().\s]+$/.test(expr)) {
throw new Error("不允許的字元");
}
return new Function(`"use strict"; return (${expr});`)();
}
console.log(safeCalculate("2 * (3 + 4)")); // 14
重點:雖然
Function仍會執行字串,但我們先做字元過濾,降低注入機會。
範例 2:JSON 文字解析
// ❌ 用 eval 解析 JSON(過時且危險)
const jsonStr = '{ "name": "Alice", "age": 25 }';
const data = eval('(' + jsonStr + ')');
console.log(data.name);
問題:如果 jsonStr 來自外部來源,攻擊者可以注入 }; maliciousCode(); //,導致任意程式碼執行。
安全寫法:使用原生 JSON.parse()。
// ✅ 正確的 JSON 解析方式
const data2 = JSON.parse(jsonStr);
console.log(data2.name);
範例 3:動態屬性存取
// ❌ 用 eval 讀取物件的屬性
const user = { id: 1, name: "Bob" };
function getProp(prop) {
return eval(`user.${prop}`);
}
console.log(getProp("name")); // Bob
問題:若 prop 為 "__proto__.polluted = true",會污染原型鏈,造成全域安全漏洞。
安全寫法:使用方括號語法或 Reflect.get,並檢查屬性名稱。
// ✅ 安全的屬性存取
function safeGetProp(obj, prop) {
if (!Object.prototype.hasOwnProperty.call(obj, prop)) {
throw new Error("屬性不存在或不允許存取");
}
return obj[prop];
}
console.log(safeGetProp(user, "name"));
範例 4:Node.js 中的動態模組載入
// ❌ 在 Node.js 中使用 eval 讀取外部檔案內容
const fs = require('fs');
function runScript(file) {
const code = fs.readFileSync(file, 'utf8');
eval(code); // 風險:任何檔案都會被執行
}
runScript('./unsafe.js');
問題:如果 file 參數被操控,攻擊者可以執行惡意腳本,取得系統權限。
安全寫法:使用 require() 或 vm.Module,並限制可載入的目錄。
// ✅ 限制載入目錄,使用 require
const path = require('path');
function safeRunScript(file) {
const allowedDir = path.resolve(__dirname, 'scripts');
const target = path.resolve(allowedDir, file);
if (!target.startsWith(allowedDir)) {
throw new Error('不允許的檔案路徑');
}
require(target);
}
safeRunScript('good.js');
常見陷阱與最佳實踐
| 陷阱 | 可能的後果 | 建議的做法 |
|---|---|---|
直接把使用者輸入傳給 eval() |
任意程式碼執行、XSS、資料外洩 | 永遠 不要 直接使用,改用安全 API(JSON.parse、Function + 正則驗證、第三方庫) |
使用 eval() 解析 JSON |
JSON 注入、程式碼執行 | 使用 JSON.parse() |
在 Node.js 中以 eval 執行外部檔案 |
本機系統被入侵、權限提升 | 使用 require、import() 或 vm,並加上路徑白名單 |
| 忽視 CSP 設定 | 雖然瀏覽器會阻止 eval,但仍可能在舊版或非瀏覽器環境執行 |
在 CSP 中加入 'unsafe-eval' 時要特別小心,盡量 不 使用 |
使用 new Function() 替代 eval |
同樣可能被注入,若未做好字串驗證 | 僅在確定字串來源可信或已嚴格過濾時使用 |
具體的最佳實踐
- 最小權限原則:只允許必要的功能,避免給予程式碼過多的全域存取權。
- 白名單驗證:對所有動態字串使用白名單(正則或字元過濾),拒絕任何不在允許範圍內的輸入。
- 使用專門的函式庫:例如
mathjs、json5、safe-eval(已棄用)等,讓解析工作交給已經驗證過的程式碼。 - 開啟 CSP:在 HTTP Header 中加入
Content-Security-Policy: script-src 'self',避免意外執行eval。 - 代碼審查與靜態分析:使用 ESLint 的
no-eval規則或 SonarQube 等工具,強制團隊在 PR 時檢查eval的使用。
實際應用場景
1. 表單驗證或計算器
許多線上計算器會讓使用者輸入算式,若直接 eval,會有 XSS 風險。改用 算式解析器(如 mathjs)或自行實作簡易的四則運算解析器,才能保證安全。
2. 動態樣板渲染
舊有的前端框架(如早期的 Mustache)會把模板字串與資料合併後交給 eval。現代框架(React、Vue、Svelte)皆採用 虛擬 DOM 或 編譯階段 處理,根本不需要 eval。
3. 伺服器端插件系統
在 Node.js 中,若要提供「插件」功能,切勿直接 eval 使用者上傳的程式碼。可採用 sandbox(如 vm2)或 子進程,將執行環境隔離,避免程式碼直接存取主進程的變數與 API。
總結
eval() 雖然在語法上看起來簡單、功能強大,但它同時也是 安全漏洞的溫床。
- 它會在呼叫者的作用域中執行任意字串,導致變數污染與權限提升。
- 任何來自不可信來源的資料都不應直接交給
eval,否則會產生 XSS、代碼注入等風險。 - 現代瀏覽器的 CSP 已將
eval列為不安全的功能,使用時必須額外開啟'unsafe-eval',這本身就違背了安全最佳實踐。
取代 eval() 的關鍵在於:
- 使用原生安全 API(
JSON.parse、Function+ 嚴格驗證)。 - 引入專業的第三方函式庫(如
mathjs、vm2),讓解析與執行工作交給已經過安全審核的程式碼。 - 在開發流程中加入靜態檢查,確保團隊不會不小心留下
eval。
只要遵守上述原則,你的 JavaScript 應用就能遠離 eval() 帶來的危險,寫出更安全、更可靠的程式碼。祝你開發順利,寫出無懈可擊的前端與後端應用!