4.4: Security
Learning Objectives
The most vulnerable security loopholes are usually human ones such as phishing or insecure storage of passwords
If we follow best practices for building apps, generally our apps should be technically secure
Know how well-known attacks such as XSS, CSRF and SQL injection work
Introduction
Data breaches more commonly exploit human behaviour than technical loopholes, using techniques such as phishing to retrieved credentials from privileged users. This is why cybersecurity is more often about mandating phishing training, multi-factor authentication and minimal access privileges across organisations than technical security of applications.
If we follow best practices for building apps as we have so far, our apps should be technically secure. When we deviate from standard libraries and configure authentication, database access, cloud access privileges manually, that is when security considerations become more relevant. Teams that manage sensitive data should conduct regular audits to ensure technical application security.
In this submodule we will learn about 3 well-known types of technical attacks on web applications, so common they are listed in the OWASP Top 10, a publicly-maintained list of the 10 most common web application security risks. These are Cross-Site Scripting (XSS), SQL Injection and Cross-Site Request Forgery.
Cross-Site Scripting (XSS)
Introduction
Cross-site scripting (aka XSS) is a type of injection attack where an attacker saves JavaScript in a system to be executed later. When executed, that JavaScript might send a request with the logged-in user's credentials to a different server, or perform an unauthorised transaction on the current server.
Part 1: Save JavaScript to database
Exploit
The first part of an XSS attack is saving JavaScript to the database. We can do this by entering JavaScript into an non-sanitised form field, such as the following.
Mitigation: Validate user input
Always validate user input before saving to database. Validation logic is complicated, but luckily others have implemented convenient libraries for us and those libraries are publicly maintained. One of the most popular validation libraries is xss
.
Part 2: Execute non-sanitised user input
Exploit
Once the app has saved our script to the database, the app should run our script every time the app retrieves that data and renders it in the frontend.
We can now make the app execute arbitrary JavaScript and alter our JavaScript to steal other users' credentials. One such exploit would be to send the logged-in user's cookies to our own server. Below is an example of such a script that creates an img
HTML element whose src
attribute is our URL with all cookies attached.
Mitigation: Sanitise database values before executing
Most modern view libraries including React will automatically sanitise values before rendering them in HTML to prevent XSS attacks. To prevent this exploit, use a standard library such as React for rendering views, and if we cannot, find a library to escape values before rendering those values in HTML.
Here is React's documentation on how it sanitises input before rendering it in JSX.
SQL Injection
Introduction
SQL injection is a type of injection attack where the attacker injects malicious SQL into user input that is subsequently executed on the database. This is commonly used to steal or corrupt data.
Exploit: Clever SQL Syntax
Imagine the following Express logic that generates a SQL query.
Now imagine instead of a valid name, we input the following to the app.
This would generate the following SQL query, which would return all user emails and passwords.
The above example is from Portswigger, a well-known web security blog. UNION
syntax let's us execute an additional SQL query and append results to the original.
Mitigation: Always parameterise SQL queries
Our app is vulnerable to SQL injection because we use raw user input in our SQL query.
To prevent this, most SQL libraries such as pg
allow us to provide user input as parameters separate from the SQL query, such that the library can sanitise that input before constructing the query. ORM libraries like Sequelize automatically sanitise input when constructing SQL queries.
Cross-Site Request Forgery (CSRF)
Introduction
Cross-Site Request Forgery (aka CSRF) is an attack where an attacker sends a forged request to a legitimate server to perform functionality on behalf of a user. This forged request is commonly generated by phishing, for example making the user click a link that triggers the attack functionality. This can be used to compromise user accounts by changing passwords, for example.
Exploit Part 1: Make user click on link
If we suspect a user's browser may be storing authentication credentials, we can use phishing techniques to make that user click on a link that either changes those credentials to something we know (e.g. change password) or perform other functionality (e.g. transfer money). This link can be masked with text (e.g. "unsubscribe here") or a call to action button (e.g. "Learn More").
Exploit Part 2: Legitimate server does not check for CSRF
As an example, if an API server's password reset route were a GET route and user credentials were stored in the browser from a previous login, attackers could craft a link to set new password credentials in the request URL while sending login credentials via cookies with the request. This is why we should always use POST or PUT requests for altering data on our servers.
Mitigation: CSRF token and token verification
To prevent CSRF, we can protect sensitive routes that should only be accessed by specific forms (and not wild links) with cryptographic tokens called CSRF tokens. These tokens would be generated by the backend, passed to the frontend on form load and validated by the backend on form submission. This guarantees that users can only submit valid form submissions from the frontend app and nowhere else.
csurf
is the most popular CSRF token library for Express.