Establishing a reasonable amount of security for a website is a non-trivial unending job. Here are some notes and resources that I find helpful.
Web Server Config
Start with TLS
All public-facing websites need to be protected by Transport Layer Security (TLS). TLS is a technology which allows web traffic to be encrypted and this encryption helps to protect the privacy and security of the users of a site.
Deploying a site with TLS can be accomplished in a free and automated fashion. Services like Let’s Encrypt and the issuance features of large cloud providers (for example AWS Certificate Manager) encourage more secure defaults and automatic management. For more information, check out the Let’s Encrypt Getting Started Guide or your hosting provider’s documentation.
Audit Your TLS Setup With SSL Labs
Even with automated setup, getting the right settings for your site can be a bit tricky, so it’s important to audit every new site while you’re standing it up. Once a site is available on the Internet, you’ll want to check it with the Qualys SSL Labs Server Test or a similar service.
Adjust your server’s config as needed until your site gets a score of “A” or better.
For information about the adjustments you need to make to improve your results, check out their SSL and TLS Deployment Best Practices.
Selecting the right cipher suites for your site can be a challenge. The key question you need to answer for your site is: “what browsers / clients / devices do I need to support?” Your frontend engineering design decisions may already have decided this for you – for example if you’ve decided to require all your users to have a browser that supports ES6 you can rule out the need to support a ton of ancient browser crypto. Use the Server Test result page’s handshake simulator section to determine the weakest cipher suite you want to support, and remove anything weaker.
- Alternatively, the Mozilla Security/Server Side TLS Wiki Page maintains a curated list of cipher suite recommendations – you just choose between “Modern” or “Intermediate”.
- ciphersuite.info is a pretty good resource for getting information about particular cipher suites and their current status
- If you’re lost, just use Mozilla’s Intermediate tier.
Configure STS, CSP, Etc.
For the next portion of the web server config you’ll want to start with an audit then iterate on your settings.
Audit With The Mozilla Observatory
The excellent Mozilla Observatory is like the SSL Labs Server Test – it is a free service which helps you audit aspects of your site and makes helpful recommendations for improvement. It will guide you through the items described below (and more).
HTTP Strict Transport Security (HSTS) is an HTTP header that notifies [browsers] to only connect to a given site over HTTPS, even if the scheme chosen was HTTP. Browsers that have had HSTS set for a given site will transparently upgrade all requests to HTTPS. HSTS also tells the browser to treat TLS and certificate-related errors more strictly by disabling the ability for users to bypass the error page.
A very secure HSTS header looks like this:
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
…but make sure you understand the implications. If you turn on this policy, clients that visit your site will not be able to use plain HTTP with it for two years. Additionally your site may be added to the HSTS preload list. So do use this header but make sure you’re ready before you activate it in production.
XSS, or Cross-site scripting is an important category of vulnerability that websites need to guard against. Again, from wikipedia:
XSS attacks enable attackers to inject client-side scripts into web pages viewed by other users. […] XSS effects vary in range from petty nuisance to significant security risk, depending on the sensitivity of the data handled by the vulnerable site and the nature of any security mitigation implemented by the site’s owner network.
One way to help protect against XSS is to use a content security policy. Now again from the Mozilla web security guidelines:
Content Security Policy (CSP) is an HTTP header that allows site operators fine-grained control over where resources on their site can be loaded from. The use of this header is the best method to prevent cross-site scripting (XSS) vulnerabilities.
Getting your CSP header right can be hard, particularly if your site depends on a lot of external resources.
Here’s an example CSP payload:
default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self'; connect-src 'self' https://api.example.com; form-action 'self'; media-src 'self' https://sometube.example.com; object-src https://sometube.example.com; font-src 'self'; frame-src 'none'; manifest-src 'none'; worker-src 'self'; base-uri 'none'; frame-ancestors 'none'; upgrade-insecure-requests; require-trusted-types-for 'script';
'none' (including their single-quote characters) are keywords with special meaning, semi-colons (
;) are required between directives. The table below describes each directive.
|Directive||What It Does|
||this sets a very strict secure default for the other
||limits the src on
||limits CSS hrefs and other loading to just this site|
||limits AJAX requests to this site and one external API|
||limits the URIs which
||limits web font src and other URIs to just this site|
||disables frame elements. we’re not using
||disables loading app manifests (
||limits the URI on Worker, SharedWorker, or ServiceWorker to just this site|
||disables setting a new base URI using a
||prevents external sites from embedding this site via
||instructs the browser to upgrade any request that would normally be plain http to the equivalent https request|
||“locks down DOM XSS injection sinks”… says google|
Obviously many sites will have very different CSPs. You should read Mozilla’s very helpful Content-Security-Policy directive documentation and write a policy that meets your needs. Then test it with the Google CSP Evaluator or some other testing tool.
See also: https://content-security-policy.com/
Because it can be a real chore to write an accurate & helpful CSP for an existing site, there is a CSP mechanism that you can use to define a report-only policy and a URI which CSP violations will be reported to.
One of the perils facing you and your visitors is Clickjacking. An attacker can embed your website in a hidden frame on their website and accomplish bad things. As wikipedia explains:
Classic clickjacking refers to when an attacker uses hidden layers on web pages to manipulate the actions a user’s cursor does, resulting in the user being misled about what truly is being clicked on.
A user might receive an email with a link to a video about a news item, but another webpage, say a product page on Amazon, can be “hidden” on top or underneath the “PLAY” button of the news video. The user tries to “play” the video but actually “buys” the product from Amazon. The hacker can only send a single click, so they rely on the fact that the visitor is both logged into Amazon.com and has 1-click ordering enabled.
While technical implementation of these attacks may be challenging due to cross-browser incompatibilities, a number of tools such as BeEF or Metasploit Project offer almost fully automated exploitation of clients on vulnerable websites. Clickjacking may be facilitated by - or may facilitate - other web attacks, such as XSS.
You should work to prevent clickjacking by preventing your site from being embedded in other sites. You can start to do this with the
A good X-Frame-Options header looks like this:
Normally when you are browsing the web and you click a link to a new site, your browser tells the new site the URL of the page you are coming from. For example, imagine you’re browsing
https://example.com/main.html and you click a link to
https://other.example.net/other.html. When your browser is requesting the new page it will send along this HTTP header:
Referer: https://example.com/main.html. This doesn’t just apply to links, it also applies to inline resources like images. The people running
other.example.net will have a record of every link followed from
example.com and every image that
example.com is loading from
other.example.net. When these assets are used as web beacons, large scale user tracking is possible and this opens the door for several security issues.
Referrer-Policy is an HTTP response header which typically instructs a browser to limit the amount of referrer information that should be sent to external servers. So returning to our example, the people running
example.com could set a Referrer Policy on their server to prevent external sites like
other.example.net from getting that referrer data.
There are quite a few options, but I’d recommend one of these:
no-referrer- Tells the browser not to send any referrer information for links or embedded content on this site.
strict-origin-when-cross-origin- Tells the browser to send full referrer information for internal links within this site and for external links it only sends a “sanitized” referrer (for example it sends
https://app.example.com/instead of the more risky-to-share
https://app.example.com/user/123/bookmarks). Importantly it also tells the browser not to send any referrer information over a non-https connection.
strict-origin- Tells the browser to send full referrer information for internal links within this site and no information to external sites. Importantly it also tells the browser not to send any referrer information over a non-https connection.
The referrer-policy header supports fallbacks. The right-most mode that is supported by the browser will be used. For example this is a moderately privacy protective policy with good security characteristics:
Referrer-Policy: no-referrer, strict-origin-when-cross-origin
App Config: CSRF Mitigation, Cookie Security, CORS
These next items involve changes to your web application code or framework settings.
Cross-Site Request Forgery Mitigation
Cross-site request forgery (CSRF) is a class of attacks wherein an attacker tricks your browser into making a request to another service, often using your account to do it. Yet again the Mozilla web security guidelines have a great example:
Cross-site request forgeries are a class of attacks where unauthorised commands are transmitted to a website from a trusted user. Because they inherit the users cookies (and hence session information), they appear to be validly issued commands. A CSRF attack might look like this:
<!-- Attempt to delete a user's account --> <img src="https://accounts.mozilla.org/management/delete?confirm=true">
Wikipedia’s example is also helpful:
When accessing the attack link to the local uTorrent application at localhost:8080, the browser would also always automatically send any existing cookies for that domain. This general property of web browsers enables CSRF attacks to exploit their targeted vulnerabilities and execute hostile actions as long as the user is logged into the target website (in this example, the local uTorrent web interface) at the time of the attack.
For mitigation, the OWASP Cross-Site Request Forgery (CSRF) Prevention Cheat Sheet is an excellent reference, here is the short list of principles from that (be sure to read the actual content of the cheat sheet):
Check if your framework has built-in CSRF protection and use it
- If framework does not have built-in CSRF protection add CSRF tokens to all state changing requests (requests that cause actions on the site) and validate them on backend
Always use SameSite Cookie Attribute for session cookies
Implement at least one mitigation from Defense in Depth Mitigations section
- Use custom request headers
- Verify the origin with standard headers
- Use double submit cookies
Consider implementing user interaction based protection for highly sensitive operations
Remember that any Cross-Site Scripting (XSS) can be used to defeat all CSRF mitigation techniques!
- See the OWASP XSS Prevention Cheat Sheet for detailed guidance on how to prevent XSS flaws.
Do not use GET requests for state changing operations.
- If for any reason you do it, you have to also protect those resources against CSRF
I’ve tended to focus on making sure the framework CSRF protection is properly configured and is operating, that the origin is always being verified, and that HTTP
GET requests never affect stored data.
Cookie Security Attributes
Typically HTTP cookies are sent to a browser by a website’s server software, app framework, or app code. After a user successfully supplies their login information to a website, the site will typically set a cookie in the user’s browser which enables them to stay “logged-in” to the site and make subsequent requests which are pre-authenticated as the user. An HTTP response header setting a cookie looks like this:
Set-Cookie: userid=1234567; Path=/; Domain=.example.com
SameSite=Strict- indicates that the cookie should only be sent by the browser when the site is directly navigated to, not merely when an asset is being loaded. This makes the cookie the opposite of a web beacon. It is also an excellent CSRF mitigation.
Secure- indicates that the cookie should only be sent by the browser over an HTTPS connection. you should always have this enabled!
Cross-origin Resource Sharing
A web app, for example
https://example.com/app, is normally restricted in what AJAX calls it can make. The Same-origin policy of all major browsers ensures that it can only access things under
https://example.com/. Sometimes you need to make AJAX calls to a private API at a different origin or to make use of the rich ecosystem of public web APIs. Cross-origin Resource Sharing (or CORS) is a way for a web API or service to specify that it provides API resources to browsers.
A CORS header can allow any origin or it can specify a list of allowed origins. An allow-all CORS header looks like this:
A more restrictive header looks like this:
https://enable-cors.org/ may have some useful information about enabling CORS in your environment.