Security

The main topic for this reading is a security vulnerability called Cross-Site Scripting or XSS. You would think it would be called CSS, but that stands for Cascading Style Sheets.

There are also a few short, miscellaneous security-related items.

Cross-Site Scripting (XSS) Attacks

The security issues when modifying the database (INSERT and UPDATE) are much worse than merely reading data (FIND), because the malicious user could put in data that indirectly attacks other users. This is an XSS attack: Cross-Site Scripting attack.

The idea of XSS is that the malicious person manages to get data treated as code and executed. The target of the attack is not the server but some victim's browser. That is, the code will be executed by the browser of some other user of the web application. Your database is just a transmission vector.

To be concrete, let's consider a web application that allows "user comments" (like Yelp, Amazon.com, Facebook, and many others). The malicious user (Mallory) puts some code in their comment, which is then executed by the victim's browser when the victim (Vicky) reads the comment.

The following figure illustrates the idea:

Mallory uses the web application to insert malicious JavaScript code that Vicky's browser executes
Mallory uses your web application to insert malicious JavaScript code that Vicky's browser executes

The most common scenario is that your web application puts some text from the database into some static HTML:

    text = doc.comment;
    return res.render("something.ejs", {text})

What happens if text is some of the following?

<img src="http://nasty.xxx/"> 
<script>window.location = "http://nasty.xxx"</script> 
<em onmouseover="window.location = 'http://nasty.xxx';"> 
    mouse over me!</em> 

Encoding

The solution to this attack is to encode or escape the characters that go to the browser, so that it won't be executed as JavaScript. (There are some additional complexities that we will get to later in the course, if we have time, but this will be a good start.)

Encoding the text can ensure that it is not treated as HTML code by the browser. There are five important characters in HTML:

Five critical syntactic characters the HTML language
NameCharacterencodedUnicode code point (decimal)Name
quot"&quot;U+0022 (34)quotation mark
amp&&amp;U+0026 (38)ampersand
apos'&apos;U+0027 (39)apostrophe (1.0: apostrophe-quote)
lt<&lt;U+003C (60)less-than sign
gt>&gt;U+003E (62)greater-than sign

So, if the dynamic text were the string

<script>...</script>

and we encoded it as

&lt;script&gt;...&lt;/script&gt;

and displayed the encoded string on the page, the user would see:

<script>...</script>

instead of whatever the script caused when it executed. So the code is displayed instead of being executed. It's treated as plain text instead of as code.

This encoding/escaping technique greatly mitigates the danger of XSS attacks.

EJS and Encoding

The EJS templating engine automatically encodes for us, when we use the usual tags for inserting dynamic data. So:

<p>User <%= username %> commented that 
<blockquote>
   <%= text %>
</blockquote>

When rendered with

   res.render('template.ejs', 
               {username: "Mallory", 
                text: "<script>...</script>"})

Will result in:

<p>User Mallory commented that 
<blockquote>
   &lt;script&gt;...&lt;/script&gt;
</blockquote>

which defeats the attack.

So, by default, EJS protects us from most XSS attacks. Whew!

Other Vectors

Suppose that, for some reason, our templates allow untrustworthy data to be inserted into tags:

<script> <%= dynamic_code1 %> </script>

<h2 onmouseover="<%= dynamic_code2 %>"> some heading </h2>

Now, suppose that Mallory knows or guesses that our app has done this. She contributes code like this:

let badUrl = code;
window.location = badUrl;

Suppose the code part is able to create a string, say from ASCII codes and such, without using any of the five encoded characters. Because Mallory's code is in a context where it will be executed by the browser, the encoding trick won't save us or Vicky from Mallory's attack.

The issue is that the code is already being rendered in a context where it will be executed, so it doesn't have to try to trick the browser into executing it.

What this means for you as a web application developer is that you must think about where you insert user-supplied data into your templates.

XSS Demo

I've created a short demo of an XSS attack in the ~cs304node/apps/passwords/ folder, namely xssdemo.js

Collection

The basic setup is that there's a collection of user-contributed comments. Rather than create a real MongoDB database collection, I'm just going to hard-code the collection in the xssdemo.js file:

/* For the purpose of this XSS demo, I want you to imagine a database
 * collection with documents in it that are user comments. We'll
 * represent that database collection with an array of documents.
 */

const collection = [
    {user: "luna",
     comment: "I think this is <b>really</b> nice!"},
    {user: "malfoy",
     comment:
     "This is <b>stupid</b>"+
     "<script>window.location='https://www.youtube.com/watch?v=dQw4w9WgXcQ';</script>"}
];

Main page

The main page just lets us go to the various demo routes. There are two demo routes, one of which rendered data with escaping and one of which doesn't use escaping. As we saw above, there are two user-contributed comments. That gives us four combinations. Here's the handler, which is trivial:

app.get("/", (req, res) => {
    return res.render("xssdemo.ejs", {})
});

and here's the important part of the EJS file:

  <ul>
      <li>Escaped
          <ul>
              <li><a href="/showEscaped/luna">/showEscaped/luna</a>
              <li><a href="/showEscaped/malfoy">/showEscaped/malfoy</a>
          </ul>
      </li>
      <li>Un-Escaped
          <ul>
              <li><a href="/showUnescaped/luna">/showUnescaped/luna</a>
              <li><a href="/showUnescaped/malfoy">/showUnescaped/malfoy</a>
          </ul>
      </li>
  </ul>

Escaped

Here's the route that uses the escaped EJS template:

app.get("/showEscaped/:user", (req, res) => {
    const user = req.params.user;
    const doc = collection.find((doc) => user === doc.user);
    return res.render("xssdemo-escaped.ejs", {doc});
});

and relevant part of the EJS file:

  <p>User <%= doc.user %> commented that <%= doc.comment %></p>

Unescaped

Here's the route that uses the un-escaped EJS template; it's identical except for the template:

app.get("/showUnescaped/:user", (req, res) => {
    const user = req.params.user;
    const doc = collection.find((doc) => user === doc.user);
  return res.render("xssdemo-unescaped.ejs", {doc})
});

and the relevant part of the EJS file, which is identical except for the tag:

  <p>User <%- doc.user %> commented that <%- doc.comment %></p>

Behavior

We'll do a demo of all four combinations in class, but just looking at Luna's contributions, the escaped page says:

User luna commented that I think this is <b>really</b> nice!

while the unescaped page says:

User luna commented that I think this is really nice!

The unescaped pages obey or execute the HTML that Luna contributed, which means that they look nicer but are vulnerable to Malfoy's XSS attack, meaning that they will execute the JavaScript in Malfoy's comment.

I leave it to you to imagine what the pages look like with Malfoy's comment. We'll see this in class.

Disabling Encoding

Are there any circumstances in which you might want to render dynamic data into executable parts of the web page? Perhaps.

  • The data might come not from users, but from some trustworthy source (maybe yourself), or
  • The data is only going to the user who contributed it, so the user could only attack themselves

If you do want to do this, EJS has a special tag:

<%- unescaped (unencoded) value %>

The reading on EJS mentioned this tag but only hinted at the XSS security risk.

Now you know.

Quizzes

Note: you actually ran this risk of an XSS attack when you rendered the crowd-sourced question in the quizzes assignment. Do you trust everyone in the crowd? How did you insert the code into your page?

Miscellaneous Security Topics

A few short but important things to know.

Omit usernames and passwords from source code

Your project source code will go in a public GitHub repository. Therefore, you should not put anything in there that is sensitive, personal, or has a security function. Obviously, usernames and passwords would qualify.

Similarly, we should omit our session key from the source code, though the fact that we are running inside the campus firewall makes this less important. However, if it weren't for the firewall, this would be another secret to not put in the source code. Or to replace with a randomly generated string.

Storing Data on your Server

Suppose you're storing sensitive data, such as SSN or credit card numbers. You have to worry about three things:

  1. Is it secure on the web server or the database server? If someone hacks into that server, can they get the data?
  2. Is it secure in transit? Your finished and deployed app should use HTTPS.
  3. Is it secure on the user's machine? For example, if the sensitive data is in a cookie or a hidden form input, it may not be secure. It should not be in the URL of a GET method. Always use the POST method if anything sensitive is involved, because browsers cache URLs, servers keep logs of URLs, and people look over other people's shoulders.

A few things to consider to reduce the dangers:

  • Do you really need the data? If you use and then discard the sensitive data (for example, don't keep their credit card number on file), then the data simply isn't there to steal.
  • First line of defense is preventing them from hacking in: that is, employ good system security. Take the security course.
  • Try to make the data unusable: store credit card numbers or SSNs in encrypted form. We'll discuss that in our reading on storing passwords.

Hidden Fields

Don't put information in hidden fields that you need to trust when it comes to your server. Anyone can do "view source" and see the hidden fields. They can then do a "save as" and modify the form (those hidden inputs) and submit it to your server.

General Suspicion

Never trust the user

Summary

XSS

  • XSS is an attack by a malicous user of your app where the victim is another user of your app
  • The means of the attack is to have the victim's browser parse and execute some malicious HTML, CSS or JavaScript code. Usually JavaScript.
  • Usually, the malicious code is delivered by inserting it into the the database, so that your app then sends it to the victim's browser via res.render().
  • Since the templates are all written by your team, the only way malicious code gets into the template is via the dynamic data that is rendered into the template.
  • The attack is prevented by encoding (escaping) the dynamic data in a way that renders it harmless, because the browser doesn't execute it, it just displays it. For example, turning an angle bracket into &lt;, so that the browser shows it as an angle bracket, instead of treating it as the beginning of a tag.
  • EJS does that encoding automatically, so XSS techniques are prevented by default.
  • However, you still have to think about where in your template you are putting user data; make sure it's not in a context where it will get executed.
  • In the rare case that you want to disable the automatic encoding, you can do so in the template by inserting the data with a different tag: <%- trusted data %>

Miscellaneous Security Admonitions

  • don't put usernames and passwords in the source code, especially if it might end up public, say on Github.
  • don't put sensitive information in the URL or use the GET method with such data
  • Never trust the user