Home / Blog / Vue.js and HTML Injection Explained
Vue.js and HTML Injection Explained

Vue.js and HTML Injection Explained

Daniel Kelly
Daniel Kelly
July 22nd 2024

Do you use v-html in your code? If so, you might be at risk of attack via HTML injection. In this article, let us show you:

  1. exactly HOW v-html puts you at risk
  2. provide examples of when NOT to use v-html
  3. provide ALTERNATIVES to v-html
  4. close with examples of when it’s OK to use v-html

How Does v-html Put You at Risk of HTML Injection

As you probably know, v-html allows you to provide a reactive string with html included which Vue will then add to the DOM as real DOM nodes (as opposed to text).

<script setup>
const html = ref('<strong>Hello</strong> world');
</script>

<template>
  <p v-html="html"></p>
</template>

This differs from the mustache syntax which escapes all HTML before inserting into the DOM.

<script setup>
const html = ref('<strong>Hello</strong> world');
</script>

<template>
  <p>{{ html }}</p>
</template>
mustache syntax

Since the HTML can include any valid HTML tags, that means it has pretty much full control to do anything to your site.

Attackers Can Deface Your Site with HTML Injection

For example, they could inject a style tag to turn your page background a random color.

const html = ref(`<style>body{ background: green !important }</style>`);

Or attack your page with a wall of tiled cat images or any other image they fancy (maybe a competitors logo!).

const html = ref(`<style>body{ background: url('http://whatev.com/cat') !important }</style>`);
attackers

Attackers Could Inject Malicious Scripts (Cross Site Scripting or XSS)

Defacing probably isn’t good for your brand but it can get worse. Bad actors could also inject malicious scripts that do all sorts of things like:

  1. redirect visitors to a look-a-like site that’s out to steal there login credentials (after all they initially visited the legit site, why shouldn’t they trust that login form?)
  2. inject a script that monitors the visitors keystrokes on the site or reads their data on protected pages and sends async requests in the background directly into the bad actors greedy hands

This is known as Cross Site Scripting or XSS for short.

How is this possible with v-html? Well, thankfully you can’t directly add a script tag into your reactive data as a string. For one, with Vite it breaks the SFC compilation but if memory serves, Vue does sanitize blatant script tags from reactive data strings. But script tags aren’t the only method of executing JavaScript.

Bad actors could use inline event listeners. Like this:

<script setup>
import { ref } from 'vue';
const html = ref(
  `<img style="display:none;" 
        src="./a-non-existant-image.jpg" 
        onerror="alert('script kitties attack!')"
  >`
);
</script>

<template>
  <p v-html="html"></p>
</template>

Examples of When NOT to use v-html

So now you know 2 ways attackers could do some real damage with a poorly placed v-html. Let’s look at a couple of examples where you might be tempted to use v-html when you really shouldn’t.

Stored HTML Comments

Pretend you’re building the next greatest social media site (TwiX?) and you want to give users the power to comment with rich text. You know how to do that right? Find a nice WYSIWYG editor and give the users the power to write good ol’ HTML which you’ll then save to your database, and display for their myriad of followers…. Easy, right? wrong!

Why not?

Because the astute hacker will do exactly the dangerous things we saw above. Even if your WYSIWYG editor only supports certain tags, that’s just a client side UX concern. A malicious user can easily use devtools to send their own HTML to your server side endpoint for saving the comment.

Once the nasty comment is saved to the DB, it’s later displayed to another user of the site with v-html and the dirty deed is done.

So the attack looks like this:

  1. You accept comments in HTML format from User A
  2. That HTML gets saved to the database
  3. User B visits the site and sees User A’s comment
  4. Since you display all comments with v-html User A’s malicious code is executed in User B’s browser

HTML from URL Query Parameters

Now let’s pretend you’re building a landing page where you want some messaging in the hero section to differ depending on where it’s coming from. Maybe ads from Facebook show the headline “We saw you checking us out on Facebook….” and ads from YouTube say “Like watching videos? We’ve got you covered”.

To provide the most flexibility to your marketing team, you make this customizable via a url query parameter like this:

<!-- mysite.com?heroCopy=<h1>The+dynamic+text+here</h1><h2>And+a+subheadline</h2>-->

<div v-html="$route.query.heroCopy"></div>

Now we’ve got the same problem! All a malicious user has to do is get a user to click on a link where the heroCopy query param is of their own devious choosing.

Alternative Approaches to v-html

Clearly there are some ways we should NOT use v-html but the use cases are legit. It WOULD be nice to allow users the ability to format their comments with rich text. It would be nice, to give marketing the flexibility to change out that hero copy per ad easily via the URL.

So what are some alternative approaches to these common use cases where you’re tempted to reach for v-html?

Dynamic Content with Rich Formatting

  1. Collect and save user content (comments in our example) in markdown instead of HTML (there are WYSIWYG editors that produce markdown too for a similar UX)
  2. Collect and save user content via other non-standard formats (for example TipTap editor can export as HTML but also as JSON with a custom structure that describes the rich text content)
  3. Collect the user content as HTML but sanitize it on the server side BEFORE saving it to the DB and displaying to other users. If you have a node server libraries like sanitize-html and dompurify.

You Might Not Need Dynamic HTML at All

  1. Consider if dynamic HTML is needed at all. For example, the second example could have easily been made safe by defining 2 separate query variables (headline and subheadline) as strings

When Should I use v-html?

Finally, let’s answer the question: When should I use v-html?

Obviously since v-html exists in Vue there are some legit use cases. What are they? Here’s a sampling of when it wouldn’t be a risk.

  1. Whenever the dynamic HTML content is provided ONLY by 100% trusted users. For example: a visual site builder where the site owner is providing the dynamic HTML (they aren’t going to sabotage themselves)
  2. When you’ve sanitized the HTML on the server beforehand
  3. When the user provided content was NOT collected as HTML (it was collected as markdown or some special JSON and you control how it goes from that format to an HTML string to pass to v-html)

Conclusion

Usually use cases for v-html include the ability for users to provide formatted content. Think twice though before slapping v-html on your site and calling it a day. Instead consider the consequences like site defamation and or stolen data from XSS. Then spend the bit of extra effort to reach for an alternative solution like markdown.

If you’d like to dive deeper into this topic checkout our FREE video lesson "Using v-html with User Provided Data”. It’s a part of our FREE course Common Vue.js Mistakes and How to Avoid Them.

Start learning Vue.js for free

Daniel Kelly
Daniel Kelly
Daniel is the lead instructor at Vue School and enjoys helping other developers reach their full potential. He has 10+ years of developer experience using technologies including Vue.js, Nuxt.js, and Laravel.

Comments

Latest Vue School Articles

7 Beautiful Next-Level Button Components with Vue, VueUse, and TailwindCSS

7 Beautiful Next-Level Button Components with Vue, VueUse, and TailwindCSS

Combine Vue, VueUse, and TailwindCSS for some practical and beautiful buttons
Daniel Kelly
Daniel Kelly
Unlocking the Power of AI in Your Vue Coding Workflow

Unlocking the Power of AI in Your Vue Coding Workflow

Explore how AI is transforming software development, acting as a valuable coding companion to streamline workflows and overcome challenges. This article delves into practical strategies for effectively integrating AI tools, offering insights for developers at any skill level.
David Robertson
David Robertson

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 200.000 users have already joined us. You are welcome too!

Follow us on Social

© All rights reserved. Made with ❤️ by BitterBrains, Inc.