
Mixing Vue.js templates with server-side templates can lead to XSS - dotboris
https://github.com/dotboris/vuejs-serverside-template-xss
======
Scryptonite
Makes sense because htmlspecialchars() doesn't protect against malicious Vue
template expressions, it only converts characters that are used to represent
html tags, entities or attributes (<>"'&) IIRC.

I think another solution (besides v-pre) to "fixing" it (though you might say
that relying on htmlspecialchars() to protect against user-supplied {{vue
expressions}} was unwise to begin with) is to replace { and } with &#123; and
&#125; after using htmlspecialchars/htmlentities.

EDIT: Another solution would be to pass a different set of delimiters to Vue
that uses characters that would be escaped by htmlspecialchars, like
demonstrated in [1] or like so:

    
    
        Vue.options.delimiters = ['<%', '%>'];
    

[1]:
[https://stackoverflow.com/a/40538194/4522571](https://stackoverflow.com/a/40538194/4522571)

~~~
bastawhiz
In truth, you should just avoid metaprogramming for this purpose. All it takes
is somebody to come along after you and say "why the hell did they change the
delimiters" and switch them all back to the default.

Just store user input somewhere safe and inject it at runtime rather than
trying to fiddle with the Vue template. If you strictly isolate the executable
code and user input, this is never a problem.

------
rk06
Let me get this straight.

The issue here is " Server side code is passing user inputted data --without
sanitizing it-- for further use, which in this case is front end".

It looks like standard XSS flaw.

~~~
pornel
No, it's "server-side does proper sanitization for server-side rendering" and
"client does proper sanitization for client-side rendering", but when both are
combined, they allow XSS, because server-side-rendered page is re-interpreted
as a client-side template.

The flaw is not immediately obvious, because each renderer is safe in
isolation.

~~~
zaarn
I would interpret that as a server-side render being improperly sanitized
since obviously data from the server is being wrongfully executed.

So that means either the server has to properly escape data or take additional
steps to isolate data and code.

------
simonw
I think the fixed code here is vulnerable to a subtle XSS.

    
    
        <?php
        $serverVars = [
          'injectMe' => (string)$_GET['injectme']
        ];
        ?>
        <script>
        window.SERVER_VARS = <?= json_encode($serverVars) ?>
    

The problem here is that the injectme parameter could include a closing
</script> tag to escape that script block. Something like this:

    
    
        ?injectme=</script><script>alert('xss')</script>
    

The fix for this exploit is to endure any < or > characters in the JSON are
escaped using JSON character encoding. I think this is the correct recipe for
that in PHP:

    
    
        json_encode($a, JSON_HEX_TAG)

~~~
megous
json_encode will escape / with \ by default, so this shouldn't be an issue.

------
koto1sa
That's a well researched problem, and is common in most JavaScript frameworks.
In practice it makes it harder to protect applications using them against XSS.

Check [https://www.slideshare.net/mobile/x00mario/jsmvcomfg-to-
ster...](https://www.slideshare.net/mobile/x00mario/jsmvcomfg-to-sternly-look-
at-javascript-mvc-and-templating-frameworks) or script gadgets research
[https://github.com/google/security-research-
pocs/blob/master...](https://github.com/google/security-research-
pocs/blob/master/script-gadgets/README.md) for more complete overview of the
issue.

Disclaimer: I'm one of the authors.

~~~
ghusbands
I made the effort of reading those links to save others the effort. Neither of
them are relevant to the vulnerability in the article.

------
fictionfuture
Rendering user input via a Vue template is gonna set off some mental alarms
with most experienced Vue devs. (Rendering user input directly is something
most devs are careful with)

Evan discusses some of these risks in the docs and I'm guessing this
particular exploit can be patched.

------
tribby
this is a rather misleading title when it's talking about something so general
(unsanitized user input) about which the author states:

> this vulnerability is not specific to ... Vue.js

I don't think anyone will read the title and think this isn't referring to a
vue-specific problem

~~~
ghusbands
Vue is where it was discovered, and it affects only a few other templating
engines. Specifically, it affects those that will search for template
invocations in arbitrary parts of the page and affects any server-side app
that insert user-supplied input into templates without escaping such template
invocations.

------
billyhoffman
I see a lot of comments here like “this is just standard XSS” and I think that
misses the bigger point (even if the author failed to properly articulate it).
The real problem here is making assumptions that then are broken.

“Reflecting user input back to the user in a basic server side app? Just
output encode it, now no XSS/html injection!”

That makes assumptions about how the outputed user input will be used and
appear in the markup. As soon as the reality changes (that output is now input
into a client-side interpretation environment) the code has a vuln. This is
important lesson to learn and it doesn’t matter what JavaScript framework or
backend framework is being used: when you change assumptions you need to
review any security decisions that were made on those assumptions.

(This is also a solid example why output encoding alone isn’t sufficient to
protect against XSS, Because output encoding makes assumptions about how the
output is being used. Input validation is key and should be the primary way to
protect against most injection attacks, followed by various types of output
encoding or parameterization)

~~~
ghusbands
There's nothing inherently wrong with a user entering <script
src="[http://evil.example.com/">](http://evil.example.com/">) </script> in
most any field, so you're not talking input validation but instead input
restriction. I'd rather have my outputs properly escaped that have arbitrary
restrictions on my inputs.

------
vertex-four
The solution is essentially an implementation of SQL query parameters in a
different context, for anyone who hasn't noticed that.

------
rgbrenner
Your html sanitizer should escape ' and "... both characters you're using
unescaped.

So does this work with a proper sanitizer? Doesn't look like it.

If you have a broken sanitizer, or worse youre inserting it without sanitizing
it, you're already vulnerable to XSS... this is just a lot of writing showing
an exploit caused by a broken sanitizer.

~~~
jakewins
This is incorrect; the problem is that htmlspecialchars() doesn't know to
escape {{ }}, and that that then allows user input to get interpreted as Vue
template expressions.

If you have a sanitizer that cleans up ' and ", that'd only serve to limit the
vulnerability to executing javascript without quotations in it. That's nowhere
near a comfortable place to be - user data should not end up interpreted as
Javascript, quotations or not.

~~~
Scryptonite
Yup. Obfusticating using a tool like
[http://www.jsfuck.com](http://www.jsfuck.com) would get around the lack of
quotations.

If the result was too verbose, you can always handroll your own JS:

    
    
        {{constructor.constructor(constructor.name.constructor.fromCharCode(97,108,101,114,116,40,39,120,115,115,39,41))()}}

------
zephod
Let's be clear - this affects client-side apps which include the template
compiler (ie. the full vue.js bundle). Right? So apps built with Webpack, and
using vue.runtime.min.js should be safe?

I'd imagine that represents the majority of Vue apps in production, but I
might be wrong.

------
EGreg
Look, I am gonna be blunt.

 _A great man once said: "Those who sacrifice essential Security to purchase a
little temporary Convenience deserve neither" :)_

I have seen Javascript frameworks and fads come and go. I remember 10 years
ago "progressive enhancement" was the greatest thing, and onclick inside your
HTML was considered hideous. Then came Angular with its ng-click and suddenly
placing behavior stuff inside declarative HTML was cool again. Then came React
and placing HTML -- er, excuse me, JSX inside your Javascript files was ...
shocking but then cool. Then came Vue...

Look, it isn't that difficult:

    
    
      HTML is for structure
      CSS is for presentation
      JS is for dynamic behavior
    

Want animations? Use classes and CSS.

Want complex behavior? Use classes, find your elements with
getElementsByClassName and do your thing.

Want extra info? Stuff it into a JSON-encoded data-{something} attribute.

There is no reason to let your declarative HTML contain code-like things. Have
it contain markers using HTML-compatible things like CSS classes and data-
attributes (we used to call this "microformats").

Then on the server...

When you render your HTML tags you escape your attributes and text content.

When you output Javascript ... you DON'T. Instead you json_encode stuff that a
.js file should consume. Oh, and use SRIs when loading the js from CDNs.

Before you execute SQL, you escape the values.

======================

 _In short - you escape for the medium you are interacting with._

======================

Seriously, it's straightforward and you can't mess it up... unless you start
using "syntax sugar" that makes you insert things into a language that it
wasn't designed for, after reading a framework touting "LOOK THE GREATEST
SHINY NEW NONSTANDARD TECHNIQUE".

It's really not adding all that much. I remember when two-way binding was hot
and touted by Angular 1. It's not anymore. I remember when CoffeeScript was
the next greatest thing. It's not so much anymore.

Meanwhile, if you stick to the ES5/6 standards and let each language of the
Web do what it's supposed to, you have a great future ahead of you. And much
less injections to worry about.

 _Update - to the downvoters: go ahead, type words! I am interested to have an
actual discussion after over 15 years of seeing SO MANY SECURITY HOLES from
this one thing: frameworks interpolating stuff into a language that was
designed for something else._

~~~
spion
You're getting downvoted because you're equating Angular 1, React and Vue.

React has one huge difference: its "template language" is (almost) never
compiled at run time. Its not HTML at all. This makes it difficult to coerce
it to interpret any random string as an expression. That annoying "build step"
that everyone hates, that is the convenience for which people are sacrificing
security.

When you have no build step, your template compiler must accept expression
strings at run-time, and it will inevitably process user input at some point
unless you're very careful all the time. "Escaping" things is a stop-gap that
doesn't work.

You could just use fully static HTML/JS endpoints (react based) combined with
fully JSON based RPC endpoints and it will be safe. Then you can combine that
into server-side rendering, and it will stay safe (unless renderToString has a
problem). No escaping required if you are not sending code strings to a
compiler at runtime.

~~~
EGreg
I get your point about precompilers. LESS, SASS, JSX and so on. But that's
only _one_ thing, my main point was "don't put executable code into your
HTML".

I would say, btw, that a Template Engine is what you need, such as Handlebars.
They compile stuff too. What is wrong with escaping? That _is_ what you do
when you dynamically interpolate stuff.

Of course interpolating stuff where it doesn't belong can lead to this:

[https://medium.com/dailyjs/exploiting-script-injection-
flaws...](https://medium.com/dailyjs/exploiting-script-injection-flaws-in-
reactjs-883fb1fe36c1)

~~~
spion
You need escaping only when you're sending strings to a compiler (which is
what you do when you do server-side rendering), and its a never ending source
of bugs. You basically need to re-implement the entire logic of a full blown
parser for the target language to guarantee successful escaping.

E.g. see `mysql_real_for_real_this_time_escape_string` versus just supporting
parameterized queries. Parameters don't need to be escaped; they will never be
interpreted as SQL code.

This list is also fun:
[https://gist.github.com/JohannesHoppe/5612274](https://gist.github.com/JohannesHoppe/5612274)
\- will your framework escape implementation cover all of them? Are you sure?

If you serve a static html (no templates) + css + JS (pre-compiled JSX
templates) client with all dynamic content being served as e.g. JSON, you're
getting rid of a large number of attack vectors from the start.

You are down to a handful of attributes, of which only dangerouslySetInnerHTML
is a React-originating attack vector because similarly to all other template
languages it sends a string to a compiler. It should probably be a symbol,
too, especially now that we have spread syntax for props.
[https://github.com/facebook/react/issues/10506](https://github.com/facebook/react/issues/10506)
\- until then, you can simply forbid spread syntax for props and/or
dangerouslySetInnerHTML.

Any other HTML attributes that have a string-to-compiler behaviour remain a
problem, regardless of whether you are using jquery or react or whatever.
(e.g. href). Its sad but for some of them the only recourse we have is
escaping. Still, thats a huge improvement over escaping everything.

~~~
EGreg
Exactly. You make a very good point. Passing PARAMETERS to a string based
query or command should be done by reference whenever possible. The CALLER
shouldn't be responsible for the escaping and interpolation. That is for the
DRIVER or API or whatever.

That just further proves my point. When you call SQL commands from PHP you
should use PDO and parametrized queries always. Values should always be passed
by reference.

Same goes when you are outputting some HTML. You should have a hash of

    
    
      {
         attributeName: value
      }
    

and it should be up to the HTML RENDERER - and not you - to escape the values
every time.

You've made another great point, which layers on top of my point, which is:

1) Use each language Y as it was intended

2) Escape everything automatically by passing values by reference to an API
layer in language X

So to sum up:

Use HTML for structure, CSS for appearance, and JS for all dynamic behaviors.
Keep your files separate! Do not allow embedding arbitrary CSS and JS in your
HTML and so on.

For MySQL use a MySQL driver API and pass values to it

For HTML use an HTML renderer API and pass values to it

For Javascript-accessible data embedded in HTML use a JSON function (easy) and
pass values to it.

For JSONP (old school) use JSON wrapped with some callback. Better: load
actual JSON using XHR and CORS. Best: use iframes and postMessage to execute
arbitrary methods in the other domain via an authorized session.

And so on.

------
zelon88
Ultimately if your input is never expected to pass a semicolon, quotation,
bracket, parentheses, or other special char I explicitly remove them from the
user input. I also believe that using POST inputs is less of a
temptation/invitation for injection. Sure anyone can send a crafted CuRL
request but it's a lot less inviting than just plugging your injection into
the address bar.

~~~
plopz
This doesn't really help when serving content into different formats, html,
json, xml, etc. Because you have different rules for what are special chars
depending on the format. Maybe one format doesn't work with single quotes
while another does.

