This past February the internet became acutely aware of the dangers of trusting third party libraries when the popular Browsealoud service was compromised and distributed a malicious cryptominer. Browsealoud provides screen reading (text-to-speech) and other web services to enhance the accessibility of websites and is used extensively by government organizations to meet accessibility requirements.
Websites using the Browsealoud libraries were infected with a malicious cryptominer known as Coinhive, a program that allows users to ‘mine’ Monero a cryptocurrency similar to Bitcoin. The malware stole the processing power of the user’s device to then mine cryptocurrencies.
Although the use of third party libraries and services is ubiquitous in today’s cloud hosted world, the explicit trust given to third parties always increases an application’s attack surface.
Although the nature of the attack is subtly different, the impacts of a compromised CDN are identical to any Cross-Site Scripting (XSS) Vulnerability.
This is why security researchers place such an emphasis on ensuring that TLS is enabled for all resources, not just the content served by the primary domain. Loading a single script over unencrypted HTTP makes a website vulnerable to man-in-the-middle (MitM) attacks. However, just because a resource was loaded over HTTPS does not prove that it has not been tampered with, it only protects against tampering during transit. The script could have been maliciously altered prior to transit, as was the case in the Browsealoud story.
The straightforward and most secure option to protect against a compromised CDN unknowingly serving malicious content is to simply host the script yourself. Although you don’t get the distributed caching benefits, web browsers still cache static content after the first load, so the performance penalty may be acceptable given the improved security posture. Since integrating the Browsealoud services into your website is a simple matter of sourcing a single script file (ba.js), one might conclude that this is a reasonable option. Unfortunately, the first line of the ba.js script warns against this.
/* [Warning] Do not copy or self host this file, you will not be supported */
/* Browsealoud Plus v2.5.0 (13-09-2017) */
SUBRESOURCE INTEGRITY (SRI)
Over the past couple of years, a new option has emerged which could effectively mitigate selected attack vectors by ensuring the integrity of externally sourced libraries.
Subresource Integrity (SRI) is a web browser feature that was proposed by the W3C to validate assets provided by CDNs. Websites can specify the cryptographic hash of a given third-party resource in the integrity attribute of any associated script and link tags.
An example configuration looks like:
Chrome, Firefox, and Opera have supported SRI since April 2016 but, unfortunately, Microsoft (IE, Edge) does not support for SRI at the present time. Consequently, self hosting third party libraries remains the most secure option until there is wider adoption.
As Troy Hunt pointed out, the Browsealoud scripts are not a versioned, static, resource. This is why they warn that you shouldn’t host the ba.js script yourself. It is software as a service, it is meant to change. So there are no guarantees that it’s hash will be constant. This is not the scenario SRI was designed to protect against.
Furthermore, even if ba.js were a statically versioned resource, SRI would still be an insufficient security protection. This is where other security analysts discontinued their research, but there is more to the story that needs to be told.
To use the Browsealoud service, a website embeds the ba.js script in their page which, when invoked, provides the user with text-to-speech accessibility features in the form of a floating toolbar.
An abbreviated list of the dependencies, scripts and resources loaded by ba.js follows:
Content Security Policy (CSP)
This is where Content Security Policy (CSP) plays an important role. By itself, SRI provides the ability to selectively validate the integrity of individual scripts. However, scripts that are loaded without the integrity attribute set will execute, as normal, without integrity controls. Thankfully, CSP can prevent this. By default, CSP instructs the browser to only allow execution of unobtrusive scripts from the same origin. CSP disallows the execution of scripts from other sources including inline script tags, dynamically loaded scripts, and content that would be executed by dangerous functions such as eval().
CSP also supports configuration options that allow third-party scripts, so long as their integrity is protected by SRI or marked with a nonce. To enable execution of third-party scripts and stylesheets that are protected by SRI, configure CSP with the "require-sri-for script style” directive. SRI and CSP are complementary technologies that can be used together to prevent execution of externally hosted content unless it’s hash is specifically whitelisted and integrity verified.
In context with CDN hosted static libraries, this is a very secure configuration. Unfortunately, this won't work for the Browsealoud service because it loads scripts at runtime. From a security perspective, this is an excellent example of how CSP enhances the security of your site by preventing the execution of dynamically loaded content. However, if you have accessibility requirements and rely on the Browsealoud for text-to-speech services, further modifications to the CSP policy must be made.
The 'strict-dynamic' CSP directive can be used to allow execution of dynamically loaded scripts, so long as they were loaded from an explicitly trusted source such as a script which is protected with SRI. Combined with the ‘require-sri-for’ directive, it is possible to selectively permit resources, and all subsequent dynamic content, without allowing arbitrary execution from unknown sources. For sites with numerous CDN hosted scripts and dependencies, the strict-dynamic directive is easier to manage than lengthy source whitelists. An example policy looks like:
Content-Security-Policy: script-src 'require-sri-for script style' 'strict-dynamic'
Although the permissive CSP policy described above is certainly more secure than using SRI by itself, it does not mitigate all security concerns. For one thing, this configuration does not prevent attacks against the dynamic content. Even if the integrity of ba.js were validated with SRI and external scripts were disallowed by CSP except those loaded by ba.js, the attacker could simply inject malicious code into a dynamic resource. As a result, protecting against a compromised service like Browsealoud is significantly harder than one might expect.
Dynamic Audio & Simplified HTML
POST /SpeechServices/index.html HTTP/1.1
HTTP/1.1 200 200
Cache-Control: no-cache, no-store, must-revalidate, max-age=0
Because dynamic web service content, such as audio streams, cannot be protected by CSP or SRI, the web application must explicitly trust the security of the third party. In the event the the service is compromised, attackers could modify the response to point to a different MP3 stream and inject arbitrary audio into all client browsers. This would not be the first appearance of Rick Astley to the security stage. The potential impact of this threat poses risks that are perhaps greater than a malicious cryptominer.
Unfortunately the difficulty securing the Browsealoud service does not stop here. Browsealoud also provides a handy feature to simplify a webpage to make it more accessible. By clicking on the “simplify” button, the HTML of the page body is sent within a POST request to babm.texthelp.com, analyzed and a simpler plaintext version is returned as a JSON object. If this third-party host was compromised, arbitrary HTML or script tags could be included in the response, defacing the page or resulting in an XSS style code execution vulnerability.
SRI and CSP are admirable technologies that can protect users against insecure CDNs when dealing with static libraries. However, when software is provided as a service and content is dynamically generated and loaded at runtime, such as is the case with Browsealoud, SRI and CSP are not enough. Ultimately, applications that rely on software a service, must explicitly trust the provider and accept the inherited risk.