Just came back from another great
HackPra Allstars, this time in the beautiful city of Amsterdam.
Mario was kind enough to invite me to ramble about random security stuff I had in mind (and this year it was
Service Workers). The presentation went OK and it was super fun to meet up with so many people, and watch all those great presentations.
In any case, I promised to write a blog post to repeat the presentation but in a written form, however I was hoping to watch the video to see what mistakes I might have made and correct them in the blog post, and the video is not online
yet. Anyway, so, until then, today this post is about something that wasn't mentioned in the talk.
This is about what type of vulnerabilities applications using Service Workers are likely to create. To start, I just want to say that I totally love Service Workers! It's APIs are easy to use and understand and the debugging tools in Chrome are beautiful and well done (very important since tools such as
Burp or
TamperChrome wouldn't work as well with offline apps as no requests are actually done.) You can clearly see that a lot of people have thought a lot about this problem, and there is some serious effort around making offline application development possible.
The reason this blog post content wasn't part of the presentation is because I thought it already had too much content, but if you don't know how Service Workers work, you might want to see the first section of the
slides (as it's an introduction) or you can read
this article or
this video.
Anyway, so back to biz.. Given that there aren't many "real" web applications using Service Workers, I'll be using the service-worker samples from the
w3c-webmob, but as soon as you start finding real service worker usage take a look and
let me know if you find these problems too!
There are two main categories of potential problems I can see:
- Response and Caching Issues
- Web JavaScript Web Development
Response and Caching Issues
The first "category" or "family" of problems are response and caching issues. These are issues that are present because of the way responses are likely to be handled by applications.
Forever XSS
The first problem, that is probably kind-of bad, is the possibility of
making a reflected XSS into a persistent XSS. We've already seen this type of problems based on APIs like localStorage
before, but what makes this difference is that the Cache API is actually designed to do exactly that!
When the site is about to request a page from the web, the service worker is consulted first. The service worker at this point can decide to respond with a cache response (it's logic is totally delegated to the Service Worker). One of the main use-cases for service workers is to serve a cached-up copy of the request. This cache is programmatic and totally up-to the application to decide how to handle it.
One coding pattern for Service Workers is to respond with whatever is in the cache, if available or, to make a request if not (and cache the response). In other words, in those cases, no request is ever made to the server if the request matches a cached response. Since this Cache database is
accessible to any JS code running in the same origin, an attacker can pollute the Cache with whatever it wants, and let the client serve the malicious XSS for ever!
If you want to try this out, go here:
Then fake an XSS by typing this to the console:
caches.open('prefetch-cache-v1').then(function(cache){cache.put(new Request('index.html', {mode: 'no-cors'}), new Response('\x3cscript>alert(1)\x3c/script>', {headers: {'content-type': 'text/html'}}))})
Now whenever you visit that page you will see an alert instead! You can use Control+Shift+R to undo it, but when you visit the page again, the XSS will come back. It's actually quite difficult to get rid of this XSS. You have to manually delete the cache :(.
This is
likely to be an anti-pattern we'll see in new Service-Workers enabled applications
often. To prevent this, one of the ideas from
Jake Archibald is to simply check the "url" property of the response. If the URL is not the same as the request, then simply not use it, and discard it from the cache. This idea is quite important actually, and I'll explain why below.
When Secure Open Redirects become XSS
A couple years ago in a
presentation with
thornmaker we explained how open redirects can result in XSS and information leaks, and they were mostly limited to things such as redirecting to insecure protocols (like javascript: or data:). Service Workers, however, and in specific some of the ways that the Cache API is likely to be used, introduce yet another possibility, and one that is quite bad too.
As explained before, the way people seem to be coding over the Cache API is by reading the Request, fetching it, and then caching the Response. This is tricky, because when a service worker renders a response, it renders it from the same origin it was loaded from.
In other words.. let's say you have a website that has an open redirect..
- https://www.example.com/logout?next=https://www.othersite.com/
And you have a service worker like this one:
- https://googlechrome.github.io/samples/service-worker/read-through-caching/service-worker.js
What that service worker does, as the previous one, is simply cache all requests that go through by refetching and the service worker will cache the response from evilwebsite.com for the other request!
That means that next time the user goes to:
- https://www.example.com/logout?next=https://www.evilwebsite.com
The request will be cached and instead of getting a 302 redirect, they will get the contents of evilwebsite.com.
Note that for this to work, evilwebsite.com must include Access-Control-Allow-Origin: * as a response header, as otherwise the request won't be accepted. And you need to make the request be cors enabled (with a cross origin image, or embed request for example).
This means that an open redirect can be converted to a persistent XSS and is another reason why checking the url property of the Response is so important before rendering it (both from Cache and on code like event.respondWith(fetch(event.request))). Because even if you have never had an XSS, you can introduce one by accident. If I had to guess, almost all usages of Service Workers will be vulnerable to
one or
another variation of these types of attacks if they don't implement the response.url check.
There's a bunch of other interesting things you can do with Service Workers and Requests / Responses, mentioned in the talk and that I'll try to blog about later. For now though, let's change subject.
Web JavaScript Web Development
This will sound weird to some, but the JavaScript APIs in the browser don't actually include any good web APIs for building responses. What this means is that because Service Workers will now be kind-of like Web Servers, but are being put there without having any APIs for secure web development, then it's likely they will introduce a bunch of cool bugs.
JavaScript Web APIs aren't Web Service APIs
For starters, the default concerns are things that affect existing JS-based server applications (like Node.js). Things from
RegExp bugs because the JS APIs aren't secure by default to things like the JSON API
lack of encoding.
But another more interesting problem is that the lack of templating APIs in Service Workers means people will write code like
this which of course means it's gonna be full of XSS. And while you could import a
templating library with
strict contextual auto-escaping, the lack of a default library means people are just likely to use insecure alternatives or just default to string concatenation (note things like angular and handlebars won't work here, because they work at the DOM level and the Service Workers don't have access to the DOM as they run way before the DOM is created).
It's also worth noting that thanks to the asynchronous nature of Service Workers (
event-driven and
promise-based) mixed with developers that aren't used to this, is extremely likely to introduce
concurrency vulnerabilities in JS if people abuse the global scope, which while isn't the case today, is very likely to be the case soon.
Cross Site Request Forgery
Another concern is that CSRF protection will have to behave differently for Service Workers. In specific, Service Workers will most likely have to depend more on referrer/origin based checks. This isn't bad in and off itself, but mixing this with online web applications will most likely pose a challenge to web applications. Funnily, none of the demos I found online have CSRF protection, which remarks the problem, but also makes it hard for me to give you examples on why it's gonna be hard.
To give a specific example, if a web application is meant to work while offline, how would such application keep track of CSRF tokens? Once the user comes online, it's possible the CSRF tokens won't be valid anymore, and the offline application will have to handle that gracefully. While handling the fallback correctly is possible by simply doing different checks depending on the online state of the application, it's likely more people will get it wrong than right.
Conclusion
I'm really excited to see what type of web applications are developed with Service Workers, and also about what type of vulnerabilities they introduce :). It's too early to know exactly, but some anti-patterns are clearly starting to emerge as a result of the API design, or of the existing mental models on developers' heads.
Another thing I wanted to mention, before closing up is that I'll start experimenting writing some of the defense security tools I mentioned in the talk, if you want to help out as well, please let me know!