-
Notifications
You must be signed in to change notification settings - Fork 27.5k
XSS issues with server-rendered Angular templates #5601
Comments
So, there's a problem with this, and I'll tell you what it is, with an example: When it comes to "actually" escaping things into HTML entity references, these are going into text nodes, and by the time the compiler looks at them, they are already going to be "dangerous". What you can do is escape these into something meaningless which the browser won't turn into an expression which will be interpolated, thus preventing execution. I'm sorry if it sounds like I'm dismissing the issue, but it sounds to me like you have the tools at your disposal to prevent unwanted expressions from getting rendered already (at the cost of putting unexpected/hard to read stuff in the view) As you suggest, \{\{ wouldn't be interpolated (by default, unless you tell the interpolate service to deal with this), so that's an option for you --- it just makes stuff look a bit gross. http://jsfiddle.net/q8B4p/1/ |
I definitely agree. Software authors need to be able to control such metadata; given the stream-expressed encoding of HTML, this requires the ability to escape metadata instructions to reduce the privilege level of potentially-harmful content. |
Right, but you can really already do exactly that --- if you simply escape, or even totally remove interpolation expressions or untrusted attributes from your rendered templates, you avoid this issue entirely. I'm not sure what we can really do in angular-land to deal with unescaped templates, we have no idea that you aren't serving exactly what you intended to. |
Thanks for your comments and especially @caitp for for taking the time to write up the fiddles. I agree that we can fix the security aspect of this by simply replacing the interpolation markers with something else. But then we have changed the contents of the view, which can produce other issues. I understand that using HTML entities cannot be used since the browser will expand them before Angular starts interpolating. My idea was that since Angular can't know if an interpolation mark is safe to parse or not, there could be a way to tell Angular if an One way to tell Angular to render a verbatim |
The issue with that is that, if there was another interpolation expression in the text node, then this would work for only one digest, because the following digest it would no longer be escaped (if you want to render it as an unescaped string). Having a way to flag an expression as being ignored might be nice, but currently it doesn't really fit into the way things work, and would likely be a pretty big/complicated change. For now, the best bet is to simply escape your stuff server-side, there really isn't much else to do about it :( |
I don't think it's possible to escape this server-side without changing the content. If a profile (in the example) happens to have "{{" in it, you can't serve that content safely without changing how it looks to the end-user (e.g. inserting a space into the middle). Since {{ could occur in other contexts that are harder to change (e.g. it could be part of an image URL), it's even more complex. I don't know how to fix this, but I believe it's a real and significant issue. |
Yeah, as said, we'll discuss this to see if we can make a change to the interpolate service / html compiler to resolve this. But the issue is always going to come back --- once you escape {{ with {-{, what happens when you need |
Sure, this is a solved problem for all escaping systems. You could make people send {{{ when they want to render {{; you'd just trim one { off of any string of {'s longer than 2. I agree that it's not obvious how to do this without adding a metadata tagging system to the in-memory copy of the page, but I don't know enough about the internals to make a specific implementation suggestion. |
Thanks for reporting this. Normal braces: { |
That's not a bad solution for them, and is probably significantly cheaper than re-arching the $interpolate service to keep track of escaped expressions or whatever. |
Is it true that Some examples:
Unless we can make any DOM-changing Javascript library escape their curly braces, is there a way to remedy this? Would we need to recommend Angular users to never use libraries that insert uncontrolled text nodes into the DOM? |
@tbosch Thanks for the creative suggestion :) We tried something similar, but instead used We abandoned the idea because it broke inlined Javascript. We were also worried it would confuse users who copy text from the page and use it for something else (e.g. this very page). We're currently escaping |
well, it's sort of like this (I believe, and it's possible I'm wrong, due to not having touched the $interpolate service much) If you give us something which the browser's HTML parser decides looks like There are a few parts of this where I could be mistaken, so it may theoretically be possible to make this work well, but I'm not totally sure it's the right thing to do, I think @tbosch's proposal would make a lot more sense. |
On Thursday 02 January 2014, Henning Koch wrote:
What about the
<%= user['bobby.tables'].profile %>
http://docs.angularjs.org/api/ng.directive:ngNonBindable Disclaimer waiver: When you send me an unencrypted email, you implicitly |
@xrg: We experimented with |
On Friday 03 January 2014, Henning Koch wrote:
Why would you need to /allow/ any interpolation within your user-supplied But, in case you do, a more fine-tuned 'non-bindable' might be the solution, Disclaimer waiver: When you send me an unencrypted email, you implicitly |
This issue is serious but the ng-non-bindable directive seems to be the perfect fix. It's certainly not powerful enough to cover your cases but should works for the others. |
I don't think using In Rails (and other frameworks like Django), XSS protection is automatic, i.e.
will be (HTML-)escaped by default, unless you do something explicit like
Escaping works by replacing This is a good thing, since it's so easy to forget manual escaping, but it cannot |
Others templating languages like Twig allow the developer to extend the grammar and the behaviour of the templating engine, you can easily add a new behaviour that wrap untrusted data. But it's maybe not possible for many other templating languages. Moreover, developers should be aware of such a thing to activate the appropriate measures. In my opinion, the best thing to do is to educate developers about this security issue. The famous "don't trust user inputs" applyed to angularjs. |
I agree with @sukei - I feel like this proposal is not appropriate for two reasons.
I'm not sure what the wisdom of enforcing this on the client-side would be, given that this sounds more like a backend problem to me. The result of trying to enforce it on the client-side would be more code, and it would not wipe out the vulnerability if the change was present in This cannot be whitelisted using the One implementation to satisfy this would be to add something to |
Even if you change the start/end interpolation markers, the HTML compiler converts As you say, it comes down to being entirely a server-side issue, but a solution such as suggested by @tbosch should be acceptable. "It broke inlined javascript" well, I'm having a hard time seeing how this is a "problem", since you typically don't want inlined javascript in user data anyways. I don't really think this is something to be fixed on the client-side |
@caitp is right, there is nothing we can do on the client side. The server side templating engines escaping is done assuming that you will output html content. That's why it converts the That's why I was talking about Twig and its extensibility that allows the developers to improve the escape function. I'm not sure that you can achieve such a thing in all other templating engine. Just keep in mind, that the solution should be set in the server and not the client. |
Inlined Javascript can be used for performance reasons to pre-populate an Angular scope without an additional AJAX request, but on second thought: Inlined Javascript is not subject to server-side escaping mechanisms, so there is no problem here. The issue remains that we're changing content by replacing curly braces with something that looks like one, but isn't. I can't copy the changed text and use them for purposes where curly braces are expected (e.g. code snippets in this very page). I can't find those braces with a text search. And I will have a hard time to find out what's wrong, since the letters will look fine, but aren't. |
I feel this thread has moved away from the original proposal. I'm not asking Angular to solve this problem on its own. @sukei correctly said "If you need to escape 'angular entities', it's up to you to write a solution that does the job.". This is what I'm trying to do: Solve it on the backend. But I can't. It is not possible to escape Angular entities without changing the content. The original proposal was to change Here is a rough draft of a change to AngularJS that would add the ability to escape interpolation symbols using The commit linked above still has some minor issues:
... but that can be fixed easily. What do you think? |
Let me sum this up before this thread goes too wild. I'll update this summary as needed with future updates: Summary of the issueIssue 1. mixing server-side and client-side templating could result in XSSexample code:
if this template is evaluated by Rails then
Once Angular processes this template then it will generate DOM that looks most likely like this:
Notice that the Because the expression was evaluated by Angular, the server-side interpolation could force Angular to executed unexpected code via expressions. This would require the user input to be crafted in a way that is meaningful to the Angular app being exploited. SolutionIt is not recommended to mix server-side and client-side templating that uses user-provided input in this way. We discourage developers from doing so because it's hard to audit and fix all the possible corner-cases that could be exploited using the interaction of these two templating systems. This is not a new recommendation, it has been in place since the first Google security review in 2011. If you really really have to though, there are some workarounds: Workaround 1Use
Workaround 2The server could use html escaping on the input ( Issue 2. Inability to escape interpolation markers on the server so that Angular does not interpret them as bindingsWhen Angular see's interpolation markers A naïve way to approach the escaping would be to use html entities to encode the marker characters in the template - just as is being done with html tags. This approach doesn't work, because Angular consumes DOM templates and not template strings. This means that when a string (containing html entities) is sent from the server to the client, the browser will parse it and turn it into DOM tree. Angular then walks the DOM tree and looks for interesting things (like interpolation markers). The problem is that by the time Angular finds the interpolation marker in a textContent attribute value, it has already been converted from html entity into a regular character. Another approach would be for the server to replace Yet another approach would be for the server to replace regular This approach almost works, except if a user tries to search for curlies in the text or tries to copy&paste the text from the view, then these pseudo-curlies could cause a lot of issues that are not obvious to understand and debug by the end user. SolutionSince it's unlikely that we can find a way to escape interpolation markers using html entities or with a similar method, the server and client might need to cooperate to get the behavior just right. One solution would be for the server to escape interpolation markers in a way that Angular understands and treats as escaped markers that should be unescaped but not interpreted as binding. Example:
|
\o/ |
since we need to be explicit about the regular and escaped interpolation markers during configuration, it might be better to just add a second argument to the $interpolateProvider. So one could do:
I'm not opposed to making this the default if we find a consensus on escaped markers that don't break anything out there. (this would be for 1.3 of course) |
PRs welcome! |
Previously, Angular would offer no proper mechanism to reveal attempted script injection attacks when users would add expressions which may be compiled by angular. This CL enables web servers to escape escaped expressions by replacing interpolation start and end markers with escpaed values (which by default are `{{{{` and `}}}}`, respectively). This also allows the application to render the content of the expression without rendering just the result of the expression. Closes angular#5601
Previously, Angular would offer no proper mechanism to reveal attempted script injection attacks when users would add expressions which may be compiled by angular. This CL enables web servers to escape escaped expressions by replacing interpolation start and end markers with escpaed values (which by default are `{{{{` and `}}}}`, respectively). This also allows the application to render the content of the expression without rendering just the result of the expression. Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601
This CL enables interpolation expressions to be escaped, by prefixing each character of their start/end markers with a REVERSE SOLIDUS U+005C, and to render the escaped expression as a regular interpolation expression. Example: `<span ng-init="foo='Hello'">{{foo}}, \\{\\{World!\\}\\}</span>` would be rendered as: `<span ng-init="foo='Hello'">Hello, {{World!}}</span>` This will also work with custom interpolation markers, for example: module. config(function($interpolateProvider) { $interpolateProvider.startSymbol('\\\\'); $interpolateProvider.endSymbol('//'); }). run(function($interpolate) { // Will alert with "hello\\bar//": alert($interpolate('\\\\foo//\\\\\\\\bar\\/\\/')({foo: "hello", bar: "world"})); }); This change effectively only changes the rendering of these escaped markers, because they are not context-aware, and are incapable of preventing nested expressions within those escaped markers from being evaluated. Therefore, backends are encouraged to ensure that when escaping expressions for security reasons, every single instance of a start or end marker have each of its characters prefixed with a backslash (REVERSE SOLIDUS, U+005C) Closes angular#5601 Closes angular#7517
(I commented on the change - #7517 (comment) - comment copied here. I appreciate how tricky escaping is and I want us to get it right! I'd be happy to submit pull requests for docs and/or code, if you approve.) While this change thankfully makes it possible to write text that expands to Alternately, the suggested workarounds in #5601 (comment) could be documented as the official way to escape Angular's curly braces. They appear to me to be a sufficient escaping system that is no more fragile than this commit's way of backslash-escaping Angular's interpolation markers. It's true that |
Them gem patches Rails XSS protection methods with a replacement of `{{` strings with `{{ DOUBLE_LEFT_CURLY_BRACE }} `. `DOUBLE_LEFT_CURLY_BRACE` is defined by Angular to return `{{`, so we actually do execute the interpolation, but only to return the original string. More information: https://github.com/opf/rails-angular-xss angular/angular.js#5601
Them gem patches Rails XSS protection methods with a replacement of `{{` strings with `{{ DOUBLE_LEFT_CURLY_BRACE }} `. `DOUBLE_LEFT_CURLY_BRACE` is defined by Angular to return `{{`, so we actually do execute the interpolation, but only to return the original string. More information: https://github.com/opf/rails-angular-xss angular/angular.js#5601
I am new to this. Please help in below code <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
{{'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)');}}
{{lastname}}
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.firstname = "John";
$scope.lastname = "'a'.constructor.prototype.charAt=[].join;$eval('x=alert(1)');"
});
</script>
Use the ng-bind directive to bind the innerHTML of an element to a property in the data model. here {{lastname}} is not giving any alert. |
Please read https://angularjs.blogspot.com/2016/09/angular-16-expression-sandbox-removal.html for the most recent update. |
Dear Angular community,
we would greatly appreciate any comments on the issue below.
Overview of the issue:
When rendering Angular templates with a server-side templating engine like ERB or Haml it is easy to introduce XSS vulnerabilities. These vulnerabilities are enabled by AngularJS evaluating user-provided strings containing interpolation symbols (default symbols are
{{
and}}
).The standard mitigation strategy by templating engines is to always escape meaningful symbols in strings (unless they are explicitely marked as "safe"). Unfortunately the inability to escape AngularJS interpolation symbols makes it hard to apply this strategy here.
Reproducing the issue:
We are using AngularJS for the frontend and Rails in the backend. While we have written full Single Page Applications with AngularJS, we are often embedding small snippets of AngularJS in server-rendered views. We love that AngularJS lets us replace jQuery spaghetti with simple code like this:
The problem is that the code snippet above enables Cross Site Scripting (XSS). If the server-parsed expression
user.profile
contains AngularJS markup like"Foo {{signOut()}} Bar"
, it will trigger functions on the current scope, allowing an attacker to trigger side effects on another computer.Proposed fix:
The standard mitigation strategy by templating engines is to always escape symbols that have meaning for downsteam parsers, unless they are explicitely marked as "safe". E. g. if the server-parsed expression
user.profile
in the code example above would yield the string"Foo <script>alert('owned')</script> Bar"
, Rails, Haml, etc. would automatically replace the angle brackets with HTML entities.To allow HTML tags to be rendered without escaping, the developer needs to explicitely mark a string as "safe" like so:
(AngularJS actually uses this approach itself, when inserting the results of an evaluated Angular expression into the DOM.)
A good default for people rendering Angular templates using server-side templating engines would be to make templating engines automatically escape Angular interpolation symbols unless an evaluated string is explictely flagged as "safe". A patch for engines like ERB or Haml would be easily created, but unfortunately Angular's
$interpolate
service does not recognize any kind of escape symbol.We propose that the
$interpolate
service should recognize escaped interpolation symbols like this:... or maybe a second pair of start/end symbols.
When encountering escaped interpolation symbols, the service would not evaluate the enclosed expression. Instead it would strip the escape symbols from the string.
(Note that we first tried to replace the curly braces with their HTML entities, which would have the added bonus of being output-neutral outside of an
ng-app
element. However, entitized braces are still recognized and their contents evaluated by Angular. We're not sure if the entities are expanded by Angular or by the browser.)Plea to not dismiss this issue
We understand that this issue is not an actual bug in AngularJS, but (like all XSS issues) is caused by combining multiple technologies that are unaware of each other. Still we believe that AngularJS' ability to co-exist with non-Angular code in the same DOM is one of AngularJS greatest strengths. It sets Angular apart from frameworks like Ember, which need to own the entire page.
By offering a way to escape interpolation symbols (or maybe some other solution we didn't think of), AngularJS could be used safely in combination with upstream templating engines.
What do you think?
The text was updated successfully, but these errors were encountered: