Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue.js devtools Universal XSS (Chrome extension) #1353

Closed
chromium1337 opened this issue Jan 29, 2021 · 2 comments
Closed

Vue.js devtools Universal XSS (Chrome extension) #1353

chromium1337 opened this issue Jan 29, 2021 · 2 comments

Comments

@chromium1337
Copy link

@chromium1337 chromium1337 commented Jan 29, 2021

This is a security issue. We tried to reach out to security@vuejs.org two weeks ago but didn't get any reply. Please check the original email for attachments.

Vulnerability Description

In devtools-background.js, there is a code injection in the toast function. It can be triggered by postMessage from any tab, which results in universal XSS upon opening the browser's developer tools(F12). An attacker can host a specially crafted web page to exploit this vulnerability, then convince a user to view the web page and open developer tools(F12) in other Chrome tabs.

Technical Details

In the manifest.json of a Chrome extension, there are three types of Javascript files to run: background, content_scripts and devtools_page. The first type is not involved in this vulnerability so we will ignore it.

content_scripts is injected into the web page that meets the URL requirements. One thing to note is that vue-devtools does not explicitly set "all-frames" to true, so content_scripts will only be injected into the topmost frame in the tab.

devtools_page will be loaded when the user opens the browser's developer tools (by hotkey F12 or Ctrl+Shift+I or just right click -> "Inspect").

One of the extension's content_scripts detector.js added an event listener for message event. It checks if the message is sent from current frame and has vueDetected set to true, then it forwards our message to chrome.runtime API.

window.addEventListener('message', e => {
  if (e.source === window && e.data.vueDetected) {
    chrome.runtime.sendMessage(e.data)
  }
})

When user opens the browser's developer tools, the devtool_page devtools-background.html will be loaded. It simply embeds devtools-background.js in a script tag. This script handles messages sent from chrome.runtime.sendMessage.

chrome.runtime.onMessage.addListener(request => {
  if (request === 'vue-panel-load') {
    onPanelLoad()
  } else if (request.vueToast) {
    toast(request.vueToast.message, request.vueToast.type) // vulnerable
  } else if (request.vueContextMenu) {
    onContextMenu(request.vueContextMenu)
  }
})

In the toast function, we can see the params message and type are introduced into src using template string, then src gets executed in the "inspectedWindow".

function toast (message, type = 'normal') {
  const src = `(function() {
    __VUE_DEVTOOLS_TOAST__(\`${message}\`, '${type}');
  })()`

  chrome.devtools.inspectedWindow.eval(src, function (res, err) {
    if (err) {
      console.log(err)
    }
  })
}

Since message and type are not sanitized before they are added to src, we can craft a special message to escape from __VUE_DEVTOOLS_TOAST__ function, and inject arbitrary javascript code to execute.

Please note that message passing using chrome.runtime.onMessage and chrome.runtime.sendMessage is available for any part of one extension. That means it is possible to send specially crafted messages from one tab and run the injected javascript code on the other tabs.

This image describes how the messages are processed in this vulnerability:

image

Additional notes

  1. Evaluating Javascript by chrome.devtools.inspectedWindow.eval is equivalent to evaluating in developer tools' console. It is not restricted by content security policy (CSP). You can test it on this page, where I set the CSP to default-src 'none'.

  2. The beta version of vue-devtools has not implemented __VUE_DEVTOOLS_TOAST__, but toast function remains untouched. That means it is necessary to add the function declaration to make the PoC work for both versions since function declaration in one code block will be evaluated first:
    image

  3. Developer tools window remains open on navigation. An attacker could navigate the tabs to any http(s) URL and execute Javascript on any domain.

Proof of concept

Please follow these steps to reproduce:

  1. Install and enable Vue.js devtools in Chrome
  2. Host poc/poc1.html in a webserver (live version), and visit it in the browser, do not close the tab.
  3. Press F12 in any other tab that has navigated to an http(s) web page, alert(document.domain) will popup.

The other one poc/poc2.html(live version) iterates through some URLs and executes alert(document.domain) on each of them. Videos of both PoCs(live version: PoC1 PoC2) are attached in the poc folder.

@chromium1337
Copy link
Author

@chromium1337 chromium1337 commented Jan 29, 2021

Please note that both Vue.js devtools 5.3.3 and Vue.js devtools 6.0.0 beta3 are affected.

My suggested fix would be using JSON.stringify to escape the message and type:

Index: packages/shell-chrome/src/devtools-background.js
IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    ===================================================================
        diff --git a/packages/shell-chrome/src/devtools-background.js b/packages/shell-chrome/src/devtools-background.js
--- a/packages/shell-chrome/src/devtools-background.js	(revision 6d8fee4d058716fe72825c9ae22cf831ef8f5172)
+++ b/packages/shell-chrome/src/devtools-background.js	(date 1610010620409)
@@ -115,7 +115,7 @@

    function toast (message, type = 'normal') {
      const src = `(function() {
-    __VUE_DEVTOOLS_TOAST__(\`${message}\`, '${type}');
+    __VUE_DEVTOOLS_TOAST__(${JSON.stringify(message)}, ${JSON.stringify(type)})});
   })()`

      chrome.devtools.inspectedWindow.eval(src, function (res, err) {
@Akryum
Copy link
Member

@Akryum Akryum commented Jan 29, 2021

Hi, thank you for your security report! I'm looking into it.

Akryum added a commit that referenced this issue Jan 29, 2021
Akryum added a commit that referenced this issue Jan 29, 2021
@Akryum Akryum closed this in 900f2be Jan 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants