# Безопасность

# Сообщения об уязвимостях

При поступлении сообщения об уязвимости — её исправление становится первоочередной задачей для нас. Сотрудник, работающий на полную ставку, бросает всё и занимается ею. Чтобы сообщить об уязвимости, пожалуйста, отправьте сообщение на электронную почту security@vuejs.org.

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

# Правило №1: Никогда не используйте ненадёжные шаблоны

Главное правило при использовании Vue для безопасности — никогда не использовать ненадёжное содержимое в качестве шаблона компонента. Это аналогично разрешению исполнения JavaScript в приложении — и даже хуже, потому что может стать причиной нарушений в работе сервера, если код выполнится при отрисовке на стороне сервера. Пример такого использования:

Vue.createApp({
  template: `<div>` + userProvidedString + `</div>` // НИКОГДА ТАК НЕ ДЕЛАЙТЕ
}).mount('#app')
1
2
3

Шаблоны Vue компилируются в JavaScript, а выражения внутри них будут выполняться как часть процесса отрисовки. Несмотря на то, что выражения исполняются в определённом контексте рендеринга, из-за комплексности возможных сред выполнения, для такой инфраструктуры как Vue, нецелесообразно полностью защищать от потенциально вредоносного исполнения кода из-за огромных накладных расходов для производительности. Самый простой способ избежать таких проблем — убедиться, что содержимое шаблонов Vue всегда достоверно и полностью контролируется вами.

# Что делает Vue для вашей защиты

# HTML-содержимое

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

<h1>{{ userProvidedString }}</h1>
1

если в userProvidedString будет содержаться:

'<script>alert("hi")</script>'
1

то он будет экранирован в следующий HTML:

&lt;script&gt;alert(&quot;hi&quot;)&lt;/script&gt;
1

Таким образом предотвращая внедрение вредоносного скрипта. Экранирование выполняется с помощью нативного API браузера, такого как textContent, поэтому уязвимость возможна только в случае, если сам браузер уязвим.

# Привязка атрибутов

Аналогичным образом, динамические привязки к атрибутам также автоматически экранируются. Это значит, что для шаблона:

<h1 :title="userProvidedString">
  hello
</h1>
1
2
3

если в userProvidedString будет содержаться:

'" onclick="alert(\'hi\')'
1

то он будет экранирован в следующий HTML:

&quot; onclick=&quot;alert('hi')
1

Тем самым предотвращая преждевременное закрытие атрибута title для добавления нового, произвольного HTML. Экранирование выполняется с помощью нативного API браузера, такого как setAttribute, поэтому уязвимость возможна только в случае, если сам браузер уязвим.

# Потенциальные опасности

В любом веб-приложении возможность выполнения пользовательского содержимого без санитизации в формате HTML, CSS, или JavaScript является потенциально опасным, и этого следует избегать везде где только возможно. Но бывают моменты, когда некоторый риск может быть приемлемым.

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

# Внедрение HTML

Как сказано ранее, Vue автоматически экранирует HTML-содержимое, предотвращая случайное внедрение HTML в приложение. Однако в тех случаях, когда вы уверены в безопасности HTML, можно отображать HTML-содержимое в сыром виде:

  • Используя шаблон:

    <div v-html="userProvidedHtml"></div>
    
    1
  • Используя render-функцию:

    h('div', {
      innerHTML: this.userProvidedHtml
    })
    
    1
    2
    3
  • Используя render-функцию с JSX:

    <div innerHTML={this.userProvidedHtml}></div>
    
    1

Совет

Запомните, что предоставляемый пользователем HTML никогда не может считаться безопасным на 100%, если не находится в изолированном iframe или в той части приложения, где только пользователь, написавший этот HTML, может получить к нему доступ. Кроме того, разрешать пользователям писать свои собственные шаблоны Vue может привести к аналогичным опасностям.

# Внедрение URL

В таком URL-адресе:

<a :href="userProvidedUrl">
  Нажми на меня
</a>
1
2
3

Существует потенциальная проблема безопасности, если URL не был «санитизирован» для предотвращения выполнения JavaScript через javascript:. Есть библиотеки, такие как sanitize-url (opens new window) или DOMPurify (opens new window), которые могут помочь с этим.

Совет

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

# Внедрение стилей

Посмотрим на этот пример:

<a
  :href="sanitizedUrl"
  :style="userProvidedStyles"
>
  Нажми на меня
</a>
1
2
3
4
5
6

Предположим что sanitizedUrl был санитизирован и это действительно настоящий URL, а не JavaScript. Но используя userProvidedStyles, злоумышленники всё еще могут добавить CSS для «click jack», т.е. стилизовать ссылку прозрачным блоком например поверх кнопки «Входа в систему». В таком случае, если https://user-controlled-website.com/ куда ведёт ссылка, визуально повторяет страницу авторизации приложения, появляется возможность перехвата логинов и паролей пользователей.

Аналогично можно представить себе, как разрешение пользователям определять содержимое элемента <style> создаст ещё большую уязвимость, предоставив полный контроль над стилями страницы. Поэтому Vue не стоит отрисовывать теги стилей внутри шаблонов подобным образом:

<style>{{ userProvidedStyles }}</style>
1

Чтобы полностью обезопасить пользователей от техники click jacking, рекомендуем разрешать полный контроль над CSS только внутри изолированного iframe. В качестве альтернативы, если всё-таки необходимо предоставить пользователю возможность настройки стилей, рекомендуем использовать объектный синтаксис и позволять указывать только значения для конкретных свойств, которые изменять безопасно, например так:

<a
  :href="sanitizedUrl"
  :style="{
    color: userProvidedColor,
    background: userProvidedBackground
  }"
>
  Нажми на меня
</a>
1
2
3
4
5
6
7
8
9

# Внедрение JavaScript

Настоятельно не рекомендуем отрисовку элементов <script> с помощью Vue, поскольку шаблоны и render-функции никогда не должны иметь в себе побочных эффектов. Но это не единственный способ для подстановки строк, которые будут расцениваться как JavaScript во время выполнения.

Каждый HTML-элемент может иметь атрибуты, чьи значения принимают строки JavaScript, например onclick, onfocus, и onmouseenter. Привязка JavaScript пользователем к любому из этих атрибутов является потенциальной угрозой безопасности, поэтому подобного следует избегать.

Совет

Запомните, что предоставляемый пользователем JavaScript никогда не может считаться безопасным на 100%, если не находится в изолированном iframe или в той части приложения, где только пользователь, написавший этот JS, может получить к нему доступ.

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

  1. Разработчик явно указывает Vue отрисовать предоставленный пользователем, не-санитизированный контент в шаблонах Vue. По своей природе это небезопасно и Vue не имеет возможности отслеживать это.

  2. Разработчик монтирует Vue на всю HTML-страницу, которая, как оказалось, содержит как содержимое отрисованное на стороне сервера, так и предоставленное пользователем. Фундаментально эта проблема аналогична #1, но иногда разработчики могут сделать так, не осознавая этого. Подобное может привести к возможным уязвимостям, когда атакующий предоставляет HTML, который безопасен как обычный HTML, но небезопасен в качестве шаблона Vue. Лучше всего никогда не монтировать Vue на узлах, которые могут содержать контент предоставленный как сервером так и пользователем.

# Лучшие практики

Главное правило в том, что если разрешаете выполнение не-санитизированного содержимого от пользователя (как например HTML, JavaScript, или даже CSS), то открываетесь для атак. Этот совет остаётся действенным всегда, независимо от того используется ли Vue, другой фреймворк или вообще никакого.

Кроме вышеизложенных рекомендаций из раздела потенциальных опасностей, рекомендуем также ознакомиться со следующими ресурсами:

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

# Координация с бэкэндом

Уязвимости безопасности HTTP, такие как подделка межсайтовых запросов (CSRF/XSRF) или внедрение межсайтовых скриптов (XSSI), в основном нацелены на бэкэнд, поэтому Vue тут мало чем может помочь. Тем не менее, рекомендуем координировать действия с командой разработчиков бэкэнда, чтобы лучше узнать как следует взаимодействовать с API, например отправляя CSRF-токены при отправке форм.

# Отрисовка на стороне сервера (SSR)

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