Series of articles: defence in depth part 6 of 7
Web browser
In the previous article, we discussed important security aspects of your infrastructure, focusing on the server side. In this article, we will discuss the challenges we face on the client side in general, and in browsers in particular.
Web browsers are an attractive target environment for delivering systems to users. It is easily accessible and requires no installation, the vast majority of users today have access to a modern web browser. For the user, compared to an application installation, it provides a well-defined environment in which to run applications.
The browser's closed environment provides protection against ransomware or other attacks that try to access your computer's resources. Mobile applications also provide protection in theory, but in practice they are often installed with access to many of the sensitive features and data found on the phone, such as microphone, camera, location and address book.
For developers, the languages, frameworks and tools for building web applications are improving, making it possible to deliver a system that today is virtually independent of the type of client the user has. However, from the application's perspective, the browser represents a difficult security challenge for us developers where we would like to highlight the following:
Browser plugins and extensions
Shared environment for many web applications of different origins
Gaps and variation in functionality across browsers
A dynamic runtime that can evaluate incoming code (javascript)
These are characteristics and prerequisites that are very important to understand as an application developer. In the case of a traditional application, from an application perspective, your environment is isolated in its own process with a static runtime. Although even for a traditional application you have similar security challenges and need to manage dependencies to third-party components and frameworks (such as Java and .NET), the problem is greater in a browser.
A secure server-side API is the key to a secure system. You can't add security to your clients for your API. An attacker can always attack your API without using the client at all.
Plugins
A modern, up-to-date browser without problematic plugins is the foundation of all web application security. If you have serious vulnerabilities here, there is virtually no protection for your web application and, in the worst case, no protection for your computer's resources. This places demands on your users that you may not control. How do you make sure your users don't install and use a vulnerable browser or plugin?
Modern browsers have a plugin model where they ask the user for permission to read or modify data at installation. Many users accept these permissions without giving much thought to what it means for your particular web application. We have no way of protecting our web application against an attacker who manages to get the user to install a plugin that has been given the right to proxy all traffic through the browser. Such an attacker can use our API with exactly the same rights as the user has.
We should use the security features that modern browsers provide. As a starting point, we support only those versions of browsers that are updated and supported by the manufacturer. Sometimes there may be market requirements and laws that dictate which browsers we must support, but we need to assess the risks and understand that we are compromising the level of security we can provide to our users.
Kasper Karlsson
We have seen many examples of vulnerable plugins over the years. Adobe's plugin for viewing PDF in Mozilla Firefox had a Universal XSS (UXSS) that allowed attacks on any web page that displayed PDF files. This was around 2007, so vulnerable plugins are not new!
Shared environment
A browser is a shared environment where web applications from different websites, with different origins, run together. In other words, it is not an isolated process in the same way as a traditional application. The basic protection to separate e.g. a banking website from unsafe websites with malware is called the Same Origin Policy (SOP).
SOP has long been implemented in all modern browsers and is such a basic protection that we often take it for granted. Perhaps it's something that application developers have never needed to understand in depth as it's always there. The principle that is important to understand is that websites are basically separated by: protocol, host and port. Understanding all the details fully is a challenge and is described in detail at https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
In our experience, it is a common mistake to open up the SOP by using a CORS configuration that opens to all HTTP verbs, regardless of their origin. This increases the risk of successful CSRF attacks.
It is important to understand that CORS is only a browser protection, not a "firewall" on the server side. If you use a BFF architecture as discussed in the previous article, you avoid the problem because all traffic comes from the same origin.
Browser shortcomings
Over time, there have also been flaws in browsers, sometimes even in the way they implement SOP. In addition to pure security bugs, inconsistent behaviour in different browsers is something that most people have had trouble dealing with at some point. Often it is harmless, such as how a certain element is rendered or supported. However, sometimes a change in behaviour causes serious problems that are difficult to detect as there are often not the resources to test in all browsers that need to be supported.
One example is how Cookie Policies have changed in the interpretation of Strict, Lax and None over the last two years. An updated standard has changed behaviour so that websites need to take into account which browser it is, in order for OAuth2/OIDC-based logins, among others, to continue to work. Not being able to log in can have serious consequences, security is also accessibility!
The problems of managing Cookie Policies are well described at e.g. https://docs.microsoft.com/en-us/aspnet/core/security/samesite?view=aspnetcore-3.1#test-apps-for-samesite-problems
Cookie Policies are a strong CSRF protection, but the fact that different browsers handle it differently means that today we still need to use protections that do not depend on browser version, e.g. "Double-Submit-Cookies" as we have discussed in the previous article on Sessions.
Both Google and Apple have clearly indicated in 2020 that they want to make it more difficult for third parties to track users between different sites, which affects, for example, solutions that use third-party cookies. See more at https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/
Dynamic runtime
Even if we manage everything we've highlighted so far, the dynamic runtime of javascripts remains a difficult challenge. You may have done everything right:
Built a secure API, with a secure infrastructure and environment, according to the principles of the previous articles
Used a secure browser, as we have covered so far in this article
However, you may still have a Cross-Site Scripting (XSS) vulnerability that allows an attacker to bypass access control and reach data that it should not have access to, for example.
An XSS means that an attacker can run scripts in the application, and thus gain the same rights to data as the user has. The vulnerability arises when the application renders, for example, insecure, incoming data as HTML. The solution is to properly output code the data for the context in which it is to be used.
The fact that our API has proper input validation is a first line of defence against XSS, but it is by no means sufficient. It is not possible in a real world scenario to completely validate the input of the API in such a way that we do not have XSS. The solution for the basic problem of XSS is always correct output encoding on the client.
Unfortunately, this is easier said than done. Even if we have a framework that supports the right output encoding for the right context (javascript, html or css) and doesn't force "eval constructs" on us, it is sometimes difficult to use the functions correctly and you might use "raw-html functions" to get specific formatting to render correctly. Note the similarity to an injection attack against the server, e.g. SQLi. As well as the link to DDSec that we covered in the previous article, which helps you identify domain boundaries and where you need output encoding and input validation.
It is important to use the protections available to reduce the risk of XSS and limit the damage of such attacks. Today, this is done by defining a Content Security Policy (CSP), which is supported in all modern browsers. It is, like CORS, an in-browser protection and does not add any security on the server side.
Note that it can be a challenge to write a strong CSP that is supported by all browsers. One tool that can help you find gaps in your CSP is the CSP Evaluator from Google: https://csp-evaluator.withgoogle.com/
Adrian Bjugård
I often see that you open up in your CSP to use components
that require e.g. unsafe-eval
. Instead, one should perhaps
think through your choice of component, or really make sure that you
use it safely.
I also see that you remove your CSP to simplify in
development work. Sometimes, unfortunately, this comes with
the production environment. Try to keep track of what your CSP looks like in production.
Maybe you can write a system test to verify that it exists
and does not contain a unsafe-eval
?
CSP is a security standard that allows the application to define a list of approved sources for the browser to fetch content and scripts from when it executes your application. You should implement a restrictive CSP for your application following the Least Privilege principle, by being explicit when defining your CSP and specifying only the necessary sources for each type of content used by the application. Aim for a system design where you only need to specify sources that you do not control in exceptional cases. The details of how to do this are described at https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
A strong CSP often has default-src
directive set at source only none
, which means that unless other directives specify source values, we fall back on none
. Then no scripts may be used in the application at all, regardless of their origin.
It is better to update your CSP frequently than to open up unnecessary sources from the start. So you need to understand the SOP to implement a good CSP.
Note that in a shared environment that you do not fully control, the source value 'self' means that scripts from the same origin as yourself can be run as part of your application.
Don't be fooled into thinking that a strong CSP is enough to protect your application against XSS. A CSP limits the attack surface when any part of the application is incorrectly decoded, it does not provide complete XSS protection.
It's a good idea to use the CSP specification's reporting mode to find out if you have any components that need customisation before implementing a CSP. However, taking care of CSP reports in a secure way is a challenge. They can be very numerous, contain XSS attacks, and it can be difficult in a large production system to assess whether any client is under attack.
In addition to XSS protection through CSP directives, there are also a number of other headers for other types of protection, including Referer policy and Feature policy.
A good tool to test which protections are active in your application is https://securityheaders.com/
Summary
So we have many protections for the user that we can use in a modern browser, which are part of an architecture where we build security in multiple layers ("Defense in Depth").
There is a trade-off to be made between accessibility, confidentiality and privacy. One example where it can be difficult to find a good balance is to meet requirements for analysis of user behaviour. There are many different tools, including "Tag Managers", which allow the detailed monitoring of a user in a very powerful way. This can be useful from an analytical perspective, but brings with it several security challenges:
The scripts that make this possible are included from a third party. Is this party trustworthy? Who guarantees that only your scripts can be linked in?
The script needs to be able to manipulate the DOM and insert code into your application. Often this is done through "eval constructs". What are the risks of opening up CSP with "unsafe-eval"?
The scripts, even if generated via a tool where the risk of mistakes is low, are often created outside the regular testing and quality process and run directly in production. Strong authentication and version control may not be in place for those writing these scripts. How do you ensure the quality of this code?
Difficult to control over time what data is stored with third parties, often the user of the tool is expected to hide what is sensitive instead of only allowing what is ok (i.e. black-listing instead of white-listing). Who checks compliance with the GDPR? The data may also be extremely sensitive as you may even store things that the user never intended to save to your back-end.
It is important to understand these characteristics and clarify the issues before using this type of service.
Web browsers are an attractive target environment, which for the vast majority of systems is a very good first choice. But it is important to understand its requirements and security model. Consider the choice of client type: for some applications with high security requirements, the browser may not be the best choice.
In the next article we will conclude and summarise the series.
Read the other parts of the article series