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

Цель этой статьи — показать вам несколько способов, которыми вы можете стать уязвимыми для XSS при использовании Vue, и, надеюсь, как их предотвратить.

Если в этот момент вы думаете "подождите, а что такое межсайтовый скриптинг?", то нам нужно немного вернуться назад. Если вы уже знакомы с этой темой, вы можете сразу пропустить следующий раздел.

Что такое межсайтовый скриптинг?

Межсайтовый скриптинг (XSS) — это тип уязвимости веб-приложения, который внедряет скрипты на стороне клиента на страницы, просматриваемые другими пользователями.

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

Подробнее о двух типах XSS можно прочитать здесь: Отраженный XSS и Сохраненный XSS.

В чем же уязвимость Vue?

Каждый раз, когда сгенерированный сервером HTML внедряется на веб-сайт, этот веб-сайт может быть уязвим для XSS-атак. В случае Vue это делается через директиву v-html.

Директива v-html

Директива v-html в Vue используется для вывода необработанного HTML в компонент вашего приложения.

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

Тем не менее, один вариант использования, упомянутый в Alligator.io, может быть, если вы работаете с устаревшей системой, в которой есть необработанный HTML, хранящийся в базе данных, и вам нужно отобразить его в своем приложении.

Таким образом, в отличие от использования выражений усов, и хотя v-html может быть полезен (в конце концов, он существует не просто так), он может открыть вас для XSS-атак, поскольку Javascript, отображаемый через v-html, будет выполняться.

Посмотрите краткую демонстрацию здесь той же строки, отображаемой с помощью выражений усов и v-html, и попробуйте щелкнуть ссылку, чтобы увидеть инъекцию в действии.

Динамическое отображение произвольного HTML-кода на вашем веб-сайте может быть очень опасным, поскольку легко может привести к XSS-уязвимостям. Используйте интерполяцию HTML только для надежного контента и никогда для контента, предоставленного пользователями.

Совмещение рендеринга на стороне сервера и на стороне клиента

Другой случай, когда сайты, использующие Vue, могут быть уязвимы для XSS, если они смешивают рендеринг на стороне сервера и на стороне клиента, даже если вы экранируете символы. Также стоит отметить, что эта уязвимость применяется, даже если вы не используете v-html.

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

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

Если мы напишем выражение с битовой математикой в ​​поле ввода, вы увидите, что оно правильно обрабатывается Vue, например, набрав

{{ 2 + 2 }}

это приведет к рендерингу приложения

You have injected: 4

Итак, теперь мы знаем, что инъекцию можно делать.

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

{{ alert(‘xss’) }}

мы получим что-то вроде:

TypeError: alert is not a function
    at Proxy.eval (eval at createFunction (vue.js:10518), <anonymous>:3:114)
    at Vue$3.Vue._render (vue.js:4465)
    at Vue$3.updateComponent (vue.js:2765)
    at Watcher.get (vue.js:3113)
    at new Watcher (vue.js:3102)
    at mountComponent (vue.js:2772)
    at Vue$3.$mount (vue.js:8416)
    at Vue$3.$mount (vue.js:10777)
    at Vue$3.Vue._init (vue.js:4557)
    at new Vue$3 (vue.js:4646)

Это связано с тем, что любые выражения Vue оцениваются в контексте их экземпляра. Поэтому, когда мы набрали alert(‘xss’), он попытался найти метод оповещения в нашем экземпляре Vue, которого, конечно же, не существует.

Чтобы обойти это, пример в репо идет с

{{constructor.constructor("alert('xss')")() }}

Если вы попытаетесь ввести это в поле ввода, вы увидите, что это работает без проблем.

Почему это работает? Цитата из dotboris:

«В javascript все конструкторы — это функции, а все функции — это объекты. Это означает, что у Vue$3 есть конструктор. Этот конструктор является конструктором Function. Написание конструктора.constructor дает нам конструктор Function.

Конструктор Function позволяет динамически определять функцию во время выполнения. Мы передаем ему код нашей функции, и он возвращает функцию, которую мы можем запустить. В этом случае мы получаем Function("alert('xss')")(). Это создает функцию, которая вызывает оповещение (настоящее оповещение в глобальной области видимости), а затем вызывает его».

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

На данный момент Vue не может отличить безопасный шаблон от любого небезопасного ввода, который мог быть отправлен пользователем.

Итак, как мы можем предотвратить это? Использование директивы v-pre всякий раз, когда значения на стороне сервера вводятся в шаблон, работает хорошо, но ее легко пропустить, когда ее нужно вручную добавлять в каждый элемент, который делает это.

Альтернативой, предложенной автором этого примера, является определение глобальной переменной на странице, которая содержит все переменные на стороне сервера, таким образом, $_GET['var'] станет SERVER_VARIABLES.var, что дает разработчику более безопасный способ передачи значения от сервера к клиенту.

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

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

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

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

V-pre и избегание прямой вставки необработанного HTML — это хорошие практики, но, как и в случае с приложением, лучше заранее понять, где в вашем приложении могут быть дыры, и узнать, как их исправить. предотвратить их.