HTML escape disabled in Vue component

ID

javascript.vue_html_escape_disabled

Severity

high

Resource

Injection

Language

JavaScript

Tags

CWE:79, NIST.SP.800-53, OWASP:2021:A3, PCI-DSS:6.5.7, vue

Description

Vue allows direct rendering of HTML using the v-html attribute in templates, or innerHTML in render functions or JSX. This is dangerous unless the passed content is sanitized against cross-site scripting (XSS) injection, or you are 100% sure that the content is safe and does not contain user input that could be passed to enable execution of JavaScript code.

Additional constructs that are potentially vulnerable to XSS injection attacks are:

  • The v-bind:href directive, which may be exploited with javascript: URLs,

  • The v-bind:style directive.

  • Event directives such as v-on:onclick.

And, in general, rendering part of content in <script> or <style> elements in Vue templates and render functions.

Rationale

Vue framework use native browser APIs, like textContent, to render HTML content in the browser, so any JavaScript code is escaped and not executed in the browser’s JavaScript engine.

That means that in templates, the text between curly brackets like this: <h1>{{ userData }}</h2> is 100% safe, as userData HTML metacharacters are escaped. Attributes of HTML elements such as <h1 v-bind:title="userProvidedInput">…​</h1> are also escaped.

But developers may skip the automatic escaping by using the constructs mentioned before.

<!-- VULNERABLE -->
<div v-html="userProvided"></div>

<!-- VULNERABLE, JSX syntax in render functions -->
<div domPropsInnerHTML={this.userProvided}></div>

<!-- VULNERABLE, attacker may inject javascript: URLs
(and navigation to attacker-controlled sites is dangerous) -->
<a v-bind:href="userProvidedUrl"></a>

<script>
  // call in a render function
  h('div', {
    domProps: {
      innerHTML: this.userProvided // VULNERABLE
    }
  })
</script>

Remediation

If possible, do not render externally-controlled untrusted input using Vue non-escaping constructs. By default, HTML templates and render functions in Vue automatically escape content in HTML elements and attributes. Use {{ …​ }} interpolated expressions instead.

In particular, avoid untrusted content into v-bind:href and :onEVENT template directives. When rendering text using rendering functions, you should use the innerText DOM attribute, instead of innerHtml.

If using an external Vue component library, check how the data passed through input properties is rendered: libraries using v-html or even document.write() with input properties are unsafe.

<!-- SAFE -->
<div>{{ userProvided }}</div>

<!-- SAFE (JSX syntax inside JavaScript render functions) -->
<div>{this.userProvided}</div>

<!-- SAFE if user may chose between a set of allowed fixed URLs -->
<a href="whiteListedUrl" v-if="cond_1">...</a>
<a href="whiteListedUrl2" v-else-if="cond_2">...</a>
...
<a href="whiteListedUrlN" v-else>...</a>

<script>
  // call in a render function
  h('div', {
    domProps: {
      innerText: this.userProvided // SAFE
    }
    // Also SAFE when written as child node
    this.userProvided
  })
</script>

Configuration

This detector does not need any configuration.

References