Day-4 DHIS2 Web Apps Security Considerations

a. Whitelisting :

Using whitelists as a protection mechanism is a robust defensive mechanism. Which domains scripts can pull data from, what type of data users can submit to the system, and what iframes to load are all things that benefit from a whitelist strategy.

b. CORS

The browsers share many security mechanisms[1], but they rely on that the developer understands e.g.: content isolation, origin inheritance, CORS and how it affects the SOP mechanisms in the browser:

c. HTML5 security cheat sheet:

Check out the HTML5 security cheatsheet[2] and get familiar with the many, many, ways to do Bad Things with simple tricks. They maintain an up-to-date list of known attack vectors (DOM, JSON, UI redressing, SVG, JS, CSS, etc) for browers and their versions, along with snippets to demonstrate.

d. CSRF anti-measures

CSRF anti-measures can be implemented client-side to ensure that the client is in a consistent state without being known to the server.

e. Do not use credentials in JS file
f. Mindset:

General Security Concerns:

1. Use SSL (Secured Socket Layer) /TLS (Transport Layer Security) for communication

It is always a good practice to send your data over HTTPS rather than HTTP and it is imperative if your app transmits sensitive data. Encrypting data transmitted between the client and server helps mitigate several attacks like man-in-the-middle(MITM) attack, packet sniffing, eavesdropping etc. Let’s see how to set up TLS/SSL in Express 4.x:

Lets first generate a self-signed certificate:
$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365

This generates a self-signed certificate valid for 365 days. NOTE: The self-signed certificate is not ideal for production. For production, you should get a certificate from a Certificate Authority(CA).

Next, enable HTTPS on Express. Additionally, redirect all HTTP traffic to HTTPS:
const fs = require('fs');
const https = require('https');
const express = require('express');
const NODE_ENV = process.env.NODE_ENV || 'development';
const PORT = process.env.PORT || 3443;
 
const app = express();
https.createServer({
  key: fs.readFileSync('/path/to/key.pem'),
  cert: fs.readFileSync('/path/to/cert.pem')
}, app).listen(PORT);

// Redirect http requests to use https in production
if (NODE_ENV === 'production') {
  app.use((req, res, next) => {
    if (req.header('x-forwarded-proto') !== 'https') {
      res.redirect(`https://${req.header('host')}${req.url}`);
    } else {
      next();
    }
  });
}
2. Use security headers generously

i) Strict-Transport-Security: The HTTP Strict Transport Security(HSTS) if set in the response header, tells the browser that it should only communicate using HTTPS instead of HTTP while communicating with the specified domain.

Syntax:
Strict-Transport-Security: max-age=

Here, max-age is the time(in secs) that the browser should remember that this site is only to be accessed using HTTPS.

Example from facebook.com:

strict-transport-security:max-age=15552000;

ii) X-Frame-Options: This HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a frame, iframe or object . Sites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites.


Syntax:

X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://siteutrust.com/
iii) X-XSS-Protection: This HTTP response header enables the built-in XSS filter in modern browsers.
Example:
X-XSS-Protection: 1
iv) X-Content-Type-Options: This response HTTP header is a marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This prevents MIME type sniffing attacks.
Syntax:
X-Content-Type-Options: nosniff

v) Content-Security-Policy: Prevents a range of injection attacks including Cross Site Scripting(XSS) attack.
Syntax:
Content-Security-Policy: policy

For a detailed explanation of CSP, go through this link. To set these headers in NodeJS, use the helmet npm package:
const express = require('express');
const helmet = require('helmet');
 
const app = express();
 
app.use(helmet())
This sets all the necessary headers in response.
To set the headers individually:
app.use(helmet({
  frameguard: {
    action: 'deny'
  }
}));

NOTE: In some web servers, the security headers can be set in the server configuration file itself. For example, in nginx server, we can set the above headers in nginx.conf as shown below:
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection 1;
add_header Content-Security-Policy "default-src 'self'";

3. Preventing CSRF attacks

Cross site request forgery (CSRF), also known as XSRF, Sea Surf or Session Riding, is an attack vector that tricks a web browser into executing an unwanted action in an application to which a user is logged in. CSRF attacks specially targets state-changing requests and can force the victim to transfer funds, change email/password and so on. CSRFs are typically conducted using social engineering, such as an email or link that tricks the victim into sending a request to a server on behalf of the attacker. The server has no way to distinguish a forged request from a genuine one.

In NodeJS, to prevent CSRF attack, we usually use the csurf express middleware:

const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const bodyParser = require('body-parser');
const express = require('express');
const csrfProtection = csrf({ cookie: true });
const parseForm = bodyParser.urlencoded({ extended: false });
 
// create express app 
const app = express();
 
// we need this because "cookie" is true in csrfProtection 
app.use(cookieParser());
 
app.get('/form', csrfProtection, (req, res) => {
  // pass the csrfToken to the view 
  res.render('send', { csrfToken: req.csrfToken() });
});
In the view use the CSRF token passed:
Enter amount:
4. Preventing XSS attacks

Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted web sites.

An attacker can use XSS to send a malicious script to an unsuspecting user. The end user’s browser has no way to know that the script should not be trusted, and will execute the script. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by the browser and used with that site. These scripts can even rewrite the content of the HTML page.

The thumb rule to prevent this category of attack is to always validate and sanitize user data before processing or storing in database. Never trust data coming from user. Validation must be done on the server-side as client-side validation can be easily bypassed using tools such as Burp Suite, TamperData etc.

A common approach to validate and sanitize user data is to use a library like validator.js.

Example: To validate an email

import validator from 'validator';
if(validator.isEmail('foo@bar.com')) {
  // Process email or store in DB
}

This library provides a number of validators and sanitizers to filter user inputs.
Here is an example to sanitize user input using xss-filters:

const express = require('express');
const app = express();
const xssFilters = require('xss-filters');

app.get('/', (req, res) => {
  let firstname = req.query.firstname; //an untrusted input
  res.send('

Hello, ' + xssFilters.inHTMLData(firstname) + '!

'); }); app.listen(3000);
5. Preventing SQL Injection(SQLi) attacks

Passing unvalidated user input directly to a SQL statement is vulnerable to SQL injection attack. Consider the following example:


// SQL query vulnerable to SQLi
sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";

// Execute the SQL statement
database.execute(sql)

Now suppose the user enters the following in the username field:

' OR '1'='1' --

The above SQL statement becomes:
sql = "SELECT * FROM users WHERE username='" + ' OR 1=1 -- + "' AND password='" + password + "'";
6. Secure cookies using cookie flags

XSS vulnerability in an application can be used to steal browser cookies. To prevent cookie stealing we can set the httpOnly flag of the cookie.

Additionally, we can tell the browser to send cookies only over HTTPS using the secure flag. secure : this attribute tells the browser to only send the cookie if the request is being sent over HTTPS. HttpOnly : this attribute is used to help prevent attacks such as cross-site scripting, since it does not allow the cookie to be accessed via JavaScript.

Example:
app.use(session({ 
 secret: ‘My super secret’, 
 cookie: { httpOnly: true, secure: true } 
}));
7. Preventing brute force and DoS attack

To prevent our site from overwhelming with a large number of requests, we need to put some kind of rate limiting to our API.

We can use the ratelimiter npm package to implement rate limiting. If you are using Express, the express-rate-limit middleware can be used as shown below:


const RateLimit = require('express-rate-limit');
 
const limiter = new RateLimit({
  windowMs: 15*60*1000, // 15 minutes 
  max: 100, // limit each IP to 100 requests per windowMs 
  delayMs: 0 // disable delaying — full speed until the max limit is  reached
});
 
// apply to all requests 
app.use(limiter);

8. Error Handling

Any error in the application should be handled gracefully by showing a custom error page to the user instead of showing stack trace in the error page thereby leaking sensitive infrastructure information like server info.

References:

[1] https://code.google.com/archive/p/browsersec/wikis/Main.wiki
[2] https://html5sec.org/
[3] https://docs.google.com/document/d/1vvszJ3EEwPlHXQ2-o7t3Pc4LLkeU6an1vhagLdtHk3Y/edit [4]https://medium.com/@rajapradhan08/best-practices-for-securing-node-js-web-applications-3b69909b2a2a
a. Whitelisting :

Using whitelists as a protection mechanism is a robust defensive mechanism. Which domains scripts can pull data from, what type of data users can submit to the system, and what iframes to load are all things that benefit from a whitelist strategy.

b. CORS

The browsers share many security mechanisms[1], but they rely on that the developer understands e.g.: content isolation, origin inheritance, CORS and how it affects the SOP mechanisms in the browser:

c. HTML5 security cheat sheet:

Check out the HTML5 security cheatsheet[2] and get familiar with the many, many, ways to do Bad Things with simple tricks. They maintain an up-to-date list of known attack vectors (DOM, JSON, UI redressing, SVG, JS, CSS, etc) for browers and their versions, along with snippets to demonstrate.

d. CSRF anti-measures

CSRF anti-measures can be implemented client-side to ensure that the client is in a consistent state without being known to the server.

e. Do not use credentials in JS file
f. Mindset:

General Security Concerns:

1. Use SSL (Secured Socket Layer) /TLS (Transport Layer Security) for communication

It is always a good practice to send your data over HTTPS rather than HTTP and it is imperative if your app transmits sensitive data. Encrypting data transmitted between the client and server helps mitigate several attacks like man-in-the-middle(MITM) attack, packet sniffing, eavesdropping etc. Let’s see how to set up TLS/SSL in Express 4.x:

Lets first generate a self-signed certificate:
$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365

This generates a self-signed certificate valid for 365 days. NOTE: The self-signed certificate is not ideal for production. For production, you should get a certificate from a Certificate Authority(CA).

Next, enable HTTPS on Express. Additionally, redirect all HTTP traffic to HTTPS:
const fs = require('fs');
const https = require('https');
const express = require('express');
const NODE_ENV = process.env.NODE_ENV || 'development';
const PORT = process.env.PORT || 3443;
 
const app = express();
https.createServer({
  key: fs.readFileSync('/path/to/key.pem'),
  cert: fs.readFileSync('/path/to/cert.pem')
}, app).listen(PORT);

// Redirect http requests to use https in production
if (NODE_ENV === 'production') {
  app.use((req, res, next) => {
    if (req.header('x-forwarded-proto') !== 'https') {
      res.redirect(`https://${req.header('host')}${req.url}`);
    } else {
      next();
    }
  });
}
2. Use security headers generously

i) Strict-Transport-Security: The HTTP Strict Transport Security(HSTS) if set in the response header, tells the browser that it should only communicate using HTTPS instead of HTTP while communicating with the specified domain.

Syntax:
Strict-Transport-Security: max-age=

Here, max-age is the time(in secs) that the browser should remember that this site is only to be accessed using HTTPS.

Example from facebook.com:

strict-transport-security:max-age=15552000;

ii) X-Frame-Options: This HTTP response header can be used to indicate whether or not a browser should be allowed to render a page in a frame, iframe or object . Sites can use this to avoid clickjacking attacks, by ensuring that their content is not embedded into other sites.


Syntax:

X-Frame-Options: DENY
X-Frame-Options: SAMEORIGIN
X-Frame-Options: ALLOW-FROM https://siteutrust.com/
iii) X-XSS-Protection: This HTTP response header enables the built-in XSS filter in modern browsers.
Example:
X-XSS-Protection: 1
iv) X-Content-Type-Options: This response HTTP header is a marker used by the server to indicate that the MIME types advertised in the Content-Type headers should not be changed and be followed. This prevents MIME type sniffing attacks.
Syntax:
X-Content-Type-Options: nosniff

v) Content-Security-Policy: Prevents a range of injection attacks including Cross Site Scripting(XSS) attack.
Syntax:
Content-Security-Policy: policy

For a detailed explanation of CSP, go through this link. To set these headers in NodeJS, use the helmet npm package:
const express = require('express');
const helmet = require('helmet');
 
const app = express();
 
app.use(helmet())
This sets all the necessary headers in response.
To set the headers individually:
app.use(helmet({
  frameguard: {
    action: 'deny'
  }
}));

NOTE: In some web servers, the security headers can be set in the server configuration file itself. For example, in nginx server, we can set the above headers in nginx.conf as shown below:
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection 1;
add_header Content-Security-Policy "default-src 'self'";

3. Preventing CSRF attacks

Cross site request forgery (CSRF), also known as XSRF, Sea Surf or Session Riding, is an attack vector that tricks a web browser into executing an unwanted action in an application to which a user is logged in. CSRF attacks specially targets state-changing requests and can force the victim to transfer funds, change email/password and so on. CSRFs are typically conducted using social engineering, such as an email or link that tricks the victim into sending a request to a server on behalf of the attacker. The server has no way to distinguish a forged request from a genuine one.

In NodeJS, to prevent CSRF attack, we usually use the csurf express middleware:

const cookieParser = require('cookie-parser');
const csrf = require('csurf');
const bodyParser = require('body-parser');
const express = require('express');
const csrfProtection = csrf({ cookie: true });
const parseForm = bodyParser.urlencoded({ extended: false });
 
// create express app 
const app = express();
 
// we need this because "cookie" is true in csrfProtection 
app.use(cookieParser());
 
app.get('/form', csrfProtection, (req, res) => {
  // pass the csrfToken to the view 
  res.render('send', { csrfToken: req.csrfToken() });
});
In the view use the CSRF token passed:
Enter amount:
4. Preventing XSS attacks

Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted web sites.

An attacker can use XSS to send a malicious script to an unsuspecting user. The end user’s browser has no way to know that the script should not be trusted, and will execute the script. Because it thinks the script came from a trusted source, the malicious script can access any cookies, session tokens, or other sensitive information retained by the browser and used with that site. These scripts can even rewrite the content of the HTML page.

The thumb rule to prevent this category of attack is to always validate and sanitize user data before processing or storing in database. Never trust data coming from user. Validation must be done on the server-side as client-side validation can be easily bypassed using tools such as Burp Suite, TamperData etc.

A common approach to validate and sanitize user data is to use a library like validator.js.

Example: To validate an email

import validator from 'validator';
if(validator.isEmail('foo@bar.com')) {
  // Process email or store in DB
}

This library provides a number of validators and sanitizers to filter user inputs.
Here is an example to sanitize user input using xss-filters:

const express = require('express');
const app = express();
const xssFilters = require('xss-filters');

app.get('/', (req, res) => {
  let firstname = req.query.firstname; //an untrusted input
  res.send('

Hello, ' + xssFilters.inHTMLData(firstname) + '!

'); }); app.listen(3000);
5. Preventing SQL Injection(SQLi) attacks

Passing unvalidated user input directly to a SQL statement is vulnerable to SQL injection attack. Consider the following example:


// SQL query vulnerable to SQLi
sql = "SELECT * FROM users WHERE username='" + username + "' AND password='" + password + "'";

// Execute the SQL statement
database.execute(sql)

Now suppose the user enters the following in the username field:

' OR '1'='1' --

The above SQL statement becomes:
sql = "SELECT * FROM users WHERE username='" + ' OR 1=1 -- + "' AND password='" + password + "'";
6. Secure cookies using cookie flags

XSS vulnerability in an application can be used to steal browser cookies. To prevent cookie stealing we can set the httpOnly flag of the cookie.

Additionally, we can tell the browser to send cookies only over HTTPS using the secure flag. secure : this attribute tells the browser to only send the cookie if the request is being sent over HTTPS. HttpOnly : this attribute is used to help prevent attacks such as cross-site scripting, since it does not allow the cookie to be accessed via JavaScript.

Example:
app.use(session({ 
 secret: ‘My super secret’, 
 cookie: { httpOnly: true, secure: true } 
}));
7. Preventing brute force and DoS attack

To prevent our site from overwhelming with a large number of requests, we need to put some kind of rate limiting to our API.

We can use the ratelimiter npm package to implement rate limiting. If you are using Express, the express-rate-limit middleware can be used as shown below:


const RateLimit = require('express-rate-limit');
 
const limiter = new RateLimit({
  windowMs: 15*60*1000, // 15 minutes 
  max: 100, // limit each IP to 100 requests per windowMs 
  delayMs: 0 // disable delaying — full speed until the max limit is  reached
});
 
// apply to all requests 
app.use(limiter);

8. Error Handling

Any error in the application should be handled gracefully by showing a custom error page to the user instead of showing stack trace in the error page thereby leaking sensitive infrastructure information like server info.

References:

[1] https://code.google.com/archive/p/browsersec/wikis/Main.wiki
[2] https://html5sec.org/
[3] https://docs.google.com/document/d/1vvszJ3EEwPlHXQ2-o7t3Pc4LLkeU6an1vhagLdtHk3Y/edit [4]https://medium.com/@rajapradhan08/best-practices-for-securing-node-js-web-applications-3b69909b2a2a