With the ever increasing ways to use our mobile devices in daily life, they also collect increasing amounts of sensitive personal data. Being mobile, these devices are easily misplaced or stolen. That makes it essential to protect the data stored on the device.
For iOS developers, Apple offers the Data Protection API (for files and databases) and Keychain (for keys and small data, like passwords). With these simple APIs, access to data can be tied to the device passcode. Although this is certainly not impenetrable, it adds a substantial obstacle at virtually no cost for the app developer.
A while ago, I took a look at the data storage security in two commonly used apps for iOS: Dropbox and Evernote. I tested them on iOS 6, as I required a jailbroken device to get reliable information. As one would expect, these apps get many basics right: passwords and keys, for example, are stored in the Keychain, where they belong. However, they’ve failed to apply data protection to their caches.
This means that a significant portion of my Dropbox files and Evernote notes are available to anyone who acquires physical access to my device for an hour or so, regardless of any passcodes I might have set, if I am running iOS 6, as 15-20% of users still are. The issue could be prevented with a few lines of code. Evernote has confirmed they will not resolve this, and Dropbox has stopped replying to me.
Update: I have verified that on iOS 7, this issue has been resolved, as the default data protection settings in the OS have changed.
Update 2: On January 14, I received a message from Dropbox, thanking me for my report and offering a 100GB account quota, and a t-shirt.
How does this data protection work?
When processing a user’s files, iOS can encrypt them with a variety of keys. Apple has a rather extensive document on how this all works, but it basically means there are a few protection levels a developer can set on a file:
- NSFileProtectionNone: No data protection is used. The file is always accessible and never encrypted. This is almost never a good choice.
- NSFileProtectionComplete: The file is only accessible while the device is unlocked. When the user locks the device, e.g. by pressing the power button, access to the file is lost until the user unlocks the device again by entering their passcode. Without the device passcode, the file can not be accessed once the device has been locked, or has been rebooted. This is the best choice for any file that does not need to be accessed in the background.
- NSFileProtectionCompleteUnlessOpen: Similar to NSFileProtectionComplete, with the exception that if an app has a file open, and the user locks the device, the app can continue reading from and writing to the file. But once the file is closed, it can not be reopened until the device is unlocked. This would be a good choice if an app wants to continue a download in the background.
- NSFileProtectionCompleteUntilFirstUserAuthentication: The file is only accessible after the device has been unlocked at least once. After that, the file remains accessible regardless of whether the device is locked or unlocked. Only after rebooting the device, the file will be inaccessible again, until the user enters their passcode. This is suitable for files that need to be accessed in the background.
In normal operation, files are also protected by the sandbox: my app is not allowed to access your app’s files. The sandbox protection can be broken by a jailbreak or some forensic tools. However, this typically requires a reboot, which is why complete until first user authentication is a much better choice than none.
To summarise: the minimum protection level developers should almost always use for all files is complete until first user authentication. Stricter is better. How you set a protection level depends on how you’re writing the file, but is usually trivial, like this example:
NSData *contents = [@"secret contents" dataUsingEncoding:NSUTF8StringEncoding]; [contents writeToFile:path options:NSDataWritingFileProtectionComplete error:&error];
Where exactly is the problem, and how did you verify it?
I have a jailbroken iOS 6 device. That means that I have remote shell access, and can probe around the filesystem. I built a very tiny iOS app that dumps the attributes of a given file. First of all, I tested it on some files generated by my Data protection demo to verify the output.
Then, I looked through the files that both Evernote and Dropbox store on the device. For example, we can see that this SQLite file is properly protected for a file that needs background access (paths trimmed for brevity):
$ DataProtectionDisplay Documents/.../.../Evernote5.sqlite ... NSFileProtectionKey = NSFileProtectionCompleteUntilFirstUserAuthentication; ...
However, the local cache of my notes is not protected at all:
$ DataProtectionDisplay Library/Private Documents/.../.../ content/.../note.html ... NSFileProtectionKey = NSFileProtectionNone; ...
This is the same for the images included in the note. For Dropbox, in which I stored an image called
secure-image.jpg, it’s no better:
$ DataProtectionDisplay Library/Caches/Dropbox/ secure-image.jpg/480x320_bestfit ... NSFileProtectionKey = NSFileProtectionNone; ...
What if I set a Dropbox/Evernote passcode?
Both Evernote and Dropbox allow you to set a passcode for access to the app. This is separate from the device passcode. For Evernote, this is only available to premium accounts. As I don’t have such an account, I can’t confirm how Evernote implemented this.
With Dropbox, this feature is available to all users. They specifically advertise this feature to protect your data in case of device theft:
For added security, you can require a four-digit passcode to be entered any time the Dropbox mobile app is launched. This way, you’ll know your Dropbox is safe even if your smartphone or tablet falls into the wrong hands.
Unfortunately, the reality is somewhat different. The passcode you enter for the Dropbox app is not used to encrypt anything. So to read the files, the passcode is inconsequential. And it’s worse: the passcode itself is stored without encryption in the app’s preferences file:
plutil -show com.getdropbox.Dropbox.plist ... DropboxPassCode = 1111; ...
The preferences file, typically accessed through NSUserDefaults, is a very simple storage method, but only suitable for data which is not sensitive in any way, and always stored with NSFileProtectionNone. By storing the passcode here, Dropbox has not only added a passcode that adds no security to the storage of the data: they’ve made it more likely that an attacker will also find the device passcode, as some users will use the same passcode for Dropbox and the device.
What should developers do?
- Always enable data protection on potentially sensitive files or databases stored by your app. If in doubt, use data protection. Do not rely on OS defaults.
- Always use the strictest protection you can use. If your app does not run in the background, use complete. Never use none, as it can virtually always be replaced with until first user authentication.
- To help you figure out the proper protection level, and get a small code sample of how you set that,I built howtostoreiosdata.com. This is a work in progress.
- If you want to add an additional passcode to your app, at the very least store it in the Keychain, not in the preferences, so that you’re not decreasing security.
- However, if you want to offer the user real protection with the additional passcode, use it to encrypt the data you are storing. Do not store the key/passcode on the device in any form: the point is that only with involvement of the user who has the key memorised, the data can be accessed. Have a look at RNCryptor for the crypto part.
- Remember to also be vigilant about any caches created by your app or any of the libraries you use. They often contain confidential data, and are easily overlooked.
What about iOS 7?
Both Dropbox and Evernote have newer versions of their apps that are only available for iOS 7.
Update: since writing this article, a friend provided me with an iOS 7 device to jailbreak and test this. I have confirmed that for both Dropbox and Evernote, for the files I tested, the protection level is now complete until first user authentication, as it should be. The Dropbox passcode is also no longer being stored in the preferences, although it is still not actually used to encrypt the data stored on the device.
Evernote confirmed they rely on the system default for their data protection settings, and I suspect Dropbox does the same. On iOS 6, this is probably none, and on iOS 7 this is probably complete until first user authentication – clearly improving the situation. However, I’ve gotten inconsistent results in trying to determine the default levels. Apple Developer Technical Support also wasn’t sure about the defaults, and recommended to never rely on them, but always set the appropriate level yourself:
On older versions of iOS I believe it was none and on iOS 7 I believe it’s complete until first user authentication. However, this shouldn’t matter to your application at all. If you want a specific protection level, you should be choosing one.
However, even assuming that this is not an issue at all on iOS 7, that still leaves 15-20% of the users exposed to an trivially preventable security issue. And this does not only affect existing users: the App Store offers the older version for any new downloads from iOS 6.
Have you contacted Dropbox/Evernote?
Yes. Neither contact has resulted in a fix being applied. Here’s a summary of my communication with them:
- 14-09-2013: Initial report sent to security at dropbox.com.
- 17-09-2013: Received confirmation that report will be forwarded to security team.
- 19-09-2013: Received reply that this issue is not significant, as it requires physical access and/or USB debugging.
- 24-09-2013: Sent clarification of the issue. Indeed, physical access is required, but theft and misplacement are common for mobile devices. That’s why data protection was created in the first place. Also, the impact is very high: complete access to all cached files in the user’s Dropbox.
- 30-09-2013: Received reply that to submit an official vulnerability report, I could contact security at dropbox.com, and that rewards can be offered to authors of vulnerability reports.
- 02-10-2013: Asked for clarification, as I had mailed security at dropbox.com initially, and thought these mails were being exchanged with the Dropbox security team.
- 10-10-2013: Received reply that I have been mailing with a member of the security team, but that the ticket does not appear to have been sent in directly to security at dropbox.com.
- 13-10-2013: Resent initial report to security at dropbox.com with reference to earlier ticket.
- 14-01-2013: Received reply thanking me for my report and offering a 100GB account quota and a t-shirt.
- 06-11-2013: Initial report sent to security at evernote.com. Received automatic confirmation.
- 13-11-2013: Received human confirmation that the issue is being looked into.
- 15-11-2013: Received reminder that it has been 5 days since the last correspondence.
- 22-11-2013: Received request to rate the support I had received in this ticket.
- 14-12-2013: Asked for update on the issue.
- 31-12-2013: Received reply that the last iOS 6 capable release will not receive further updates, and the latest version for iOS 7 does not declare specific NSFileProtection attributes.
- 02-01-2014: Received request to rate the support I had received in this ticket.