Skip to main content

Чому eval небезпечний

eval() це вбудована функція, яка бере рядок і змушує рушій JavaScript виконати його як звичайний код. Вона одна з найнебезпечніших і найсуперечливіших функцій мови, бо стирає межу між даними і кодом, тому її майже завжди варто замінити безпечнішою конструкцією.

Теорія

TL;DR

  • eval(str) виконує вміст рядка str як JavaScript-код і повертає значення останнього виразу.
  • Головна проблема це безпека: неконтрольований рядок означає виконання довільного коду, XSS та ін'єкції.
  • Продуктивність падає, бо рушій не може заздалегідь проаналізувати й оптимізувати такий код (JIT блокується).
  • eval() працює в поточній області видимості, тому може створювати і перезаписувати змінні навколо себе.
  • Для майже всіх реальних задач є заміна: JSON.parse(), доступ за ключем obj[prop], мапа функцій, Function() у контрольованому випадку.

Швидкий приклад

javascript
eval("console.log('Hello from eval!')"); // output: Hello from eval! const x = 10; const y = 5; console.log(eval('x + y')); // 15

eval «розуміє», що рядок 'x + y' це вираз, обчислює його в поточній області видимості і повертає результат.

Що саме робить eval

eval() не «парсить формулу» і не має власної пісочниці: він передає рядок тому самому рушієві, який виконує решту вашого коду, і з тими самими правами.

Приклад 1, виконання виразу:

javascript
const x = 10; const y = 5; console.log(eval('x + y')); // 15

Приклад 2, створення змінних (небезпечно):

javascript
eval('var z = 100;'); console.log(z); // 100

Проблема в тому, що змінна z створюється в поточній області видимості і може перезаписати наявні значення.

Приклад 3, динамічний код (погана практика):

javascript
function runCode(code) { eval(code); } runCode("alert('running arbitrary code')");

Це виконає будь-який код, переданий рядком. Якщо цей рядок прийшов від користувача, ви отримуєте XSS-атаку або повний контроль над застосунком.

Небезпека для безпеки (XSS та ін'єкції)

eval() виконає будь-який переданий код, зокрема шкідливий, введений користувачем або зловмисником.

javascript
const userInput = "alert('pwned')"; eval(userInput); // runs straight away in the browser

Замість alert там може бути код, який читає document.cookie, localStorage чи токен сесії і надсилає їх на сторонній сервер. Це прямий шлях до XSS-атак і витоку даних. Саме тому Content Security Policy за замовчуванням блокує eval() директивою unsafe-eval.

Зниження продуктивності

Код усередині eval() виконується повільніше, бо рушій не може його оптимізувати: він не знає заздалегідь, що там буде. Кожен виклик eval() змушує рушій наново аналізувати та інтерпретувати рядок, а функція, що містить eval(), часто втрачає оптимізації JIT цілком, бо рушій не може довести, які змінні в ній використовуються.

Ламає область видимості

Код усередині eval() може створювати або змінювати змінні поточного контексту, що робить програму непередбачуваною і складною для налагодження.

javascript
let a = 10; eval('a = 99;'); console.log(a); // 99, the value changed "from the inside"

У строгому режимі ('use strict') ситуація трохи краща: eval() отримує власну область видимості для оголошень, тому var усередині нього більше не тече назовні. Але читати й перезаписувати наявні змінні він усе одно може.

Погана читабельність і підтримка

eval() робить код «магічним»: до моменту виконання невідомо, що саме буде запущено. Лінтери й статичний аналіз не бачать усередині рядка нічого, автодоповнення й перейменування символів у редакторі не працюють, а стектрейси вказують на згенерований код, а не на зрозуміле місце у файлі.

Безпечні альтернативи

ЗадачаЧим замінити
Виконати математичний виразFunction() або готовий парсер виразів
Розібрати JSONJSON.parse()
Виконати динамічний код із заздалегідь відомих функційОб'єкт з мапою функцій
Отримати властивість за іменемobj[propName], а не eval('obj.' + propName)

Приклади заміни.

Погано:

javascript
eval("user.name = 'Tim'");

Добре:

javascript
user['name'] = 'Tim';

Погано:

javascript
const obj = eval('(' + jsonStr + ')');

Добре:

javascript
const obj = JSON.parse(jsonStr);

Погано:

javascript
eval('sum(5, 10)');

Добре:

javascript
const actions = { sum: (a, b) => a + b }; actions['sum'](5, 10);

Типові помилки

  • Вважати, що new Function(...) безпечний. Він теж компілює рядок у код, просто в глобальній області видимості, тому для недовіреного вводу він так само небезпечний.
  • Використовувати eval() для розбору JSON. Це і повільніше, і вразливо; JSON.parse() існує саме для цього.
  • Будувати ім'я властивості рядком: eval('obj.' + key) замість obj[key]. Другий варіант коротший, швидший і безпечний.
  • Думати, що санітизація рядка робить eval() безпечним. Надійно екранувати довільний JavaScript практично неможливо.
  • Забувати про приховані форми eval: рядок як перший аргумент setTimeout('doWork()', 100) виконується так само.
  • Дивуватися, чому eval() не працює в продакшені: CSP без unsafe-eval блокує його, і це нормальна поведінка.

Коротко запам'ятати

АспектОцінка
Що робитьВиконує рядок як JavaScript-код
БезпекаДуже небезпечно: XSS та ін'єкції
ПродуктивністьПовільно
НалагодженняСкладно
ОптимізаціяБлокує JIT
Коли використовуватиЛише в дуже рідких випадках, коли рядок повністю під вашим контролем

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.