TL;DR
Finding a bug in Safari which made it possible to bypass a filter and achieve XSS. The vulnerability re-appeared twice, one time identified by someone else.
Almost a year ago, I started to look into the assets belonging to a company that are running a public bug bounty-program. One way of approaching a target is to look for plain HTML-files hosted on a site that is not normally built that way. This type of file often contains DOM-XSS vulnerabilities (related: https://blog.detectify.com/2018/10/04/iframe-busters-lead-to-xss/).
Here is one of the files I stumbled upon (removed unnecessary code for readability):
<script type="text/javascript"> var parseQueryString = function parse(queryString) { */ parsing /* return params; } var search = window.location.search; var query = parseQueryString(search); var redirect = query.redirect; redirect = decodeURIComponent(redirect); var parser = document.createElement('a'); parser.href = redirect; var protocol = parser.protocol.toLowerCase(); if (['javascript:', 'vbscript:', 'http:', 'https:', 'data:'].indexOf(protocol) < 0) { window.top.location = redirect; } </script>
The purpose of the page seems to be to redirect to a mobile app. It takes the redirect-parameter, checks the protocol against a blacklist and if not found redirects to it.
To exploit this we need to a create a link that will execute as Javascript while the protocol of it is not ‘javascript’. As far as I know this should not be possible according to browser specifications. However, as with all software, browsers do not always follow specifications.
Thinking the filter part worked as it should, I focused on the redirect part. There is a .toLowerCase() used, would it be possible to use some unicode characters that turns into another when it is made lowercase? Can a protocol be splitted by a newline but still work the same?
The attempts were unsuccessful and I added the endpoint to a todo-textfile that are full of other failed attempts to someday take up again.
Fast forward to late spring, about half a year after the first look at the file. With some newly found energy, I picked this one from the long backlog of things to look at and decided to focus on the redirect part instead of the filter.
I followed a hunch and started with Safari. After playing around with it before I remembered that the domain and URL-handling are weird in general. If there are bugs in my favour, this is the most likely place.
I opened up the console, wrote a quick function to emulate the bypass, and started to play around. Is there any chance this would work?
`//` being a single-line comment in Javaccript, `%0a` is a URL-encoded newline to escape the comment. However, no luck here, things worked as they should. But then..
To my surprise it actually worked. Call it luck and manual fuzzing, but hey, it works! An empty protocol is not specified in the blacklist so it bypasses the filter.
The final payload would then be `?redirect=javascript://%0aalert(document.domain)`.
This was reported to a few of the bigger sites that used this SaaS-vendor. By pointing one of their subdomains to the service they were themselves affected by it. The SaaS-vendor got contacted by one of the affected sites and the vulnerability was soon patched upstream. Apple also got contacted about the bug. This is usually where everything ends and gets forgotten, but not this time.
This was soon patched by the SaaS-vendor, meaning that all the affected sites were now safe again. However, the fix seemed to break the functionality of the page which lead me to think it was a temporary fix. I continued to visit the page once in a while waiting for a permanent solution, but after a while just forgot about everything.
(It is possible the temporary fix actually work and I just misunderstood it. I was staring blind on JavaScript, while they in the last fix reflected values server side, something I did not look for.)
Fast forward some months, and I received a link to vpnMentor’s write-up which shows that the temporary fix had been replaced with a more permanent one. However that in turn resulted in new XSS-vulnerabilities, this time found by vpnMentor.
What makes everything interesting is that the initial payload still worked, even after the vulnerabilities found by vpnMentor had been resolved. I received the link to the writeup because the sender thought I had forgotten to report it the first time. The fix for the second vulnerability was still vulnerable to a third vulnerability, using the very same payload as in the first report.
This was no longer a pure DOM-XSS. Instead of reading the URL-parameters in Javascript, they were now reflected server side. However, that aside and it more or less still worked in the same way.
I want to emphasize that even though there has been a few vulnerabilities now, the response time from the affected SaaS-vendor as always been very short and handled well. All software has issues.
The solution of fixing the third vulnerability was now to add ‘ ‘ and ‘:’ to the blacklist. I cannot think of any bypasses to this, but maybe there are ones?
// App redirects only. if ([ 'javascript:', 'vbscript:', 'http:', 'https:', 'data:', 'ftp:', ':', ' ' ].indexOf(protocol) < 0) { window.top.location = validate("[injection]"); }
It is most likely this function need to support a lot of different custom app protocols making it more or less impossible to use a whitelist instead of a blacklist, an approach that otherwise would been strongly recommended.
Apple were notified about the protocol bug when it was initially discovered half a year ago. It still works in the latest version of Safari on both Mac and mobile devices.
Author:
Linus Särud
Security Researcher
@_zulln