Breaking Down the MarioNet Service Worker Attack? Can Your Website Be Compromised?
Recently, a group of academic researchers gained a lot of attention by publishing a paper about a potential security issue related to service workers. They call it MarioNet.
This has led to many in the media referencing this paper and possibly igniting a false sense of trouble for service workers and raising concern over service worker security.
The research paper describes a potential case or scenario where the use of service worker could be compromised to create a botnet to do malicious work. Should you be concerned?
Is the attack describe legitimate, or is it just a precautionary exercise to demonstrate a scenario that needs to be guarded against?
It is important to cover this topic and review some of the security features designed into the service worker specification. I think every course I have taught and every client I have consulted on progressive web applications have touched on service worker security concerns.
"In general, the highly dynamic nature of JavaScript, the lack of mechanisms for informing users about the implemented functionality, and the instant execution of script code, which does not leave room for extensive security checks before invocation, are facilitators for malicious or unwanted in-browser code execution."
Its important to understand how service workers are restrained and controlled so you and other business decision makers, chief technology officers and others can rest assured about potential vulnerabilities.
Unfortunately, this paper does not quite understand service workers, what is part of the service worker specifications and what has actually been implemented by different browsers.
It also does not account for security precautions taken by different browsers, or how the service workers specifications actually isolates service worker activity between domains to keep your sight in your user experience safe.
The actual attack, as described in the paper, relies on a specification that is an early draft, PeriodicSync. This concept is not yet implemented by any browser.
I don't think the authors may have been trying to reference the background sync API, which again is only implemented by Chrome at this point. Background sync cannot be externally triggered through a service worker.
- How does Service Worker Scope and Isolation Work?
- What is the actual service worker life cycle?
- What does this mean for you and your website?
- What can you do to guard against the potential issue of this sort of attack?
- And is this attack a problem for your actual business data and business security? Or is it isolated to your customers, period?
- And what are some ways that your sight might get compromised, too, be vulnerable to this sort of attack?
The MarioNet Attack
The premise of the MarioNet is malicious code, specifically a malicious service worker could be registered to execute code on a client system without the user's knowledge.
The attack assumes a service worker can be triggered to execute remotely, on-demand, without any visible user notification. It also assumes a service worker could be registered for a third party website.
The paper outlines 4 scenarios it considers to be sources of a potential attack:
- the website registers a benign service worker that includes untrusted dynamic third-party scripts, which in turn possibly load malicious code;
- the website includes third-party libraries,1 one of which can turn rogue or be compromised, and then divert the user to a new tab (e.g., using popunders or clickjacking) where it can register its own serviceworker bound to a third-party domain;
- the website is compromised and attackers plant their malicious JavaScript code directly into the page, thus registering their malicious service worker—a scenario that we see quite often in recent years; or
- the website includes iframes with dynamic content, which are typically auctioned at real-time and loaded with content from third parties.
To execute this style of attack a service worker would need to be registered from an external domain from the original page's domain. This means a website could have multiple service workers registered and executing in parallel.
For the record, you can't register a service worker from a third party domain, even from an iFrame.
The attack leverages a web specification called the PeriodicSync API to trigger the service worker to execute on demand.
What is PeriodicSync?
Right now it is nothing more than an idea. It is an early draft of a potential API specification. With that being said, not much has been put into writing, it more at a discussion level.
Go read the specification, oh wait, there isn't one.
The purpose of the API is to allow a website to trigger a background update to ensure cached resources are up to date on the client device.
You can think about this as being something similar to how your phone updates apps for you in the background. This saves you from wasting time manually updating your apps. They are just always up to date.
Right now a website could sort of stay up to date with the latest code version, but it is complicated. It requires a lot of cache invalidation management. And the site would not be updated until a web page within the domain is loaded by the user.
I have tried several approaches to this problem and they get tedious and can be fragile in the end. I am fascinated with the topic and have wasted many hours developing potential solutions. So trust me, don't over think this if you try attempt trying to keep your PWA synchronized with the latest code.
Anyway, this is why web platform vendors are discussing an API that might allow the website to trigger an update on the client in the background. There are all sorts of security and privacy issues that need to be resolved before the final mechanism can be defined.
I don't expect anything to be defined soon.
So in short, for now you don't have to worry about PeriodicSync being the source of a security issue since it does not even exist.
The SyncManager
Another possible scenario outlined by the paper is the use of the SyncManager, which is part of the Background Sync API.
"In addition to service workers, we use the SyncManager interface to register background 'sync registrations' for the service worker, to keep the Servant always alive."
Where to start with the problems stated in that quote?
First, the SyncManager is part of the Background Sync API, which does not keep a service worker always alive. Background sync is designed for you to be able to pass your network requests through the SyncManager so they can be somewhat guaranteed to be sent even if the network is not currently present.
If the network is available the request is passed along and there is no disruption to the normal flow of the application. If the network is not available, think airplane mode or while you are dining at many of my local Italian restaurants (inside humor) the network request is saved to a queue.
When the device senses the network is available the request is completed, even if the browser has been closed or the user is not currently using the device.
A good example of this from my normal routine is videos and photos. I take photos and videos on my Android all the time. I have the device configured to sync with my Google Photos account only when WiFi is available. I don't have to manually upload the media to the cloud, it just happens when an unmetered network is available.
This is sort of how Background Sync works. Right now it does not discriminate between cellular or Wifi connections, but does handle scenarios when the network is completely unavailable.
Something you need to realize is there is NO mechanism for a server to trigger background synchronization. The activity is initiated by the device. It does so by triggering an event to execute the service worker and process the background requests in the queue.
Their must be requests in the synchronization queue for the background sync event to trigger.
Plus, requests will only by held in a queue for so long, typically no more than 24 hours.
This means a third party server could not remotely trigger any sort of service worker activity through the SyncManager.
Service Worker Execution Context
Because service workers are essentially an operating system service, at least that is a common way I try to explain them, they run in the background and do not require a user interface (UI).
"Unlike web workers, a service worker, once registered and activated, can live and run in the background, without requiring the user to continue browsing through the publisher’s website—service workers run in a separate thread and their life cycle is completely independent from the parent page’s life cycle."
This means the user would not notice the service worker execution. It also means the service worker would not need a website's page to be open or even an open browser instance to run.
The service worker would contain the code to perform a malicious act, cryptocurrency mining for example.
There are several assumptions made by the researchers definition and most of them are not possible.
A Brief Review of the Service Worker Life Cycle
A point the MarioNet paper continuously emphasizes is the service worker will launch and run in the background continuously. In theory this could be the case, but is not how service workers are designed.
Service workers are executed by the platform, either the browser instance or the device's operating system, in response to events. Some examples of events that can trigger a service worker to be executed include a web page within the service worker's scope being loaded, a push notification being received or a background synchronization.
I discussed the background sync earlier. So let me dive into the other two triggers.
Obviously when a page within the service worker's scope is loaded by a browser the service worker will be executed. The primary function is to intercept all network requests to execute the designed logic to determine how the request will be handled. You could resolve a response from cache, the network or build a response.
Check out my article on >a href="blog/service-worker-cache">service worker caching to dive a little deeper into the basic concept.
For push notifications things are a little different. Out of the three event triggers this is one where the user much provide permission and you must visually notify the user of the notification.
If you are unfamiliar with push notifications this is a messaging mechanism used by many sites and applications to interrupt users with messages, typically to make announcements.
For a user to receive your push notification you must prompt them for permission. Then when you send a push notification you must show them a visual message, we call them toasts.
It is a perfectly legitimate action to send a push notification to a device to trigger an application update, you just have to notify the user what is going on.
So for a MarioNet style attack to be triggered via a push notification there are several pieces that have to come to together to enable the attack. The user has to give permission to the offending code. The notification has to notify the user and of course the notification has to pass through the messaging service's plumbing. This means Apple, Microsoft, Google and other hardware vendors push notification services have to allow messages from a malicious attacker.
Not likely to happen.
There is a little more to a service worker's life cycle we need to cover.
First, they don't run 24/7. In fact they only run as long as they need to run.
You can keep a web page open, like me, in a background tab. The page is often dormant, or not being used. In these cases the browser or platform will spin the service worker down to conserve CPU and battery resources.
In fact this can cause developers a little frustration because even when we are debugging service workers with the devtools open the ]service worker can be terminated](blog/service-worker-termination).
This does not mean a service worker could not stay alive for a very long period of time. You could include code in your service worker to continuously perform background calculations, like cryptocurrency mining, etc.
Do browsers safe guard against this scenario?
Yes, but it is up to the browser to determine the exact rules around how long it will keep a service worker around.
Section 2.1.1 of the service worker specification address this:
A user agent may terminate service workers at any time it:
- Has no event to handle.
- Detects abnormal operation: such as infinite loops and tasks exceeding imposed time limits (if any) while handling the events. Service Worker Lifetime
So the browser decides when to terminate the service worker.
What could keep a service worker open?
Not globally executed code. Jeff Posnick seems to address this scenario in a StackOverflow question.
"The flip side of that is that every time the service worker thread is stopped, any existing global state is destroyed."
He also mentions the service worker lifetime is NOT tied to a web page's lifetime. The service worker can be started and stopped as needed to conserve device resources.
This means a service worker would need to be 'held open' using a waitUntil or a respondWith method. The first is used for several service worker events, like install and activate, to tell the service worker dispatcher work is happening and to NOT close the service worker. respondWith is part of the Fetch API and effectively does the same thing but tells the browser to wait until a response has been supplied before killing the process.
There are nuances to both of these scenarios, but I don't think either would allowed to keep a service worker running continuously.
Notice the 'abnormal operation' language in the quote above from the service worker specification.
A couple of other scenarios raised by the MarioNet paper include client-side AJAX request and Sockets. Neither of these could be used to keep a service worker alive.
The first is just a network request and would most likely be initiated from an externally hosted script, which would not trigger the service worker' fetch event handler.
And web sockets would not be an issue since you cannot iniate a socket connection from within a service worker. At least as far as I can been able to determine.
How Service Worker Scope is Defined
Pay attention because this may be the ruin of any potential MarioNet style attack.
Service workers are limited in scope to the domain they are registered. You could register multiple service workers for a website, but each one is scoped separately.
A service worker's scope is limited to the folder it physically resides and lower.
A common mistake new progressive web app developers make is saving their service worker in the 'js' or 'scripts' folder with the rest of their JavaScript files.
When you do this your service worker 'does not work' because it is limited to work in the /js folder and below only, not your root folder or any other folder path on your site.
I often describe a typical corporate intranet where you may have a /hr and a /accounting sub folder structure. Each department can have their own service worker registered. But those service workers cannot access the other service worker's scope. They are isolated.
So what does this mean for MarioNet?
The scenario of a service worker being registered from another website is completely false. That is not how service workers function and they don't allow this activity as a way to safe guard against this sort of attack.
Section 6.3.1 of the Service Worker specification addresses this scenario very clearly:
"A service worker executes in the registering service worker client's origin. One of the advanced concerns that major applications would encounter is whether they can be hosted from a CDN. By definition, these are servers in other places, often on other origins. Therefore, service workers cannot be hosted on CDNs. But they can include resources via importScripts(). The reason for this restriction is that service workers create the opportunity for a bad actor to turn a bad day into a bad eternity." Service Worker Origin Restriction
More on the importScripts potential issue later. You will want to read that part, trust me!
Here is an example of trying to register an external service worker:
"Failed to register a ServiceWorker: The origin of the provided scriptURL ('https://love2dev.com') does not match the current origin ('http://localhost:18500')."
You get a SecurityException because you have tried something that is considered insecure.
What about in injected iframe?
You can't register a service worker from an iframe. The MarioNet paper even states this:
"Also, no iframe or third-party script can register its own service worker."
A third party or cross-domain loaded script cannot register a service worker either. A standard safe-guard baked into the service worker specification.
If you are wondering how wide spread third party scripts are you should just review the network waterfalls of the sites you visit. This is just part of a waterfall that consisted of over 1000 network requests because the page loaded a third party ad service script that subsequently loaded several other ads services, etc.
This is just one of many examples of how outsourcing control of your web page can leave you vulnerable to many unwanted side affects.
Well all this means is for MarioNet to execute you must have an infected service worker installed on your website. In other words you would be the source of the malicious code.
If you blindly install platform extensions, like WordPress plugins, you could be installing code that modifies your service worker to inject malicious code into your service worker's execution cycle.
Service workers can import scripts and this could be the source of a potential attack. In fact this is the primary area I think you could introduce problematic code to your service worker.
A problem free, open source projects have is the rampant malicious code merged into code bases. This is a problem for projects of all languages and platforms. I have seen audits of the node, NPM and Yarn, modules ecosystems and here is an alarming number of modules infected with the exact type of code reference in the MarioNet paper.
I do import helper libraries into most of my service workers. For example I import Mustache and LoDash quote often. I also use WorkBox in more complex service workers. All these libraries are loaded via an importScripts call.
Unfortunately not all developers are careful to audit the libraries they include in projects to see if there is malicious code. I know I have removed several cryptocurrency minor Trojans in the past couple of years.
Make sure to audit any third part scripts you import to your service worker. A good way to double check this is to review your page's network waterfall. If you see requests for files that look odd you should check to see where they are being requested and how they function. If you cannot determine they are required or even what they do, it might be wise to remove the offending library and find an alternative solution.
You are responsible for the quality of code in your service worker, even when you outsource it to an open source library.
Problems With the MarioNet Attack Scenario
Let's review why there are many problems with the MarioNet style attack.
You can't register an external or third party service worker. A service worker can only be registered from a URL's domain. It can be scoped to a sub-folder, but must reside on that 'server', not a CDN or any other external domain.
This means any malicious code must be part of your service worker, a file you should have 100% control over.
Periodic Sync is not a thing yet, it is not even an early specification draft. Just a concept.
SyncManager cannot be triggered remotely, which makes exploiting background sync difficult. Plus it is only implemented by Chrome at this time.
Service workers don't run 24/7. They do spin down to preserve device resources.
Wrapping Things Up
I appreciate when potential security holes are exposed. They give us the opportunity to be on the look out for malicious code and hopefully patch our systems.
What I worry about is when scenarios like the MarioNet are distributed and based on poor understanding of the platform. As we saw this week, the news of a potential security threat with security workers was picked by some in the media and the message not only amplified, but further blurred.
To be frank I find myself spending more and more time trying to explain away 'bad science', like MarioNet, around Progressive Web Apps to clients. It is good to always be concerned when you are betting your business on a technology.
But it is extra bad when you bet against a technology choice because the one you picked flat out mislead you about the alternative choice. In this case service workers and progressive web apps.
I have to admit more than half the content I read about progressive web applications is flawed and misleading. This is causing a problem. The MarioNet paper, while having a valid scenario included seems to have propagated a lot of false assertions in the media. This is only leading decision makers to make bad decisions.
After reading my article and interpretation of the MarioNet attack scenario I hope you see there is nothing real you should be concerned with today and most likely in the future.
One thing I did note about the paper is they did not provide any example code. I think this is important because it does not provide a way for me or anyone else to test their scenario. They do mention they tested in a few browsers, but honestly I don't know how. Most of the potential scenarios cannot be done due to security limitations, etc.
The only scenario I could see them really testing is where the domain's service worker contains malicious code. Which means they used a known compromised service worker to test.
You do need to audit the code you include in your service worker, especially if it is not code you wrote. If you rely on developers to create your service worker you should audit the code they provide. Make sure they are not being lazy and outsourcing your security to an unknown third party.
The authors of the service worker specification have been very cautious about allowing service workers to be vulnerable to this specific type of attack.
Service workers are isolated by domain and can only be registered by the originating web page and the service worker source must belong to the same domain as the requesting page.
Service workers cannot be registered from a third party domain, cross-domain script, request or from an iFrame.
Service workers cannot be triggered remotely by a master server. They can only be trigger in response to a legitimate event on the device and all but loading a web page are gated behind a user permission flag.
In other words there are already safe guards in place to prevent a MarioNet style attack from happening.
Service workers and progressive web apps are a safe technology that help the web platform level up to an equal footing with native applications. Security has always been a first class priority when designing how service workers are implemented.
So feel safe using service workers in your websites.