Web Security

Many web applications contains security vulnerabilities hackers can exploit. Let's take a look at the most commonly used ones, and how we can prevent them.

Lecture material

Fun from xkcd:

The rest of the recommended readings are primarily articles giving examples of vulnerabilities that have existed. You don't need to read all of them, but look through a few to get a good understanding of what a vulnerability can be in practice.

Various articles

Cross-Site Scripting (XSS)

Vulnerability

The vulnerability Cross-Site Scripting (XSS) is when a hacker manage to inject client-side code (HTML, CSS or JavaScript, etc.) on your website that is executed by your users' web browsers when they visit your website. By injecting client-side JavaScript code a hacker can tell the users' web browsers to do some bad things for the user, such as:

  • Read information found on the webpage (which might be sensitive information about the user if she has logged in to an account) and then send that information to the hacker's own server in an HTTP request.
  • Send HTTP requests to various websites. These websites will think the requests are intentionally sent by the user, so they will happily carry out the requests. This is especially troublesome if the user has logged in to an account on these websites.

Example

In the search form below users should be able to enter search terms, and then get back a list with the search result. To indicate to the user what she has searched for, it also displays the search term. In this example, the search result is never displayed, only what the user searched for. Try entering a search term containing HTML code, for example <b>Hi!</b>, or <b onclick="alert('Hi!')">Click me!</b> (client-side JavaScript must be enabled in your web browser for the example to work). Although these examples are not harmful to the user, it shows the potential danger with the vulnerability if a hacker takes advantage of it.

Search:

Protection

When data comes from an untrusted source (for example a user or a third-party application you are using) you must escape the HTML code before sending it to the web browser as part of the webpage in the HTTP response. You can escape the data by replacing the following characters in it (the characters that have special meaning in HTML syntax) with their corresponding entity names:

  • < can be replaced with &lt;
  • > can be replaced with &gt;
  • " can be replaced with &quot;
  • ' can be replaced with &apos;

When the web browser for example sees &lt;, it will display the < character.

Example

With proper protection, the search form in the previous example would work like the one shown below (client-side JavaScript must be enabled in your web browser for the example to work).

Search:

Express

When you use request.send("Input from a client.") you are responsible for escaping the input yourself. When you pass input to a view, the view engine usually escapes the data for you. For example, when using Handlebars as your view engine, data you insert into the view using {{expression}} will escape the HTML code in `expression`. This is usually what you want to happen, but if you for some reason don't want that, you can use {{{expression}}}, which won't escape the HTML code in `expression`, but then you are responsible to make sure that no XSS vulnerability exists.

Cross-Site Request Forgery (CSRF)

Vulnerability

The vulnerability Cross-Site Request Forgery (CSRF) is about tricking the user's web browser into sending HTTP requests to other websites that is gainable for the hacker. This is especially troublesome if the user is logged in to an account on these websites, for example:

  1. Alice sends her correct login credentials to bank.com.
  2. bank.com creates a session containing an identifier for Alice's bank account.
  3. Alice web browser stores the session's identifier in a cookie.
  4. Alice somehow ends up at bad-website.com, which contains client-side code from a hacker.
  5. The bad client-side code tells Alice's web browser to send an HTTP request to bank.com to transfer $1000 to the hacker's own bank account. Since Alice before logged in to bank.com, that cookie with the session identifier is attached to the request, so to bank.com it looks like the HTTP request is intentionally sent by Alice, so bank.com carries out the request.

SQL injection

Vulnerability

The vulnerability SQL injection is when a hacker manages to alter an SQL query the web application sends to the database. This happen when the web application tries to dynamically build the SQL query with some values it has received from the client, and the hacker can alter the query by providing a carefully chosen value.

Imagine a website containing user accounts. The URI /accounts/ACCOUNT_ID is used to identify the account with the id ACCOUNT_ID. For example, /accounts/3 identifies the account with the id 3. When a GET request for this URI is received, the web application needs to send a query like SELECT * FROM accounts WHERE id = ACCOUNT_ID to the database. If that query is dynamically generated like this:

app.get("/accounts/:ACCOUNT_ID", function(request, response){
    
    const ACCOUNT_ID = request.params.ACCOUNT_ID
    
    const query = "SELECT * FROM accounts WHERE id = "+ACCOUNT_ID
    
    // Send query to DB.
    
})

Then the hacker can use a clever ACCOUNT_ID to alter the query sent to the database. If an expected account id like 3 is used, then the query sent to the database will be SELECT * FROM accounts WHERE id = 3, but if the hacker uses an account id like -1 OR username = 'Alice', then the query sent to the database will be SELECT * FROM accounts WHERE id = -1 OR username = 'Alice', which the hacker can use to to retrieve Alice's account. This might not be a big deal, but if the hacker would use the account id -1 OR password = 'abc123', he would get back the first user to have the password abc123, and can then simply login to that account (assuming passwords aren't hashed).

Example

Below you can change the URI and see how the query to the database changes (requires client-side JavaScript to be enabled).

GET /accounts/

SELECT * FROM accounts WHERE id =

Protection

Don't use input from the client (a query string parameter, a cookie, the body of the request, a dynamic URI parameter, etc.) to dynamically generate SQL queries sent to the database. Instead, use placeholders for dynamic values in the query, and pass the values separately to the database.

Express and SQLite 3

app.get("/accounts/:ACCOUNT_ID", function(request, response){
    
    const ACCOUNT_ID = request.params.ACCOUNT_ID
    
    const query = "SELECT * FROM accounts WHERE id = ?"
    const values = [ACCOUNT_ID]
    
    db.get(query, values, function(error, account){
        // ...
    })
    
})