- Putting the Helmet on — Securing Your Express App
- By Dominik Kundel
- Translation from: The Gold Project
- This article is permalink: github.com/xitu/gold-m…
- Translator: lsvih
- Read by: Swants
Express is based on Node.js and is an excellent framework for building Web services. It is easy to learn and, thanks to the concept of middleware, can be easily configured and extended. Although there are various frameworks for creating Web applications, my first choice is always Express. However, using Express directly does not fully follow security best practices. Therefore, we need to use modules like helmet to improve the security of the application.
The deployment of
Before starting, make sure you have installed Node.js and NPM (or YARN). You can download and view the installation guide at the node.js website.
We’ll use a new project as an example, but you can also apply these features to existing projects.
Create a new project by running the following command from the command line:
mkdir secure-express-demo
cd secure-express-demo
npm init -y
Copy the code
Run the following command to install the Express module:
npm install express --save
Copy the code
Create a file named index.js in the secure-express-demo directory and add the following code:
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
app.get('/', (req, res) => {
res.send(`<h1>Hello World</h1>`);
});
app.listen(PORT, () => {
console.log(`Listening on http://localhost:${PORT}`);
});
Copy the code
Save the file and give it a trial run to see if it works. Run the following command to start the service:
node index.js
Copy the code
Go to http://localhost:3000 and you should see Hello World.
Check the Headers
Now let’s improve application security by adding and removing some HTTP headers. You can use some tools to check its headers, such as using curl to run the following command:
curl http://localhost:3000 --include
Copy the code
The –include flag allows it to print the HTTP headers of the response. If you don’t have Curl installed, you can use the Network panel of your most common browser developer tools instead.
You can see the following HTTP headers included in the received response:
HTTP/1.1 200 OK x-POWER-by: Express content-type: text/ HTML; charset=utf-8 Content-Length: 20 ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:36:10 GMT
Connection: keep-alive
Copy the code
In general, a header starting with X- is a non-standard header. Note the X-powered-by header, which exposes the framework you’re using. For the attacker, this reduces the cost of attack because they can only focus on the known vulnerabilities of the framework.
Put your helmet on
Let’s see what happens if we use a helmet now. Run the following command to install helmet:
npm install helmet --save
Copy the code
Add the Helmet middleware to your app. Modify index.js as follows:
const express = require('express');
const helmet = require('helmet');
const PORT = process.env.PORT || 3000;
const app = express();
app.use(helmet());
app.get('/', (req, res) => {
res.send(`<h1>Hello World</h1>`);
});
app.listen(PORT, () => {
console.log(`Listening on http://localhost:${PORT}`);
});
Copy the code
This uses the default configuration for a helmet. Now let’s see what it does. Restart the service and check the HTTP headers again by running the following command:
curl http://localhost:3000 --inspect
Copy the code
The new headers will look something like this:
HTTP/1.1 200 OK
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Type: text/html; charset=utf-8
Content-Length: 20
ETag: W/"14-SsoazAISF4H46953FT6rSL7/tvU"
Date: Wed, 01 Nov 2017 13:50:42 GMT
Connection: keep-alive
Copy the code
The first thing to celebrate is the absence of the X-powered-by header. But now we have a bunch of new headers, and what do they do?
X-DNS-Prefetch-Control
This header doesn’t do much to increase security. A value of off turns off the BROWSER’s DNS prefetch of the URL in the page. DNS prefetch can improve the performance of your site by increasing image loading speed by 5% or more, depending on the MDN description. However, enabling this feature can also cause caching problems when users visit the same web page multiple times.
If you know this, please leave a message
The default value is off, so if you want to make a helmet better, you can enable DNS prefetch by passing {dnsPrefetchControl: {allow: true}} when calling helmet().
X-Frame-Options
X-frame-options allows you to control whether pages can be loaded in frames such as < Frame />, , or . Unless you really need to open the page in these ways, disable it completely with the following configuration:
app.use(helmet({
frameguard: {
action: 'deny'}}));Copy the code
All modern browsers support X-frame-options. You can also control it with content security policies, described later.
Strict-Transport-Security
Also known as HSTS (Strictly Secure HTTP Transport), it is used to ensure that protocol degradation (back to HTTP) does not occur when accessing HTTPS sites. If a user once visits an HTTPS site with this header, the browser ensures that future visits to the site are not allowed to communicate using HTTP. This feature helps prevent man-in-the-middle attacks.
Sometimes you can see this in action when you try to access a portal like https://google.com while using public WiFi. WiFi tries to redirect you to their portal, but you’ve visited Google.com over HTTPS, and it has the strict-transport-Security header, so the browser will block redirects.
You can visit the MDN or OWASP wiki for more information.
X-Download-Options
This header is only used to protect your application from bugs in older versions of IE. In general, if you deploy HTTP files that cannot be trusted for download, the user can open the files directly (without having to save them to the hard disk first) and execute them directly in the context of your app. This header ensures that the user must download the file locally before accessing it, thus preventing it from being executed in the context of your app.
You can visit the Helmet documentation and MSDN blog for more information.
X-Content-Type-Options
Instead of using the content-type sent by the server to determine the file Type, some browsers use “MIME sniffing” to determine the Content Type based on the file Content and execute the file based on that.
Suppose you provide a way to upload images to a web page, but an attacker uploads some image file containing HTML code, and if the browser uses MIME sniffing to execute it as HTML code, the attacker can perform a successful XSS attack.
This MIME sniffing can be disabled by setting the headers to nosniff.
X-XSS-Protection
This header enables basic XSS defense in the user’s browser. It can’t protect against all XSS attacks, but it can protect against basic XSS. For example, if the browser detects that the query string contains something like a
Upgrade your helmet
These are just the default Settings provided by helmet. Among other things, it lets you set headers like Expect-ct, public-key-pins, cache-control, and referrer-policy. You can find out more about this in the helmet helmet documentation.
Protect your web page from unexpected content
Cross-site scripting is an unstoppable threat to Web applications. If an attacker can inject and run code into your application, the consequences could be a nightmare for you and your users. There is a solution that can try to prevent unexpected code from running on your web pages: CSP (Content Security Policy).
CSP allows you to set up a set of rules that define the sources from which your pages can load resources. Any resource that violates the rules will be automatically blocked by the browser.
You can specify this rule by changing the Content-security-policy HTTP header, or you can use the Meta tag if you cannot change the header.
The header looks something like this:
Content-Security-Policy: default-src 'none';
script-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
style-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==';
img-src 'self';
font-src 'nonce-XQY ZwBUm/WV9iQ3PwARLw==' fonts.gstatic.com;
object-src 'none';
block-all-mixed-content;
frame-ancestors 'none';
Copy the code
In this example, you can see that we only allow font access from our own domain name or from Google Fonts’ fonts.gstatic.com; Only images in this domain can be loaded; Only scripts and style files that do not specify a source are allowed to be loaded, but must contain a specified nonce value. The nonce value needs to be specified as follows:
<script src="myscript.js" nonce="XQY ZwBUm/WV9iQ3PwARLw=="></script>
<link rel="stylesheet" href="mystyles.css" nonce="XQY ZwBUm/WV9iQ3PwARLw==" />
Copy the code
When the browser receives the HTML, it clears all the nonce values to be safe. Other scripts cannot get the values and therefore cannot add them to the web page.
You can also disable all HTTP mixed content and all elements contained in HTTPS pages, and disable all content that is not images, stylesheets, and scripts by setting default-src to None. In addition, you can disable iframe with frame-ancestors.
You can write the headers yourself manually, but fortunately there are already a number of CSP solutions available in Express. Helmet supports CSP, but nonce needs to be generated by you. I personally use a module called express-cSP-header for this purpose.
Install and run express-cSP-Header:
npm install express-csp-header --save
Copy the code
Add and modify the following for your index.js to enable CSP:
const express = require('express');
const helmet = require('helmet');
const csp = require('express-csp-header');
const PORT = process.env.PORT || 3000;
const app = express();
const cspMiddleware = csp({
policies: {
'default-src': [csp.NONE],
'script-src': [csp.NONCE],
'style-src': [csp.NONCE],
'img-src': [csp.SELF],
'font-src': [csp.NONCE, 'fonts.gstatic.com'].'object-src': [csp.NONE],
'block-all-mixed-content': true.'frame-ancestors': [csp.NONE]
}
});
app.use(helmet());
app.use(cspMiddleware);
app.get('/', (req, res) => {
res.send(`
<h1>Hello World</h1>
<style nonce=${req.nonce}>
.blue { background: cornflowerblue; color: white; }
</style>
<p class="blue">This should have a blue background because of the loaded styles</p>
<style>
.red { background: maroon; color: white; }
</style>
<p class="red">This should not have a red background, the styles are not loaded because of the missing nonce.</p>
`);
});
app.listen(PORT, () => {
console.log(`Listening on http://localhost:${PORT}`);
});
Copy the code
Restart the service, go to http://localhost:3000, and you’ll see a paragraph with a blue background because the associated style has successfully loaded. The other paragraph has no style because its style lacks the nonce value.
The CSP Header also allows you to set the URL for reporting violations, and you can also collect relevant data by enabling report mode only before strictly enabling CSP.
You Can check out more information at MDN CSP Introduction and check out CSP compatibility at the Can I Use website. Most major browsers support this feature.
conclusion
Unfortunately, there is no one-size fits all solution when it comes to security, and new vulnerabilities emerge all the time. However, you can easily set these HTTP headers in your Web application, significantly improving the security of your application, so why not? If you want to learn more about best practices for improving security with HTTP headers, check out SecurityHeaders.
If you want to learn more about web Security best practices, visit the Open Web Applications Security Project (OWASP), which covers a wide range of topics and useful resources.
Please feel free to contact me if you have any questions or other tools for promoting node.js Web applications:
- Twitter: @ dkundel
- Email:[email protected]
- Making: dkundel
Diggings translation project is a community for translating quality Internet technical articles from diggings English sharing articles. The content covers the fields of Android, iOS, front end, back end, blockchain, products, design, artificial intelligence and so on. For more high-quality translations, please keep paying attention to The Translation Project, official weibo and zhihu column.