In my previous article, I showed you how to protect your server from attacks and malicious software. This part will focus completely on the third layer of security - your application itself. So here, I will show you techniques that you can use to protect your application from attacks and intrusions.
Using a Database
When communicating with a database and in order for your data to remain safe, keep the following key points in mind:
Always Escape Queries
To stop attackers from using SQL Injection, you have to escape all users' input so they can't inject SQL queries into your application(for example, during a login). In pretty much all of the database drivers, for all languages, there is an option to escape user input. For example in node-mysql for Node.js, instead of doing your queries like this:
connection.query('SELECT * FROM users WHERE name = \''+ username +'\' AND password = \''+ password '\';', function (err, rows, fields) {
You can automatically escape them using this syntax:
connection.query('SELECT * FROM users WHERE name = ? AND password = ?;', [ username, password ], function (err, rows, fields) {
Variables from the array provided as a second argument to the query
method will be inserted in place of question marks in the query string and will be automatically escaped. The same can be done in PHP using PDO's prepared statements:
$stmt = $dbh->prepare("SELECT * FROM users WHERE name = ? AND password = ?;"); $stmt->bindParam(1, $username); $stmt->bindParam(2, $password); $stmt->execute();
Never Trust Content From the Database
Even if you escape your database queries, you never know if a bug in your application has let some malicious code through. If you have written your app in a way to where you always assume that the content in the database is safe, then your users are endangered. For example, this code:
if ($result = $mysqli->query('SELECT * FROM users WHERE name = "'. $mysqli->escape_string($username) .'";')) { if ($row = $result->fetch_assoc()) { if ($result = $mysqli->query('SELECT * FROM someData WHERE uid = '. $row['id'] .';')) { ... } } }
Is unsafe! If something goes wrong and the id of the user contains some SQL injection code, then you are going to have problems. $row['id']
should also be escaped using the escape_string()
method, like so:
if ($result = $mysqli->query('SELECT * FROM users WHERE name = "'. $mysqli->escape_string($username) .'";')) { if ($row = $result->fetch_assoc()) { if ($result = $mysqli->query('SELECT * FROM someData WHERE uid = '. $mysqli->escape_string($row['id']) .';')) { ... } } }
Use a Salt When Hashing
Now imagine this situation: some genius hackers break through your carefully crafted security and get their hands on your database. They then begin to brute-force all of your users' passwords. If you used a salt when hashing them, you can sleep peacefully and assure your users that their data is safe. Because unless the attacker has a quantum computer in their basement, it will take them years to crack any of the passwords.
Using a salt means that you append a few random characters to the password before hashing (these characters are what is called the salt) and store them with the password. You may think that this is not very secure, since the attacker will know the salt if they gain access to the database. But since the salt is different for every user, even if two of them use the same password their hashes will be different. This forces the attacker to crack them one by one, which makes it very time consuming and usually not worth the time wasted. This also means that the attacker can't use rainbow tables or a dictionary to lookup the passwords.
Salting a Password in Node.js: node-mysql
First you should generate the salt. For example, you could use crypto.randomBytes()
:
var crypto = require('crypto');
/* this should be done on request with the user's data for registration, instead of just adding it to the database */ crypto.randomBytes(16, function (e, salt) {
This function will throw an error if, according to the Node.js documentation, "there is not enough entropy to generate cryptographically strong data". In such a case, you should either try again or use crypto.pseudoRandomBytes()
, which generates non-cryptographically strong random bytes, but since this will only happen occasionally, we can use it:
if (e) { salt = crypto.pseudoRandomBytes(16); }
Now let's add the data. For simplicity, I'll only use a username, password and salt in this example:
/* password and username should be extracted from request body */ var hash = crypto.createHash('sha-256').update(password + salt.toString()).digest('hex'); sql.query('INSERT INTO users VALUES(?, ?, ?);', [ username, password, salt ], function (err, rows, fields) { /* your other app logic here, what happes after the user data is into the database */ }); });
When your user wants to login, you have to get the salt from the database and then check the password:
sql.query('SELECT salt FROM users WHERE name = ?;', [ username ], function (err, rows, fields) { if (rows.length < 1) { /* the user does not exist */ } else { var hash = crypto.createHash('sha-256').update(password + rows[0].salt).digest('hex'); sql.query('SELECT 1 FROM users WHERE name = ? AND password = ?;', [ username, hash ], function (err, rows, fields) { if (rows.length < 1) { /* wrong password */ } else { /* password ok */ } }); } });
And that's pretty much it. Now let's see how we can do this using PHP.
Salting a Password in PHP
In PHP this is much simpler, because the built in functions password_hash()
and password_verify()
automatically uses a salt and the hash returned contains it, so you don't even need another column in the database (or field in case of NoSQL). It would look something like this:
$stmt = $dbh->prepare("INSERT INTO users VALUES (?, ?);"); $stmt->bindParam(1, $username); /* hash the password, the function adds random salt automatically */ $stmt->bindParam(2, password_hash($password)); $stmt->execute();
To check the login, you just have to call password_verify()
. It automatically gets the salt from the hash so you only need to supply the hash from your database and the plaintext password to compare. The needed code, is as follows:
$stmt = $dbh->prepare("SELECT password FROM users WHERE name = ?;"); $stmt->bindParam(1, $username); $stmt->execute(); $row = $stmt->fetch(PDO::FETCH_ASSOC); if ($row) { /* check if the password matches */ if (password_verify($password, $row['password'])) { /* password ok! */ } } else { /* the user does not exist */ }
POSIX: Drop Privileges When You Don't Need Them
This is one of the best defense mechanisms you can use to protect your machine and therefore your users. You need elevated privileges for a couple of things, mainly listening on port numbers lower than 1024 or messing with system files. But you should never run any app as root when you do not need to, just in case the attacker finds a bug in your code that allows them to execute commands on your server and if the code is running as a privileged user, it's game over. The attacker can do whatever they want and will probably be done before you even notice anything. This is why you should drop the privileges as fast as possible. In Node.js it would look like this:
var app = http.createServer(...); ... app.listen(80); // listen on the 80 port, to do so you have to run your app as root user process.setuid('app_user'); // drop privileges - switch to a non-root user - only POSIX platforms sadly
The process.setuid()
function will change the user identity of the process to the one passed to it - it can be either a numerical ID or a username string (in the second case, this function will block while getting the ID of the user). The user should be unprivileged and only have access to the app-specific files, to limit the risk of giving the attacker access to anything else on your machine.
An Alternative Way: Using authbind
Some people say (for example in this comment on dropping root privileges), that the solution above is not perfect and they prefer to use authbind
instead. It's your decision which one you choose to use and it pretty much depends on your setup. But anyways, authbind
is a command that was designed for this purpose - it allows your app to bind to ports lower than 1024 without root privileges (so it's only covering that scenario). To use it, first create a file: /etc/authbind/byport/port
, where port is the port number you want to bind to and make it executable by the user you will use, to execute your app. Then switch to your user and start the app like this:
authbind node yourapp.js
Or like this from root:
su -c 'authbind node yourapp.js' youruser
With this, you can accomplish the same end goal as with the POSIX solution, just now using authbind
instead, if that's what you prefer.
Wrap Up
So hopefully you've picked up some new techniques for working with sensitive data throughout these two articles. In the previous article we started off learning how to protect our data on the server by choosing the right server provider, updating our OS, securing ports and using an antivirus. In this article, we wrapped up our discussion by talking about securing our application itself through the use of proper database security, password salting, and user privileges.
Feel free to share your own security tips in the comments and thank you for reading.
Comments