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 { and } 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:
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.
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:
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.
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.
> the problem is that htmlspecialchars() doesn't know to escape {{ }}, and that that then allows user input to get interpreted as Vue template expressions
Arguably, that's not a problem, because '{' and '}' aren't HTML special characters, so the function is working exactly as intended.
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.
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.
As I said, "sacrificing essential Security to purchase a little Convenience."
And I bet you it's not 10x easier at all! Not even 2x. Write a small piece of code illustrating this convenience, and I will show you a similar piece of code that is just as easy to write/maintain and retains the proper separation of concerns. Seriously, I'm happy to be proven wrong on the 10x easier front.
Yes but can that code be portable as a compliment you can embed multiple times in a web page?
Separation of concerns is not the same as separation of technologies. Putting JS, CSS, and HTML into separate files isn't necessarily separation of concerns, you're just separating tightly coupled code in different languages into multiple files.
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.
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:
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.
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 - 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.
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.
I appreciate your view here, and to some extent you have valid points – but you're also way off the mark in several ways that don't really do anybody any favours. Ultimately I think that precision is really required here.
I'll use the React example, because I'm currently elbow-deep in it.
JSX is explicitly not 'HTML in JS' or 'JS in HTML' or anything else like that. It doesn't (or at least, shouldn't) run in the browser. It's only a sugary syntax for making it easier to write JS code that outputs HTML, and it exists because writing imperative code that outputs deeply nested trees is kind of annoying to write. Even better, everything is properly sanitised at the correct level; there's essentially no way to use unescaped data without explicitly trying to do so. That's a pretty great state of affairs.
I'm not going to be a cheerleader for all this stuff, because you're right that the correct tool should be used for the correct job. But frankly if you don't see the benefits of stability, modularity, and even security of using something like React versus patching together your own framework using `getElementsByClassName` then I think you're missing something.
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.
It took me a minute to understand why this is happening, but it comes down to this: everything is escaped properly, but the Vue.js component is defined _in the HTML_. By injecting anything at all inside the component's markup, you're modifying the component's source code rather than modifying inputs to it. Even if you're using a perfectly well-built HTML escape function, the user can then write code that gets parsed and executed because it's part of the component's source template.
Edit: A better approach here would have been to compile the component into a JS file and load it separately from the page itself, then pulling user input from a global variable or data attribute somewhere in the page. The same attack could conceivably be carried out on React if you're building your components inline (albeit sans-JSX) and injecting user input into the raw <script> source.
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.
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.
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)
There's nothing inherently wrong with a user entering <script src="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.
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.
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.
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.
44 comments
[ 3.5 ms ] story [ 101 ms ] threadI 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 { and } 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:
[1]: https://stackoverflow.com/a/40538194/4522571Just 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.
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.
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.
If the result was too verbose, you can always handroll your own JS:
Arguably, that's not a problem, because '{' and '}' aren't HTML special characters, so the function is working exactly as intended.
Evan discusses some of these risks in the docs and I'm guessing this particular exploit can be patched.
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:
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.
And I bet you it's not 10x easier at all! Not even 2x. Write a small piece of code illustrating this convenience, and I will show you a similar piece of code that is just as easy to write/maintain and retains the proper separation of concerns. Seriously, I'm happy to be proven wrong on the 10x easier front.
Separation of concerns is not the same as separation of technologies. Putting JS, CSS, and HTML into separate files isn't necessarily separation of concerns, you're just separating tightly coupled code in different languages into multiple files.
A table you can reorder ?
Separating data from presentation ?
Sharing widgets ?
Applying style conditionally ?
Then combine all that ?
Of course you can do it easily. In a team of 1.
Why, yes, getting rid of React might let you reduce your team size too.
Code talks - bullsh#t walks as Linus said
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.
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...
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 - 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 - 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.
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
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.
I'll use the React example, because I'm currently elbow-deep in it.
JSX is explicitly not 'HTML in JS' or 'JS in HTML' or anything else like that. It doesn't (or at least, shouldn't) run in the browser. It's only a sugary syntax for making it easier to write JS code that outputs HTML, and it exists because writing imperative code that outputs deeply nested trees is kind of annoying to write. Even better, everything is properly sanitised at the correct level; there's essentially no way to use unescaped data without explicitly trying to do so. That's a pretty great state of affairs.
I'm not going to be a cheerleader for all this stuff, because you're right that the correct tool should be used for the correct job. But frankly if you don't see the benefits of stability, modularity, and even security of using something like React versus patching together your own framework using `getElementsByClassName` then I think you're missing something.
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.
The flaw is not immediately obvious, because each renderer is safe in isolation.
So that means either the server has to properly escape data or take additional steps to isolate data and code.
Server knows that it is passing data for Vue templates, not html. So, it can't be considered safe in isolation.
PS: I see that the article is informative. as I was not aware of such exploits. However, the headline is too clickbait-y.
Edit: A better approach here would have been to compile the component into a JS file and load it separately from the page itself, then pulling user input from a global variable or data attribute somewhere in the page. The same attack could conceivably be carried out on React if you're building your components inline (albeit sans-JSX) and injecting user input into the raw <script> source.
Check https://www.slideshare.net/mobile/x00mario/jsmvcomfg-to-ster... or script gadgets research https://github.com/google/security-research-pocs/blob/master... for more complete overview of the issue.
Disclaimer: I'm one of the authors.
> 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
“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)
I'd imagine that represents the majority of Vue apps in production, but I might be wrong.