This iOS Security Flaw Needs To Be Addressed In Every App

Tom Colvin
4 min readFeb 16, 2022

--

A flaw in the way iOS handles app uninstalls means that sensitive Keychain data isn’t deleted when the rest of the app’s user content is. It affects most apps, even ones that don’t directly use Keychain.

When users uninstall an app from their iPhone or iPad, they rightly expect that app’s data to be fully deleted. And, in general, it is; the only exceptions being data stored in shared spaces such as photos added to the gallery.

But, as it turns out, iOS doesn’t delete some of the most sensitive data that an app may store, which includes passwords and security tokens. That data will remain on your device after the app is uninstalled.

Oh no it won’t

Why is this a problem?

Firstly, and most obviously, some of an app’s most critical data will remain on users’ devices when they have been reassured otherwise. And in fact, users have no way to remove this data at all, save for wiping their entire phone.

This can lead to other privacy-busting problems. Imagine for example that you use a service but then uninstall the app, apparently deleting all data. Months later, you want a fresh start, so you reinstall. In fact the app will likely log you in as your old user. There is an additional privacy issue in that the service you are using will see you reinstating your old account.

Even worse, imagine a situation where someone deletes all their apps to hand a phone to someone else. (The right way to do this of course is to use the “Erase iPhone” function, but not all consumers know that). They would reasonably assume that all their data has been removed. But in fact the new user may potentially have access to all the previous owners’ accounts just by reinstalling the relevant apps.

Does this affect my app?

Probably, yes. Most apps don’t use the Keychain directly, because they use a third-party library which does the work for them (Firebase for example). This flaw affects any app which uses Keychain directly or indirectly.

Obviously, if you’re manually storing cryptographic tokens and not using the Keychain, then you’re doing it wrong.

So how should apps work around this?

There is no way of hooking into the uninstall process, simply because Apple doesn’t give you that opportunity. The OS manages your app’s uninstallation entirely by itself, and you have no control over the specifics of what it does. So, you can’t delete your Keychain on uninstall.

So the next best thing is to delete the Keychain data whenever you detect that the app has been reinstalled. That won’t prevent the data from lying dormant on your phone, but it’ll prevent it from being used when it potentially shouldn’t be.

A proposed workaround

The idea is to place a flag in the app’s storage area as soon as the app is run for the first time. If that flag didn’t previously exist then we know that it’s a fresh install, and we should delete any Keychain data.

The code looks like this for a SwiftUI app:

struct MyApp: App {
init() {
// Find if this is a first run of a fresh install
let defaults = UserDefaults.standard

// If the "IsSubsequentRun" key doesn't exist, it's a fresh
// install
if !defaults.bool(forKey: "IsSubsequentRun") {

// ...so we delete the whole keychain
deleteEntireKeychain()

// And set the key to true, so we know it's not a fresh
// install any more.
defaults.set(true, forKey: "IsSubsequentRun")
}
}
}

For a UIKit app, the code inside init() should instead be put in your AppDelegate’s application(_:didFinishLaunchingWithOptions:)function.

In either case, make sure of course that the code is run before initialising any libraries which might make use of the Keychain data (such as Firebase).

Finally, add the following function which can be used to delete your app’s entire Keychain data:

import Security

...

func deleteEntireKeychain() {
let secItemClasses = [
kSecClassGenericPassword,
kSecClassInternetPassword,
kSecClassCertificate,
kSecClassKey,
kSecClassIdentity
]

// Query every item in each security class
for secItemClass in secItemClasses {
let query: NSDictionary = [
kSecClass: secItemClass,
kSecAttrSynchronizable: kSecAttrSynchronizableAny
]

// ...and delete those items
SecItemDelete(query)
}
}

That import statement goes at the top of your script, obviously.

In conclusion

This is a privacy-busting security flaw. It causes sensitive data which users would reasonably expect to have been deleted, to in fact stick around. There is no complete workaround. However, the above code will at least guarantee that any data which should have previously been wiped, cannot be used in your app.

The flaw described in this article has been tested on 13th February 2022 and is present in iOS 14.2 and 15.0, and the workaround presented has been tested against both those versions.

Tom Colvin is CTO of Conseal Security, the mobile app security experts; and Apptaura, the app development specialists. Get in touch if I can help with any mobile security or development projects!

--

--

Tom Colvin

The Android Sherpa. Google Developer Expert in Android and CTO of Apptaura, the app dev specialists. Message for free consultancy. All articles 100% me, no AI.