17

I have an application that consists of a server-side REST API written in PHP, and some client-side Javascript that consumes this API and uses the JSON it produces to render a page. So, a pretty typical setup.

The data provided by the REST API is "untrusted", in the sense that it is fetching user-provided content from a database. So, for example, it might fetch something like:

{
    "message": "<script>alert("Gotcha!")</script>"
}

Obviously, if my client-side code were to render this directly into the page's DOM, I've created an XSS vulnerability. So, this content needs to be HTML-escaped first.

The question is, when outputting untrusted content, should I escape the content on the server side, or the client side? I.e., should my API return the raw content, and then make it the client Javascript code's responsibility to escape the special characters, or should my API return "safe" content:

{
    "message": "&lt;script&gt;alert(&#039;Gotcha!&#039;);&lt;\/script&gt;"
}

that has been already escaped?

On one hand, it seems to be that the client should not have to worry about unsafe data from my server. On the other hand, one could argue that output should always be escaped at the last minute possible, when we know exactly how the data is to be consumed.

Which approach is correct?

Note: There are plenty of questions about handling input and yes, I am aware that client-side code can always be manipulated. This question is about outputting data from my server which may not be trustable.

Update: I looked into what other people are doing, and it does seem that some REST APIs tend to send "unsafe" JSON. Gitter's API actually sends both, which is an interesting idea:

[
    {
        "id":"560ab5d0081f3a9c044d709e",
        "text":"testing the API: <script>alert('hey')</script>",
        "html":"testing the API: &lt;script&gt;alert(&#39;hey&#39;)&lt;/script&gt;",
        "sent":"2015-09-29T16:01:19.999Z",
        "fromUser":{
            ...
        },"unread":false,
        "readBy":0,
        "urls":[],
        "mentions":[],
        "issues":[],
        "meta":[],
        "v":1
    }
]

Notice that they send the raw content in the text key, and then the HTML-escaped version in the html key. Not a bad idea, IMO.

I have accepted an answer, but I don't believe this is a cut-and-dry problem. I would like to encourage further discussion on this topic.

alexw
  • 8,468
  • 6
  • 54
  • 86
  • Do it on the server and on the client. – Leo Farmer Sep 28 '15 at 20:05
  • @LeoFarmer that is one possibility, but then I have to worry about double-escaping issues. – alexw Sep 28 '15 at 20:24
  • @DeblatonJean-Philippe absolutely not. There are [many reasons](http://lukeplant.me.uk/blog/posts/why-escape-on-input-is-a-bad-idea/) why escaping *before* you store in the database is a bad idea. – alexw Sep 29 '15 at 18:03

4 Answers4

20

Escape on the client side only.

The reason to escape on the client side is security: the server's output is the client's input, and so the client should not trust it. If you assume that the input is already escaped, then you potentially open yourself to client attacks via, for example, a malicious reverse-proxy. This is not so different from why you should always validate input on the server side, even if you also include client-side validation.

The reason not to escape on the server side is separation of concerns: the server should not assume that the client intends to render the data as HTML. The server's output should be as media-neutral as possible (given the constraints of JSON and the data structure, of course), so that the client can most easily transform it into whatever format is needed.

The Spooniest
  • 2,863
  • 14
  • 14
  • It does make sense that "the server's output is the client's input, and so the client should not trust it". – alexw Sep 29 '15 at 18:11
  • I had learned this lesson a long time ago, however I was looking for the validation today. I also believe that the server should not be concerned with the escaping as data provided by server may be used by different clients, some clients may not be web browsers where escaped data would not work. – 11thdimension Jul 05 '17 at 18:13
3

For escaping on output:

I suggest reading this XSS Filter Evasion Cheat Sheet.

To prevent user correctly you better not only escape, but also before escaping filter it with an appropriate anti XSS library. Like htmLawed, or HTML Purifier, or any from this thread.

IMHO sanitizing should be done on user inputed data whenever you are going to show it back in web project.

should I escape the content on the server side, or the client side? I.e., should my API return the raw content, and then make it the client Javascript code's responsibility to escape the special characters, or should my API return "safe" content:

It's better to return already escaped, and xss purified content, so:

  1. Take raw data and purify if from xss on server
  2. Escape it
  3. Return to JavaScript

And also, you should notice one important thing, like a load of your site and read/write balance: for example if your client enters data once and you are going to show this data to 1M users, what do you prefer: run protection logic once before write (protect on input) on a million time each read(protect on output)?

If you are going to show like 1K posts on a page and escape each on client, how well will it work on the client's mobile phone? This last one will help you to chose where to protect data on client or on server.

theUtherSide
  • 3,338
  • 4
  • 36
  • 35
Alexander Arutinyants
  • 1,619
  • 2
  • 23
  • 49
  • Please note that I'm talking about *outputting* content, not processing input. – alexw Sep 28 '15 at 20:07
  • Ok, but IMHO escaping should be absolutely done before save. – Alexander Arutinyants Sep 28 '15 at 20:09
  • 4
    Absolutely not! That is an [anti-pattern](http://security.stackexchange.com/a/42521/74909). See [this](http://lukeplant.me.uk/blog/posts/why-escape-on-input-is-a-bad-idea/) blog post as well. – alexw Sep 28 '15 at 20:11
  • They say **A common anti-pattern is to HTML-escape all your input.** And they say do validations, tha't what is the tools I mentioned for too. But I prefer to remove all attacking parts explicitly! – Alexander Arutinyants Sep 28 '15 at 20:18
  • 1
    fair enough. but even with proper input validation, output still needs to be escaped. – alexw Sep 28 '15 at 20:20
  • In our project we were writing a markdown powered editor next way: 1. We were escaping everything in `code` part, then sanitizing only vurnalable code by using [OWASP HTML Sanitizer](https://www.owasp.org/index.php/OWASP_Java_HTML_Sanitizer_Project), And that's it. – Alexander Arutinyants Sep 28 '15 at 20:21
  • But you are probably right about not to escape on input, but sanitize on input is preffered anyway. – Alexander Arutinyants Sep 28 '15 at 20:25
  • Ok, please update or remove your answer. It may be confusing to others who might assume this is a question about input. – alexw Sep 28 '15 at 20:29
  • The HTML Sanitizer you're referencing should only be used if the site is allowing users to author html content and have it be rendered somewhere on the site. In every other case, input should be treated as just regular text, not html. This answer is irrelevant to the question asked by OP – Joe Phillips May 11 '18 at 15:40
  • Well I'm not sure that "input should be treated as just regular text" in every other case, because it depends on a lot of factors. The answer describes the particular opinion and underscores importance of the prevention of the problem during the input. And the second part of the answer, @joePhillips, makes reasonable argumentation on how to decide wither to do escaping on server or on client side. The rest about the answer was discussed in the comments. – Alexander Arutinyants May 12 '18 at 20:26
1

This answer is more focused on arguing whether to do client-side escaping vs server-side, since OP seems aware of the argument against escaping on input vs output.

Why not escape client-side?

I would argue that escaping at the javascript level is not a good idea. Just an issue off the top of my head would be if there was an error in the sanitizing script, it would not run, and then the dangerous script would be allowed to run. So you have introduced a vector where an attacker can try to craft input to break the JS sanitizer, so that their plain script is allowed to run. I also do not know of any built-in AntiXSS libraries that run in JS. I am sure someone has made one, or could make one, but there are established server-side examples that are a little more trust-worthy. It is also worth mentioning that writing a sanitizer in JS that works for all browsers is not a trivial task.

OK, what if you escape on both?

Escaping server-side and client-side is just kind of confusing to me, and shouldn't provide any additional security. You mentioned the difficulties with double-escaping, and I have experienced that pain before.

Why is server-side good enough?

Escaping server-side should be sufficient. Your point about doing it as late as possible makes some sense, but I think the drawbacks of escaping client-side are outweighed by whatever tiny benefit you may get by doing it. Where is the threat? If an attacker exists between your site and the client, then the client is already compromised since they can just send a blank html file with their script if they want. You need to do your best to send something safe, not just send the tools to deal with your dangerous data.

Community
  • 1
  • 1
Gray
  • 7,050
  • 2
  • 29
  • 52
  • Thanks. You make some very good points about the reliability of client-side code. I'm planning to render the data client-side with HandlebarsJS, which I would expect to either: 1) succeed 100% and render the escaped data; or 2) fail for some reason and not render anything at all. It does seem strange that my own server should be labeled unsafe, but I have the feeling that @Spooniest is the "correct" answer. I wish I could accept both of these answers as correct. – alexw Sep 29 '15 at 15:57
  • @alexw Thanks for your feedback. The other answerer speaks with a lot of confidence about handling JSON that way, but it is not a recommendation I have seen in many places to be honest. I would discourage you (in general) against accepting answers so quickly. Even if it wasn't my answer, the hope is that someone would come across and provide an even better answer, but as it stands there is not much reason for people to view and contribute to this question. – Gray Sep 29 '15 at 17:11
  • @alexw of course, but there is not incentive for people to view the question since they will consider your problem solved. If that answer solved your problem, then it was the right choice. I am not salty - I was just curious to see responses/votes on these questions for my own learning purposes. Hopefully people still drop by and vote/edit/answer/comment/etc . – Gray Sep 29 '15 at 17:19
  • 1
    See my update. I agree that this is still a topic worthy of continued discussion. – alexw Sep 29 '15 at 17:25
1

TLDR; If your API is to convey formatting information, it should output HTML encoded strings. Caveat: Any consumer will need to trust your API not to output malicious code. A Content Security Policy can help with this too.

If your API is to output only plain text, then HTML encode on the client-side (as < in the plain text also means < in any output).

Not too long, not done reading:

If you own both the API and the web application, either way is acceptable. As long as you are not outputting JSON to HTML pages without hex entity encoding like this:

<%
payload = "[{ foo: '" + foo + "'}]"
%>
    <script><%= payload %></script>

then it doesn't matter whether the code on your server changes & to &amp; or the code in the browser changes & to &amp;.

Let's take the example from your question:

[
    {
        "id":"560ab5d0081f3a9c044d709e",
        "text":"testing the API: <script>alert('hey')</script>",
        "html":"testing the API: &lt;script&gt;alert(&#39;hey&#39;)&lt;/script&gt;",
        "sent":"2015-09-29T16:01:19.999Z",

If the above is returned from api.example.com and you call it from www.example.com, as you control both sides you can decide whether you want to take the plain text, "text", or the formatted text, "html".

It is important to remember though that any variables inserted into html have been HTML encoded server-side here. And also assume that correct JSON encoding has been carried out which stops any quote characters from breaking, or changing the context of the JSON (this is not shown in the above for simplicity).

text would be inserted into the document using Node.textContent and html as Element.innerHTML. Using Node.textContent will cause the browser to ignore any HTML formatting and script that may be present because characters like < are literally taken to be output as < on the page.

Note your example shows user content being input as script. i.e. a user has typed <script>alert('hey')</script> into your application, it is not API generated. If your API actually wanted to output tags as part of its function, then it'd have to put them in the JSON:

"html":"<u>Underlined</u>"

And then your text would have to only output the text without formatting:

"text":"Underlined"

Therefore, your API while sending information to your web application consumer is no longer transmitting rich text, only plain text.

If, however, a third party was consuming your API, then they may wish to get the data from your API as plain text because then they can set Node.textContent (or HTML encode it) on the client-side themselves, knowing that it is safe. If you return HTML then your consumer needs to trust you that your HTML does not contain any malicious script.

So if the above content is from api.example.com, but your consumer is a third party site, say, www.example.edu, then they may feel more comfortable taking in text rather than HTML. Your output may need to be more granularly defined in this case, so rather than outputting

"text":"Thank you Alice for signing up."

You would output

[{ "name", "alice",
"messageType": "thank_you" }]

Or similar so you are not defining the layout in your JSON any longer, you are just conveying the information for the client-side to interpret and format using their own style. To further clarify what I mean, if all your consumer got was

"text":"Thank you Alice for signing up."

and they wanted to show names in bold, it would be very tricky for them to accomplish this without complex parsing. However, with defining API outputs on a granular level, the consumer can take the relevant pieces of output like variables, and then apply their own HTML formatting, without having to trust your API to only output bold tags (<b>) and not to output malicious JavaScript (either from the user or from you, if you were indeed malicious, or if your API had been compromised).

SilverlightFox
  • 32,436
  • 11
  • 76
  • 145