Образовательная платформа, позволяющая студентам составлять запросы MongoDB, отказалась от модуля Node.js vm и теперь парсит каждый запрос в абстрактное синтаксическое дерево (AST) перед выполнением. Это изменение устраняет песочницу, которую можно было взломать, защищая бэкенд от произвольного кода, который может быть внедрен через некорректно сформированную строку.

Почему исходная песочница не справилась

Первая реализация оборачивала активный дескриптор базы данных в вызов vm.runInContext и пыталась ограничить пользователей с помощью регулярного выражения. Разрешались только имена методов, таких как find или aggregate; все остальные идентификаторы должны были отфильтровываться.

Две ошибки сделали этот подход небезопасным:

  • Фильтрацию регулярными выражениями можно обойти. JavaScript позволяет использовать синтаксис квадратных скобок (obj["constructor"]) для доступа к любому свойству. Злоумышленник может получить доступ к конструктору Function, создать новую функцию и выполнить любой код. Регулярное выражение никогда не увидит доступ к базовому свойству, так как исходный код можно переписать бесчисленным количеством способов.
  • vm не является границей безопасности. В документации Node.js указано, что vm изолирует глобальный объект, но не весь процесс. Путем внедрения живого соединения с базой данных в песочницу код внутри контекста сохранял возможность вызывать любой метод этого соединения, включая те, которые записывают или удаляют данные. Песочница не мешала коду воздействовать на основной процесс.

Решение на основе AST

Команда заменила выполнение кода статическим анализом. Строки запросов теперь передаются парсеру acorn, который создает AST — древовидное представление синтаксической структуры кода. AST проверяется узел за узлом на соответствие строгому белому списку:

  • Литералы, массивы и объекты разрешены только тогда, когда они выступают в качестве простых значений.
  • Вызовы методов ограничены предопределенным набором (find, sort, limit и т. д.). Любой другой вызов отклоняется.
  • Доступ к вычисляемым свойствам (например, obj[expr]) или любой тип узла, не указанный в списке, вызывает немедленную ошибку.

Поскольку парсер работает с деревом, а не с необработанным текстом, его невозможно обмануть альтернативными написаниями или трюками с квадратными скобками. Цепочка конструкторов, которая могла бы обойти регулярное выражение, отображается как нераспознанный узел и отклоняется до запуска любого кода.

Что это значит для безопасности

Новая архитектура следует философии «запрещено по умолчанию»:

  • Определяйте то, что разрешено, а не то, что запрещено. Текстовые «белые списки» не могут быть исчерпывающими; AST имеет конечный набор типов узлов, что делает исчерпывающую проверку осуществимой.
  • Никогда не передавайте живые ресурсы внутрь песочницы. Передача дескриптора базы данных в изолированный контекст дает песочнице прямой доступ к бэкенду. Подход с парсером никогда не передает живой объект пользовательскому коду; он лишь извлекает намерение запроса.
  • Проверяйте структуру, а затем безопасно выполняйте. Как только AST проходит валидацию, платформа преобразует разрешенные вызовы в реальные методы драйвера MongoDB, используя собственный доверенный путь выполнения.

На что обратить внимание в дальнейшем

  • Проверяйте любое использование eval, new Function или vm в вашем коде. Даже белый список может быть обойден из-за динамической природы JavaScript.
  • Используйте AST-парсинг для пользовательского кода, где это возможно. Такие библиотеки, как acorn, esprima или babel-parser, упрощают этот процесс.
  • Ограничивайте доступ к живым объектам. Если к соединению с базой данных, дескриптору файла или сетевому сокету необходим доступ, оберните его в прокси, который открывает только минимально необходимые методы.
  • Автоматизируйте тестирование граничных случаев. Генерируйте запросы, использующие синтаксис квадратных скобок, вычисляемые ключи или манипуляции с прототипом, чтобы убедиться, что ваш парсер их отклоняет.

Итог

Использование vm.runInContext в качестве песочницы создает ложное чувство безопасности; фильтры на основе регулярных выражений не могут охватить гибкий синтаксис JavaScript, а песочница не изолирует ресурсы процесса. Парсинг пользовательского ввода в AST и использование белого списка только для понятных вам узлов создает надежный и поддерживаемый барьер, который останавливает вредоносный код до его запуска. Если ваша платформа позволяет пользователям писать код, замените выполнение в стиле eval на статический анализ уже сегодня.