Безопасность является важным фактором при создании frontend приложений, поскольку они часто являются отправной точкой для атак. Я решил собрать в одну статью основные меры, которых стоит придерживаться или о которых хотя бы нужно задуматься.

Следуя этим основным мерам безопасности, frontend приложения можно сделать более безопасными и менее уязвимыми для атак. Однако важно помнить, что безопасность - это непрерывный процесс, который должен постоянно контролироваться и улучшаться с течением времени. Меры могут быть комбинированы, а некоторые опущены и не использованы - это определяет контекст проекта. Контекст может не позволять заниматься некоторыми видами безопасности, однако настаивать на этом необходимо и полезно, так как это снижает не только технический уровень уязвимости, но и напрямую влияет на репутационные риски компании.

HTTPS

HTTPS расшифровывается как Hypertext Transfer Protocol Secure. Это протокол, используемый для шифрования данных, передаваемых через Интернет между клиентом (например, веб-браузером) и сервером. Протокол HTTPS использует комбинацию симметричного и асимметричного шифрования для обеспечения безопасной передачи данных и их невозможности перехвата или изменения неавторизованными сторонами.

Как HTTPS обеспечивает безопасность? (подробно и полезно)

Работает это примерно так. Когда клиент подключается к серверу по протоколу HTTPS, сервер отправляет клиенту свой сертификат SSL / TLS, который содержит открытый ключ, используемый для установления безопасного соединения. Затем клиент генерирует симметричный сеансовый ключ, который используется для шифрования и дешифрования данных, которыми обмениваются клиент и сервер во время сеанса.

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

Таким образом, протокол HTTPS шифрует данные, используя комбинацию симметричного и асимметричного шифрования, чтобы гарантировать, что данные передаются безопасно и что они не могут быть перехвачены или изменены неавторизованными сторонами. Это помогает защитить конфиденциальную информацию, такую как пароли, номера кредитных карт и личные данные, от перехвата злоумышленниками.

О каком перехвате идет речь?

Когда мы говорим о перехвате в контексте HTTPS, то имеем в виду перехват сетевых пакетов, которые передаются между клиентом (например, веб-браузером) и сервером через Интернет. Когда клиент подключается к серверу по незащищенному соединению (HTTP), данные, передаваемые между клиентом и сервером, отправляются в виде обычного текста, что означает, что они могут быть перехвачены и прочитаны любым, кто отслеживает сетевой трафик. Перехватить можно с помощью различных инструментов, таких как сетевые анализаторы (снифферы), которые захватывают и анализируют сетевые пакеты по мере их перемещения по сети.

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

Проверка входных данных (валидация)

Проверка входных данных - это процесс проверки достоверности введенных пользователем данных перед их обработкой приложением. Целью проверки является предотвращение инъекционных атак, которые происходят, когда злоумышленник вставляет вредоносный код (например, SQL, JavaScript или HTML) в приложение, используя уязвимости в полях ввода.

Распространенные типы инъекционных атак

SQL-инъекция

Этот тип атаки возникает, когда злоумышленник вставляет SQL-код в поле ввода, которое используется для запроса к базе данных. Злоумышленник может использовать SQL-инъекцию для доступа к конфиденциальным данным или выполнения вредоносных команд в базе данных.

Допустим, в приложении есть панель поиска, которая позволяет пользователям искать продукты по названию. Приложение может создать SQL-запрос для поиска продуктов на основе поискового запроса, введенного пользователем. Однако, если приложение не выполняет надлежащую валидацию, злоумышленник может ввести SQL-код в строку поиска, который будет выполнен приложением. Например, злоумышленник может ввести следующий поисковый запрос: OR 1=1 --. Это может привести к тому, что SQL-запрос вернет все продукты в базе данных, независимо от поискового запроса, введенного пользователем.

Атака с использованием межсайтового скриптинга (XSS)

Этот тип атаки возникает, когда злоумышленник вводит вредоносный код JavaScript в поле ввода, которое отображается другим пользователям. Злоумышленник может использовать XSS для кражи конфиденциальных данных, таких как сеансовые файлы cookie, или для перенаправления пользователей на вредоносный веб-сайт.

Допустим, в приложении есть раздел, где пользователи могут оставлять комментарии. Приложение может отображать комментарии на веб-странице без надлежащей валидации данных. Злоумышленник может опубликовать комментарий, содержащий вредоносный код JavaScript, который будет выполнен другими пользователями, просматривающими этот комментарий. Например, злоумышленник может опубликовать следующий комментарий: <script> alert('You have been hacked!'); </script>. Это приведет к появлению окна предупреждения на экранах всех пользователей, просматривающих комментарий.

Кстати, на хабре есть прекрасная статья, раскрывающая эту тему очень широко. XSS атакует! Не краткий обзор где и как искать уязвимости

Внедрение команд (sh-команды)

Этот тип атаки происходит, когда злоумышленник вставляет вредоносные команды в поле ввода, которое используется для выполнения системных команд. Злоумышленник может использовать внедрение команд для выполнения произвольных действий на сервере. Чтобы обезопасить приложение от атак с использованием инъекций, проверка входных данных должна выполняться для всех полей ввода в приложении.

Допустим, в приложении есть функция, которая позволяет пользователям загружать файлы. Приложение может использовать системную команду для обработки загруженного файла без надлежащей валидации входных данных. Злоумышленник может загрузить файл, содержащий вредоносные команды, которые будут выполняться приложением. Например, злодей загрузит файл со следующим именем: ; rm -rf /. Это приведет к тому, что приложение выполнит rm -rf / и удалит все файлы на сервере.

В действительности, большинство сайтов и приложений обладают внутренней защитой, либо разработчики уже позаботились о том, чтобы обеспечить её. Поэтому подобные примеры вряд ли можно будет протестировать на большинстве сайтов. Иначе, уже в текущей статье, было бы возможно наблюдать всплывающее сообщение "You have been hacked!", в качестве XSS внедрения.

Виды проверок

Белый список

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

const usernameInput = document.getElementById('username');
 
usernameInput.addEventListener('input', function(event) {
  const regex = /^[a-zA-Z0-9]+$/;
  const input = event.target.value;
 
  if (!regex.test(input)) {
    // Показать ошибку ввода
    // Не дать отправить форму
  }
});

Черный список

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

const phoneInput = document.getElementById('phone');
 
phoneInput.addEventListener('input', function(event) {
  const blacklist = /['";<>]/;
  const input = event.target.value;
 
  if (blacklist.test(input)) {
    // Показать ошибку ввода
    // Не дать отправить форму
  }
});

Очистка

Удаление или кодирование любых потенциально вредоносных символов из входных данных перед их обработкой. Например, преобразование любых HTML-тегов в соответствующие им HTML-объекты.

const commentInput = document.getElementById('comment');
 
commentInput.addEventListener('input', function(event) {
  const input = event.target.value;
  const sanitizedInput = input.replace(/</g, '&lt;').replace(/>/g, '&gt;');
});

Достаточно ли этих проверок?

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

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

Аутентификация и авторизация

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

Слабые и предсказуемые пароли

Здесь важно убедиться, что действуют надежные рекомендации для создания паролей. Слабые или легко угадываемые пароли могут представлять серьезную угрозу безопасности, поскольку они могут быть легко скомпрометированы злоумышленниками. Этот вариант носит рекомендательный характер для пользователя, но также обязан быть валидирован с двух сторон - frontend и backend. Короче, всё, что здесь можно сделать - это предупредить пользователя, что его пароль не лучшего качества и вообще принадлежит другому пользователю.

Небезопасное хранение данных аутентификации

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

localStorage.setItem('sessionToken', token);

В этом примере токен аутентификации хранится в локальном хранилище браузера, которое не является безопасным механизмом хранения. Злоумышленник, получивший доступ к устройству пользователя, может легко получить доступ к этому токену и использовать его для получения несанкционированного доступа к учетной записи пользователя. Более безопасным подходом было бы хранить токены в cookie с флагами HttpOnly и secure. Хотя на хабре существует статья, рассказывающая, как использовать localStorage просто потому что это возможно ????

Всё о файлах cookie и их безопасности

Отсутствие надлежащей проверки подлинности и авторизации

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

Атаки CSRF (подделка межсайтовых запросов)

Атаки CSRF происходят, когда злоумышленник обманом заставляет пользователя выполнить действие на веб-сайте без его ведома или согласия. Это может быть сделано путем создания вредоносной ссылки или формы, которая отправляет данные на веб-сайт, или путем использования уязвимостей в механизмах аутентификации веб-сайта. Чтобы предотвратить атаки CSRF, важно использовать токены CSRF или другие механизмы, чтобы гарантировать, что запросы поступают из правильного источника.

<form action="/changePassword" method="POST">
  <!-- Вот здесь -->
  <input type="hidden" name="_csrf" value="csrfToken">
  
  <input type="password" name="newPassword">
  <button type="submit">Change Password</button>
</form>

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

Если с предыдущими примерами всё довольно просто, то с CSRF могут потребоваться дополнительные разъяснения. Токен CSRF генерируется на сервере и появляется на странице в пользовательском HTTP-заголовке или cookie (или там и там). Когда пользователь отправляет форму или делает запрос, приложение включает токен в данные запроса, и сервер проверяет, соответствует ли токен значению, сохраненному в сессии пользователя или файле cookie. Для передачи токена существует специальный заголовок: 'X-CSRF-Token': csrfToken

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

CSP - политика безопасности содержимого

CSP - это функция безопасности, которая может помочь предотвратить различные типы атак, включая межсайтовый скриптинг (XSS) и атаки с использованием инъекций данных. CSP работает, позволяя определить белый список надежных источников, из которых веб-приложение может загружать ресурсы, такие как скрипты, таблицы стилей, изображения и шрифты. Любые ресурсы, которые явно не занесены в белый список, будут недоступны к загрузке.

Чтобы настроить CSP во внешнем интерфейсе, можно включить мета-тег Content-Security-Policy в раздел head HTML-документа. Значением тега должна быть строка, содержащая одну или несколько директив, определяющих источники контента, которые разрешено загружать браузеру. Вот пример мета-тега CSP, который позволяет загружать скрипты только из того же источника, что и страница:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">

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

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com">

Существует множество других директив, которые можно использовать с CSP для управления загрузкой различных типов контента:

  • default-src: Указывает источник по умолчанию для всех типов контента

  • script-src: Указывает источники, из которых могут быть загружены скрипты.

  • style-src: Указывает источники, из которых могут быть загружены таблицы стилей.

  • img-src: Указывает источники, из которых могут быть загружены изображения.

  • font-src: Указывает источники, из которых могут быть загружены шрифты.

  • connect-src: Указывает источники, к которым приложение может отправлять сетевые запросы (например, AJAX, WebSocket).

  • frame-src: Указывает источники, из которых могут быть загружены фреймы.

  • child-src: Указывает источники, из которых могут быть загружены дочерние фреймы.

Чтобы максимально повысить эффективность CSP, важно тщательно проанализировать использование внешних ресурсов приложением и определить политику, которая допускает только минимально необходимые источники. Это может помочь снизить риск атак и повысить общую безопасность приложения. Важно отметить, что в то время как Content-Security-Policy заголовок обеспечивает надежную защиту от многих типов атак, но не полностью исключает возможность межсайтовых скриптовых (XSS) атак. Вот несколько сценариев, в которых XSS-атака все еще может быть возможна даже при наличии CSP:

  1. Устаревшие браузеры
    Некоторые старые браузеры могут не полностью поддерживать Content-Security-Policy заголовок, или может поддерживать его таким образом, чтобы его можно было обойти.
    Злоумышленники могут использовать это в своих интересах, нацеливаясь на пользователей со старыми браузерами, которые не полностью реализуют CSP.

  2. Ресурсы, не совместимые с CSP
    Если приложение загружает ресурсы (например, изображения или скрипты) из стороннего домена, который не реализует CSP, злоумышленник потенциально может использовать этот ресурс для выполнения скрипта на странице.

  3. Неправильная настройка политики
    Неправильная настройка Content-Security-Policy может сделать приложение уязвимым для атак.
    Например, если непреднамеренно допускается unsafe-inline или unsafe-eval, злоумышленник потенциально может внедрить вредоносные скрипты на страницу.

  4. Методы обхода
    Как и в случае с любым механизмом безопасности, злоумышленники постоянно ищут способы обхода защиты CSP.
    Были случаи, когда злоумышленники находили способы обхода защиты CSP, с помощью ошибок браузера.

Крайне рекомендуется ознакомиться с документацией по использованию CSP.

Ограничение доступа к данным

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

  1. Свести к минимуму использование файлов cookie
    Файлы cookie часто используются для хранения информации о сессии пользователя или других конфиденциальных данных. Однако файлы cookie уязвимы для различных атак, таких как CSRF или перехват сессии.
    Поэтому важно ограничить объем данных, хранящихся в файлах cookie.

  2. Ограничить использование API
    Важно ограничить использование API и предоставлять необходимые данные или функциональные возможности только авторизованным пользователям.

  3. Использовать элементы управления доступом
    Элементы управления доступом можно использовать для ограничения доступа к функциям приложения. Реализуя управление доступом на основе ролевой модели, можно ограничить доступ к определенным частям приложения на основе роли или разрешений пользователя.

Несколько примеров, демонстрирующих ошибки, связанные с ограничением доступа к данным:

  • Отправка клиенту ненужных данных, таких как конфиденциальная информация: пароли, личные идентификационные номера или номера кредитных карт. 

  • Отправка всех данных сразу, а не только необходимых, которые нужны клиенту. 

  • Использование слабых паролей

  • Хранение конфиденциальных данных в виде открытого текста вместо их хеширования или шифрования. 

  • Отсутствие валидации пользовательского ввода, допускающая инъекционные атаки, такие как SQL-инъекция или межсайтовый скриптинг (XSS). 

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

  • Отображение конфиденциальной информации в сообщениях об ошибках, например запросов к базе данных или путей к файлам. 

Обновление зависимостей

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

  • Злоумышленник может внедрить вредоносный код в приложение через уязвимый пакет

  • Внедрение XSS

  • Удаленное выполнение кода (RCE)

  • Отказ в обслуживании (DoS) - приложение перестает отвечать на запросы или аварийно завершает работу

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

Рекомендации по безопасности GitHub
Безопасность npm-пакетов. Часть 1
Безопасность npm-пакетов. Часть 2

Вместо выводов

Да, конечно, я понимаю, что многие рекомендацию звучат как "спасибо, кэп". Но, поверьте, многие рекомендации лежат на видном месте, но их почему-то не видно в коде. Целью статьи было собрать в едином месте общие рекомендации и объяснить их простым языком с примерами и ссылками для ознакомления.

Комментарии (6)


  1. aleksandy
    23.05.2023 04:14

    const sanitizedInput = input.replace(/</g, '<').replace(/>/g, '>');

    Имелось ввиду

    const sanitizedInput = input.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    

    ?


    1. Libiros Автор
      23.05.2023 04:14

      Да, конечно. Спасибо, уже исправил


      1. dynamica
        23.05.2023 04:14

        Есть же Sanitizer API


        1. Libiros Автор
          23.05.2023 04:14

          Верно :)
          Так же верно и то, что его не всегда могут использовать (а надо бы)


  1. robzarel
    23.05.2023 04:14

    спасибо за статью, очень интересный контент)

    Недавно читал статью про выступление на хайлоад 2017 (достаточно старую, но кажется всё ещё актуальную статью), https://habr.com/ru/companies/oleg-bunin/articles/412855/, в которой описываются варианты обхода csrf защиты.

    Судя по статье с хайлоада - если есть xss уязвимость, то использование cookie (равно как и localstorage), для хранения токенов, становится небезопасным (куки можно угнать обойдя csrf защиту). И вроде получается, что основная защита от угона токена это как раз защита от внедрения чужого кода в своё приложение - csp/cors. т.е. будто бы не важно какой способ хранения токена выбрали - если словили xss, то имеем большой риск потери токена (пока срок его хранения не истёк).
    Если будет время/желание - было бы интересно услышать ваше мнение, актуальны ли данные проблемы с обходом csrf (и как следствие доступа к кукам) и сталкивались ли вы на практике с тем, что несмотря на защитные меры по csrf атакам, они не срабатывали

    Заранее благодарю)


    1. Libiros Автор
      23.05.2023 04:14

      Спасибо, статья занимательная и до сих пор актуальная!
      Что касается защиты, важно помнить о двусторонней безопасности. Да, можно накосячить на фронте, но у нас есть еще и бек. Подвожу к тому, что и рекомендуется в статье - использовать SameSite Cookies, но это обоюдная ответственность :)

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