Давайте поговорим о eval в JavaScript.

Вы, наверное, слышали об этом. В противном случае документация MDN расскажет вам все, что вы хотите знать. Короче говоря, это функция, которая пытается оценить строку как код JavaScript.

Если для вас это звучит опасно, то вы правы. Это! Если злоумышленник может контролировать строку, которую вы передаете этой функции, он может делать все, что пожелает. Это распространяется от атак XSS в браузере до атак на стороне сервера в Node.js. 😱

Вот несколько слов, которые помогут сломать материал:

Если атакованный веб-сайт не имеет какого-либо типа CSP, мы можем даже украсть файлы cookie пользователей с помощью одной строки кода, отправив запрос на веб-сайт, находящийся под нашим контролем:

Итак, вот вопрос: можем ли мы сделать использование eval безопасным? Давайте рассмотрим наш первый пример, в котором мы убиваем текущий процесс узла.

Наша первая идея могла бы быть чем-то вроде поиска в строке скобок, чтобы избежать вызова функции:

Это работает для данного кода. Но мы можем легко обойти это с помощью шаблонных литералов:

Никаких скобок, тем не менее выполнение функции. 😕

Тогда почему бы нам просто не поискать слово kill внутри строки кода?

Теперь мы будем в безопасности, правда? Не совсем. Оказывается, существует бесчисленное множество способов написать kill, фактически не записывая точную строку.

Фактически вы можете написать что угодно (а также оценить что угодно), используя всего шесть символов (, ), [, ], ! и +. Посмотрите демо на JSFuck!

Давайте применим это здесь и воспользуемся тем фактом, что запуск (![]+[])[!+[]+!+[]] вернет строку l:

Опять же, проверка безопасности обойдена! 😕 😢

Так что давайте откажемся от попыток искать в строке плохой код. Давайте сделаем что-нибудь более умное! Если мы не хотим, чтобы код обращался к объекту process, то почему бы нам не «переопределить» его. Чтобы мы фактически не переопределяли его, мы используем IIFE, используя преимущества лексической области видимости JavaScripts. (Это означает, что переменная process, определенная внутри функции, не переопределит глобальный объект, а скорее заменит его в области видимости текущей функции.)

Если мы запустим этот код, мы получим ошибку, в которой говорится, что process.kill не является функцией. (На самом деле это не так, потому что это просто undefined!) Отлично, правда?

Что ж, как вы могли догадаться, есть способ обойти и это. Для этого воспользуемся конструктором Function. Он работает как eval в том смысле, что принимает строку, которая будет оцениваться каждый раз при вызове результирующей функции.

Но есть тонкая разница. Когда мы определяем функцию с помощью конструктора, функция не создает собственного замыкания. Функция всегда будет выполняться в глобальном масштабе! (Такое поведение также можно найти в документации.)

Мы можем использовать это, чтобы освободиться от закрытия IIFE. Это означает, что мы снова сможем получить доступ к глобальному объекту process. Давай попробуем:

Мы тоже взломали эту! 😕 😢 😰

Но что, если мы также переопределим объект Function? Разве это не спасет нашу проблему?

Да, это предотвращает выполнение этого точного кода. Но, как это было раньше со строками, в JavaScript мы можем получить доступ к конструктору Function бесчисленным количеством способов. Это так же просто, как получить доступ к свойству constructor переменной, которая является функцией. Чтобы дать вам некоторое представление, все эти выражения true:

Мы можем просто выбрать один из них, и мы снова заставим наш эксплойт работать:

Оценка безопасного кода больше не требуется… 😕 😢 😰 😭

В конце концов, я не занимаюсь экспортом безопасности, но все же могу придумать довольно простые идеи, как обойти многие из этих проверок. (Мне всегда интересно, что могут придумать профессиональные хакеры…)

Может быть, я все-таки ошибаюсь, и есть способ сделать оценку кода пуленепробиваемой. Если да, то дайте мне знать! 😉

Если вы зашли так далеко, мое сообщение должно быть ясным: не используйте eval для оценки кода или Function для создания функций, если строка, которую вы передаете, не контролируется вами на 100%!

Фактически setTimeout и setInterval столь же опасны. Первый аргумент не обязательно должен быть функцией, он также может быть строкой! Затем эта строка будет оцениваться после тайм-аута или после каждого интервала.

Оставайтесь в безопасности и получайте удовольствие! 🎉