Man in the Middle Attacks via JavaScript Service Workers
Tags: JavaScript, Hacking, Technology, Cybersecurity, Programming

Install Our Man in the Middle

All of this is quite impressive and shows us how powerful service workers are! But how could they be used as a MitM? In order to understand this, we first need to look at how service workers operate.

The service worker life cycle (photo by the author)

The figure above demonstrates a simplified life cycle of a service worker. A service worker must first be registered on the website before it can be used. After registering, the browser will install the worker in the background. As soon as it is installed, the service worker receives the activate event, which developers then typically use to initialize the loading of static files into the cache. Next, the service worker goes into an idle state where it will take two states: Either it is terminated to save memory or it handles fetch and message events. That includes all requests from the pages that fall under the scope of the worker.

If a new service is registered, the web page prioritizes the use of the old worker and sets the new service worker in a waiting state. Once the web page is closed, the old service worker is removed and the new one is activated. This can be important for MitM, as it means that the MitM worker is only loaded after reloading the web page.

JavaScript code to register a service worker

JavaScript code that defines the service worker

Two code snippets can be seen above. The first snippet is used to register a service worker. It’s important to know is that a service worker can only be installed from a location in the same domain. This makes it more difficult for attackers. Nevertheless, arbitrary file upload or a similar approach can be used to bypass this limitation (more on this later).

The second code snippet shows the definition of a service worker. The fetch event is always activated when the website sends a request. In the example above, the worker only returns Hello World! when given a request.

Now that we know the general use of service workers, let’s look at the MitM proof of concept:

A proof of concept Flask application

We can see in the Flask application that I added an open redirect. This is done because the address that the service worker is loaded from must have the same origin. I talk more about this later in the limitations section. The index.html that is rendered contains the standard service worker installation code we saw earlier, the only difference being that the service worker URL makes use of the open redirect: https://victim.com/?url=https://attacker.com/static/sw.js .

A proof of concept service worker

The service worker itself receives a fetch event on every request and subsequently sends two requests out. The first fetch event (on line 4) sends the requested URL and header information to the attacker's domain. Needless to say, arbitrary information can be added to this request. The second fetch event (on line 6) sends a request to the initially requested URL in order to return the real response to the user.

The access log below shows the attacker’s side on June 29, 2020, where a user requested https://victim.com/foo without any header information. The initiator of this request is the service worker from https://victim.com/?url=https://attacker.com/static/sw.js .

123.456.789.123 — — [29/Jun/2020:00:25:02 +0000] “GET /mitm?url=https://victim.com/foo&headers={} HTTP/1.1” 404 209 “https://victim.com/?url=https://attacker.com/static/sw.js" “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36”

If you are curious about whether you have ever used a service worker or not, you most definitely have. Open your developer console where they are all listed. In Google Chrome, for instance, it is located in the Application tab under Service Workers. The picture below shows our MitM service worker: