Безопасное программирование на JavaScript

Безопасное программирование на JavaScript

JavaScript — это основа фронтенда всего интернета. Независимо от того, транскомпилируете ли вы TypeScript в JavaScript, создаете ли быстрые скрипты на Node.js или строите красивый, но «простой» интерфейс, который взаимодействует с более сложными API, — JavaScript буквально повсюду. Из-за своей повсеместности он является первостепенной целью для атак. В этой статье мы рассмотрим десять советов по написанию более безопасного кода на JavaScript.

1. Межсайтовый скриптинг (XSS)

Первое, что всегда обсуждают в контексте безопасности JavaScript, — это межсайтовый скриптинг (XSS). XSS — это форма инъекции; она означает, что злоумышленник заставил ваше приложение интерпретировать или выполнить его вредоносный код, вместо того чтобы обрабатывать его как данные. Пользовательский ввод всегда следует рассматривать как данные, но, к сожалению, компьютеры можно обмануть, если мы не будем осторожны.

XSS — это единственный тип инъекций, который работает исключительно в JavaScript. Это также единственный тип инъекций, который атакует непосредственно пользователя, захватывая контроль над браузером и используя его против жертвы. Все остальные типы инъекций атакуют не пользователя: например, SQL-инъекции атакуют сервер баз данных, инъекции команд — операционную систему хоста, а LDAP-инъекции — LDAP-сервер.

С помощью XSS злоумышленник может использовать браузер для доступа к вашим cookies (включая сессионную информацию, если она хранится в cookie небезопасным способом), загрузки внешних скриптов (если это не заблокировано с помощью политики безопасности контента — CSP), установки кейлоггера, вандализма на вашем сайте и т.д. Всё, что может JavaScript, может сделать и XSS-атака; единственное ограничение — воображение злоумышленника.

Хотя случаи XSS стали реже благодаря повышению осведомленности (например, проекту OWASP Top Ten), современным фреймворкам, которые автоматически экранируют вывод, и более серьезному отношению команд к безопасности, эта проблема по-прежнему остается актуальной и высокой угрозой.

Чтобы свести к нулю шансы стать жертвой XSS, выполняйте следующие действия:

  • Проводите проверку входных данных для всей информации, предоставляемой или изменяемой пользователем. Если вам необходимо принять потенциально опасные символы (такие как <, >, ', ", - и т.д.), защитите свое приложение, экранируя их (добавляя обратную косую черту) или санируя (заменяя их на другие символы или полностью удаляя).
  • Кодируйте (экранируйте) вывод всего, что будет отображаться на экране, включая данные из API, которые будут использоваться фронтендом. Если ваш фреймворк может делать это за вас — это самый простой и эффективный способ.
  • Используйте заголовок Content Security Policy (CSP), чтобы указать все сторонние компоненты (особенно скрипты), которые разрешено использовать в вашем приложении. Первое, что попытается сделать вредоносный XSS-скрипт, — это подключиться к другому, более крупному скрипту в интернете.
  • Добавьте флаг HttpOnly к своим cookies, чтобы гарантировать, что даже если вы что-то упустили, атакующий не сможет получить доступ к вашей сессионной информации через cookies.
  • Проводите ручной код-ревью, используйте инструменты статического (SAST) или динамического (DAST) анализа или закажите пентест, чтобы быть абсолютно уверенными, что ничего не упустили.

2. Используйте фреймворки JavaScript, которые кодируют вывод за вас

React, Angular и Vue.js автоматически выполняют кодирование вывода, а также имеют множество других замечательных функций. При использовании любого фреймворка будьте осторожны с опасными функциями, которых следует избегать или использовать с особым вниманием, такими как dangerouslySetInnerHTML в React и функции bypassSecurityTrustAs* в Angular.

3. Избегайте встроенных скриптов (inline scripting)

Как бы ни было соблазнительно вставить JavaScript прямо в HTML, если вы «просто хотите быстро что-то сделать», это значительно увеличивает риск XSS (и создает проблемы с поддержкой кода в будущем). Кроме того, это создает беспорядок. Храните свой JavaScript в отдельных внешних файлах, чтобы сохранить этот дополнительный уровень безопасности и порядка. Вы можете определить свои скрипты в заголовке CSP, чтобы всё оставалось аккуратным и организованным. Это похоже на встроенный SQL, когда мы объединяем пользовательский ввод и напрямую передаем его на выполнение в базу данных — возникают опасные ситуации.

4. Используйте строгий режим (use strict)

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

Строгий режим предотвращает молчаливые ошибки, запрещает небезопасные действия, мешает использовать зарезервированные слова (например, let) не по назначению, предотвращает случайное создание глобальных переменных и делает использование this более безопасным и предсказуемым. Кроме того, строгий режим может улучшить производительность, помочь выявить ошибки раньше, направить вас к лучшим практикам и повысить безопасность.

5. Используйте инструменты с открытым исходным кодом

Многие из этих рисков хорошо известны, и разработчики из сообщества open-source создали инструменты для их mitigation. Используйте полезные библиотеки и инструменты с открытым исходным кодом (FOSS), чтобы писать более безопасный код:

  • DomPurify для санирования (очистки) ввода от XSS.
  • Retire.js для поиска устаревших, неподдерживаемых или опасных библиотек JavaScript.
  • Npm Audit, Yarn Audit, Snyk CLI (бесплатная версия) для проверки актуальности ваших зависимостей.
  • DOM Snitch — экспериментальное расширение для Chrome от Google, которое помогает выявлять небезопасные практики в клиентском коде.
  • Nodejsscan — бесплатный инструмент статического анализа для приложений Node.js.
  • Semgrep Community edition, Bearer CLI (бесплатная версия), Horusec и др. для проведения бесплатного статического анализа. Они работают с несколькими языками, включая JavaScript.
  • Zap для проведения бесплатного динамического анализа (DAST). Важно: получите разрешение от начальства, прежде чем запускать Zap на работе! Это хакерский инструмент, и его использование против любого веб-сайта или приложения без письменного разрешения является незаконным. Этот инструмент может нанести ущерб при неправильном использовании. Остальные инструменты абсолютно безопасны в использовании.

6. Четко обозначайте текст в коде

Будьте последовательны в своем коде: текст — это текст, и то, что находится внутри вашей переменной, не является кодом, который должен быть выполнен или интерпретирован. Мы обеспечиваем это, помещая любые данные от пользователя в безопасный для JavaScript или CSS элемент данных. Например, не используйте innerHTML (который интерпретируется), вместо этого используйте innerText или textContent, которые предназначены только для отображения текста и не интерпретируются. Всегда четко указывайте, что что-то является текстом; не позволяйте DOM (объектной модели документа) решать это за вас, потому что иногда он ошибается.

7. Применяйте переменные только к безопасным атрибутам

Если вы должны установить element.setAttribute с данными из переменной (которая часто содержит пользовательские данные и, следовательно, потенциально опасна), используйте только безопасные, статические атрибуты, а не динамические (изменяемые). Примеры безопасных атрибутов: align, alt, class, color, cols, dir, height, lang, title, width и т.д. Не используйте динамические, небезопасные атрибуты для element.setAttribute, такие как onclick или onblur. Атрибуты, в которых что-то «происходит» или меняется, не являются безопасными, и вам не следует использовать их с пользовательскими данными.

Говоря об использовании переменных (пользовательских данных)… их следует размещать только внутри значений CSS-свойств, а не в других контекстах. Мы не хотим, чтобы они заставляли наше ПО вести себя непредсказуемо.

Пример: <style> selector { property : $varUnsafe; } </style>

8. Проверяйте ввод на стороне сервера (бэкенде)

При проведении проверки входных данных в целях безопасности (не для удобства, скорости или других причин) проверка должна выполняться на стороне сервера, а не во фронтенде. Любой, кто использует перехватывающий прокси (например, Zap или Burp Suite), может «перехватить» запрос от вашего фронтенда, изменить его и передать на бэкенд, как будто он не был тронут. Если это сделано умело, бэкенд не узнает, что запрос был изменен после того, как покинул фронтенд.

Почему это плохо? Допустим, у вас есть проверка ввода в JavaScript, которая принимает только символы a-z, A-Z и цифры. Она принимает эти данные и затем отображает их пользователю. Теоретически приложение отклонит ввод "' or 1=1 --", так как некоторые символы не входят в белый список. Однако если злоумышленник использует перехватывающий прокси, он может ввести «Hello» в поле, оно пройдет проверку, а затем он изменит поле в запросе на "' or 1=1 --". Если на бэкенде проверки нет, приложение использует эти данные как обычно, потенциально подставляя их в SQL-запрос.

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

9. Избегайте проблемных функций

Избегайте следующих, часто проблемных, функций JavaScript. Особенно если вы собираетесь использовать их с пользовательскими данными. Если вы должны их использовать, действуйте с крайней осторожностью.

eval(), innerHTML, outerHTML, Function(), escape(), unescape(), document.write(), document.writeln(), setTimeout() / setInterval() со строковым аргументом, unescapeHTML(), decodeURI() / encodeURI(), with(), конструкторы, использующие строковый аргумент (переменную), new function, и setAttribute() (помните: используйте только со статическими значениями).

10. Защищайте его как любое другое приложение

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

Из стандартных элементов «безопасного кодирования» самым важным является следование жизненному циклу безопасной разработки (Secure SDLC). Это означает, что ваша команда добавляет дополнительные процессы для обеспечения безопасности, надежности и устойчивости ПО. Некоторые из таких активностей: моделирование угроз, безопасный код-ревью, автоматизированное динамическое тестирование, анализ сторонних зависимостей, усиление безопасности цепочки поставок ПО, создание security-пользовательских историй и включение требований безопасности в общие требования проекта. Любые меры безопасности, добавленные в ваш SDLC, помогут создавать более надежные системы, на которые могут положиться ваши пользователи.

Путь к написанию более безопасного JavaScript начинается с небольших, но последовательных изменений. Выберите одну или две лучшие практики и начните применять их уже сегодня. И не забудьте поделиться тем, что узнали, с коллегами. Обмениваясь знаниями и постоянно улучшая код, мы можем сделать JavaScript не только более красивым и мощным, но и безопасным для всех.

20
2025
Безопасное программирование на JavaScript | RF Blog