Permalink
Browse files

WVT: add Universal Link protection

Universal Links in iOS allow 3rd party apps to claim URL hosts that
they own, so when links to those hosts are being opened in other
apps, that 3rd party app is executed to handle that URL request.

Unfortunately there is no way to disable this, so in a web browser
app like Endless (I also tested in Chrome, Firefox, Brave, Tob, and
Onion Browser), tapping on a link can immediately spawn an installed
3rd party app which will make that URL request without any
confirmation or warning.

For example, if the user has the eBay app installed and taps on a
link in Endless pointing to a http://rover.ebay.com/ URL, the eBay
app will immediately be opened and show the auction page being
requested, which could contain an <img> tag in the auction
description that loads from a 3rd party server.  While this isn't a
big deal for Endless, it is for Tor- and VPN-based apps that are
based on Endless which are trying to keep the user's network
activity contained inside the app.

Since UIWebView (and WKWebView) offer no indication that such a URL
will be opened as a Universal Link (and probably won't ever, for the
same reason that iOS disabled UIApplication:canOpenURL: so apps
can't figure out which other apps the user has installed), implement
a workaround.

In WebViewTab's webView:shouldStartLoadWithRequest: delegate method,
always return NO for top-level requests that get here (which are
links that have been tapped on, window.location= calls, and iframes)
but then just start a new request for the same URL.  This seems
enough to bypass Universal Link activation and still works with a
bunch of sites and Javascript that I tested.

Test URL: https://endl.es/tests/decloak
  • Loading branch information...
jcs committed Feb 13, 2017
1 parent 9d31894 commit 436091ff17f3b8724eebb21b235250ae6286fc01
Showing with 32 additions and 7 deletions.
  1. +2 −0 Endless/WebViewTab.h
  2. +30 −7 Endless/WebViewTab.m
@@ -13,6 +13,8 @@
#define ZOOM_OUT_SCALE 0.8
#define ZOOM_OUT_SCALE_ROTATED 0.7

#define UNIVERSAL_LINKS_WORKAROUND_KEY @"yayprivacy"

typedef NS_ENUM(NSInteger, WebViewTabSecureMode) {
WebViewTabSecureModeInsecure,
WebViewTabSecureModeMixed,
@@ -297,18 +297,37 @@ - (BOOL)webView:(UIWebView *)__webView shouldStartLoadWithRequest:(NSURLRequest

/* treat endlesshttps?:// links clicked inside of web pages as normal links */
if ([[[url scheme] lowercaseString] isEqualToString:@"endlesshttp"]) {
url = [NSURL URLWithString:[[url absoluteString] stringByReplacingCharactersInRange:NSMakeRange(0, [@"endlesshttp" length]) withString:@"http"]];
[self loadURL:url];
NSMutableURLRequest *tr = [request mutableCopy];
[tr setURL:[NSURL URLWithString:[[url absoluteString] stringByReplacingCharactersInRange:NSMakeRange(0, [@"endlesshttp" length]) withString:@"http"]]];
[self loadRequest:tr withForce:NO];
return NO;
}
else if ([[[url scheme] lowercaseString] isEqualToString:@"endlesshttps"]) {
url = [NSURL URLWithString:[[url absoluteString] stringByReplacingCharactersInRange:NSMakeRange(0, [@"endlesshttps" length]) withString:@"https"]];
[self loadURL:url];
NSMutableURLRequest *tr = [request mutableCopy];
[tr setURL:[NSURL URLWithString:[[url absoluteString] stringByReplacingCharactersInRange:NSMakeRange(0, [@"endlesshttps" length]) withString:@"https"]]];
[self loadRequest:tr withForce:NO];
return NO;
}

if (![[url scheme] isEqualToString:@"endlessipc"]) {
if ([[[request mainDocumentURL] absoluteString] isEqualToString:[[request URL] absoluteString]])

/* regular http/https urls */
else if (![[url scheme] isEqualToString:@"endlessipc"]) {
/* try to prevent universal links from triggering by refusing the initial request and starting a new one */
BOOL iframe = ![[[request URL] absoluteString] isEqualToString:[[request mainDocumentURL] absoluteString]];
if (iframe) {
#ifdef TRACE
NSLog(@"[Tab %@] not doing universal link workaround for iframe %@", [self tabIndex], url);
#endif
} else if ([[[url scheme] lowercaseString] hasPrefix:@"http"] && ![NSURLProtocol propertyForKey:UNIVERSAL_LINKS_WORKAROUND_KEY inRequest:request]) {
NSMutableURLRequest *tr = [request mutableCopy];
[NSURLProtocol setProperty:@YES forKey:UNIVERSAL_LINKS_WORKAROUND_KEY inRequest:tr];
#ifdef TRACE
NSLog(@"[Tab %@] doing universal link workaround for %@", [self tabIndex], url);
#endif
[self loadRequest:tr withForce:NO];
return NO;
}

if (!iframe)
[self prepareForNewURL:[request mainDocumentURL]];

return YES;
@@ -455,6 +474,10 @@ - (void)webView:(UIWebView *)__webView didFailLoadWithError:(NSError *)error
/* "The operation couldn't be completed. (Cocoa error 3072.)" - useless */
if ([[error domain] isEqualToString:NSCocoaErrorDomain] && error.code == NSUserCancelledError)
return;

/* "Frame load interrupted" - not very helpful */
if ([[error domain] isEqualToString:@"WebKitErrorDomain"] && error.code == 102)
return;

NSString *msg = [error localizedDescription];

0 comments on commit 436091f

Please sign in to comment.