本文 AI 產出,尚未審核

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.parseFunction + 正則驗證、第三方庫)
使用 eval() 解析 JSON JSON 注入、程式碼執行 使用 JSON.parse()
在 Node.js 中以 eval 執行外部檔案 本機系統被入侵、權限提升 使用 requireimport()vm,並加上路徑白名單
忽視 CSP 設定 雖然瀏覽器會阻止 eval,但仍可能在舊版或非瀏覽器環境執行 在 CSP 中加入 'unsafe-eval' 時要特別小心,盡量 使用
使用 new Function() 替代 eval 同樣可能被注入,若未做好字串驗證 僅在確定字串來源可信或已嚴格過濾時使用

具體的最佳實踐

  1. 最小權限原則:只允許必要的功能,避免給予程式碼過多的全域存取權。
  2. 白名單驗證:對所有動態字串使用白名單(正則或字元過濾),拒絕任何不在允許範圍內的輸入。
  3. 使用專門的函式庫:例如 mathjsjson5safe-eval(已棄用)等,讓解析工作交給已經驗證過的程式碼。
  4. 開啟 CSP:在 HTTP Header 中加入 Content-Security-Policy: script-src 'self',避免意外執行 eval
  5. 代碼審查與靜態分析:使用 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() 的關鍵在於:

  1. 使用原生安全 APIJSON.parseFunction + 嚴格驗證)。
  2. 引入專業的第三方函式庫(如 mathjsvm2),讓解析與執行工作交給已經過安全審核的程式碼。
  3. 在開發流程中加入靜態檢查,確保團隊不會不小心留下 eval

只要遵守上述原則,你的 JavaScript 應用就能遠離 eval() 帶來的危險,寫出更安全、更可靠的程式碼。祝你開發順利,寫出無懈可擊的前端與後端應用!