10 Easy Web Application Security Measures You Can Take Today
This article explains 10 easy defense-in-depth mechanisms that you can enable for your website right now!
It's surprising that you can find quick and easy solutions in a complex and nuanced field such as web application security. But its true! Here are 10 easy measures you can take to improve the security of your web application (In no particular order!)
HTTP Security Headers
One of the easiest ways to improve your application's security is to enable the built-in protections browser developers ship but keep disabled by default. These can be enabled by certain HTTP Response headers. Let's discuss a few of them among other quick and easy techniques.
HTTP-Strict-Transport-Security (HSTS)
One of these headers is the so called HTTP-Strict-Transport-Security (HSTS) header which basically enhances the security of TLS on your website. It comes with a variety of improvements, such as automatically upgrading http:// links to your website to https://.
The catch? This will only work if your user already visited your website before. This is called the Trust On First Use principle, or in short: TOFU. However, there is a solution for this. The HSTS preload list. To check the eligibility of your online presence to be included in the list, you can visit the official preload list website.
If you are not on the preload list, the value that the Open Web Application Security Project (OWASP) recommends is Strict-Transport-Security: max-age=31536000 ; includeSubDomains
.
X-Content-Type-Options
Did you know that there aren't any response headers that are required to be sent in order for an HTTP response to be considered valid according to the specification? That begs the question: what does a browser do, if we omit an important response header such as the Content-Type? Will it assume that we intended to send a plain text document? A binary file? HTML code?
The answer is not that simple. However, the authors of HTTP's specification have thought of a slightly flawed solution. Here is their recommended approach.
If a Content-Type header field is not present, the recipient MAY either assume a media type of "application/octet-stream" (...) or examine the data to determine its type.
On the first glance, this sounds like a pretty good idea. But if you think a bit further, what happens, if you let a user upload a profile image to your web server and it is then served without a specified Content-Type header? Users may upload files containing HTML code and the browser will determine that its Content-Type header should probably say text/html
. In other words, you involuntarily introduced an XSS vulnerability.
Cue the X-Content-Type-Options
header. Configuring it should be relatively straightforward, as it only has one valid argument: nosniff
. This will disable the automatic detection of the content type and prevent any HTML or JavaScript code from being rendered or executed.
Referrer-Policy
This header may be a bit confusing due to its correct spelling. Let me explain. According to Wikipedia, In 1996 Computer Scientist Phillip Halam Baker's proposal for the "Referer" Header was incorporated into the HTTP/1.0 standard. However, the actual name of the header should have been "Referrer", which would be the correct spelling. The question is, why was its name spelled incorrectly? Well.
document co-author Roy Fielding remarked in March 1995 that "neither one (referer or referrer) is understood by" the standard Unix spell checker of the period.
I don't know about you, but for me this sounds convincing. So what does the Referer header actually do and why is there a dedicated policy required? Whenever you click a link on a website, your browser will automatically add a Referer header to your request, which will then be sent to the server to which that link points. If you are, for example on https://example.com/users/alice
and click on a link pointing to https://evil.com/
, the evil.com website will be informed that you were previously visiting alice's profile.
This is far from ideal and may even leak sensitive information, such as Oauth tokens to a third party website when the right conditions are met. Luckily, you can prevent that from happening with a Referrer-Policy. Not only is it spelled correctly, it also offers you a fine-grained control over if and where any data might eventually be sent. So let's take a look. According to mdn, this header has a variety of modes.
no-referrer
: No referer header is ever being sent.no-referrer-when-downgrade
: This will send the full header, except if the destination has a weaker security level, i.e. HTTP->HTTPS. However, it will still be sent between two HTTP websites.origin
: Only the origin (protocol://host:port) will be sentorigin-when-cross-origin
: This will only send the full URL, if the destination is on the same origin as the source url.same-origin
: The whole referer header will be omitted, when the destination address is of another originstrict-origin
: This will only send the referer header to same-origin URLs with the same protocol security level, e.g. HTTPS -> HTTPS.strict-origin-when-cross-origin
: This is the default behaviour. This will only send the full referer header to any site if the security level stays the same.unsafe-url
: This will send the referer header anywhere, regardless or the origin or protocol.
You are free to choose which ever option you deem suitable for your site. However, OWASP recommends using the value no-referrer
.
Content-Security-Policy (CSP)
The full extend of this header goes far beyond the scope of this article, but in short, this header is like a swiss army knife for client side security. It allows you to define from which locations resources on your website are allowed to be loaded. If you want to harden your web application and have a safety net for a hypothetical XSS vulnerability that may arise, you could use CSP for example to instruct browsers to only load scripts from your own site. The same works with stylesheets, images and any other type of content.
Not only does it allow you to define a whitelist of hosts, you can also use so called nonces and hashes to allow for inline-scripts. A nonce is a number that is unique for each response. It has to be present in both your CSP header and the script you want to execute. A hash on the other hand only needs to be present in your CSP header. What you do is hash the content of a script on your page and put the resulting hash into your Content-Security-Policy header. A web browser will then go ahead and check for each script on your webpage, whether or not a corresponding hash exists in the CSP header. If there is no matching hash, the script will not execute.
Again, mdn does have a pretty handy guide for this header where you may get some inspiration on what may be the best option for your site. Make sure to use Google's CSP evaluator to assess the security of your policy once you are done.
There will be no mention of OWASP's recommendation for this one, due to two reasons. First of all, it contains a value, that may still allow the execution of unsafe scripts. Additionally, a Content-Security-Policy should be tailored to your specific application and your needs. Therefore, this is not a simple one-click opt-in option. Instead this needs some thought and planning. In order not to break anything, you can use the Content-Security-Policy-Report-Only
header instead of the Content-Security-Policy
header. This will report any violations, but not actually enforce the policy. More on that on the mdn page.
Cookie Security
Not only can you activate certain Security Headers for your site, you can also enable some secure attributes on your cookies. Below are the configuration options that you have.
The `Secure` Attribute
This will ensure, that cookies are only sent over a secure channel, such as an HTTPS request. This prevents Man in the Middle (MITM) attacks from exposing your cookie.
The `HttpOnly` Attribute
In JavaScript there is the document.cookie
property, which contains the users cookies for your website. If there is an XSS attack, an attacker could simply read that value and suddenly has access to your session cookies and can take over your account. It is therefore mandatory to put that attribute onto your session cookies. However, there is a big issue: Usually an attacker does not need to know your cookie value.
Why not? Well, since the attacker is apparently already in a position where cookies can be read with JavaScript, they can also just issue requests with it. The session cookies are sent along every request anyway, so all the attacker needs to do is write a malicious script, that may read and append a CSRF token to the request, and carry out the malicious action, such as changing the email address directly. Therefore, this attribute is not foolproof, but it may prevent some sensitive data from being leaked nonetheless.
The `domain` attribute
This attribute specifies for which domain the cookie is valid. If unset, it defaults to the current hostname without subdomains. However, if it is specified, subdomains are also enabled. Therefore you need to be careful that you don't introduce a wider scope than initially intended.
The `path` attribute
This cookie attribute lets you specify the path to which the cookie is allowed to be sent. If the path attribute contains the path /blog
the cookie may not be sent when you visit the path /account
. Like the domain attribute, this allows for granular control to where cookies are being sent and allows you to avoid unnecessary exposure of them.
The `expires` attribute
You set the expiration date of a cookie using expires
. This allows you to automatically delete any cookies after a certain amount of time. However, you need to ensure, that this is also the case on the server side.
The `SameSite` attribute
The introduction of this attribute is a real game changer in regards to all kinds of client-side vulnerabilities. Browsers like Google Chrome made the SameSite attribute behaviour the default behaviour for all cookies, except if the developers explicitly opt out (with some slight differences). The attribute knows three different modes.
none
: this means that any website can send a request, such as a post request to your website and browsers will happily include the cookies in said request. This is one of the main problems in Cross-Site Request Forgery (CSRF) attacks. It is usually not recommended to leave this asnone
.Lax
: this is the ideal balance between usability and security when it comes to the SameSite attribute. This will allow cookies to be sent when the request triggers a top level navigation, such as clicking a link on a third party website.Strict
: This does not allow cookies to be sent from any request initiated from a third party website. This also means that, if you were to apply this to a session cookie, you had to log in again to the website each time you click a link to visit it from an external website.
Therefore the ideal solution should be setting this attribute to Lax. The web.dev website has a great article about this topic.
Conclusion
These are some security measures that are quick and easy to implement. However, there are a few more we haven't talked about yet, such as Cookie Prefixes and some newer HTTP headers. If you want to follow along and get some more quick and easy tips, as well as some more detailed information about Web Application Security, we would like to invite you to subscribe to our newsletter by clicking the subscribe button, but of course it's up to you whether or not you want to receive emails from us.