AppSec Blog

AppSec Blog

How Not to Store Passwords in iOS

The WordPress iOS App


I was looking for an open source iOS application and quickly came across the WordPress app. Once you log in to your WordPress blog via the app your credentials are then stored on the device itself. If done correctly this is not necessarily a bad thing. However, the WordPress app's implementation leaves a bit to be desired. Take a look at the following snippet of code from WPcomLoginViewController.m and in Spot the Vuln fashion see if you can find the issue.

Update: The WordPress for iOS team has already committed a change for the next release to address the issue. Check out their comments below.

...snip... - (void)saveLoginData { if(![username isEqualToString:@""]) [[NSUserDefaults standardUserDefaults] setObject:username forKey:@"wpcom_username_preference"]; if(![password isEqualToString:@""]) [[NSUserDefaults standardUserDefaults] setObject:password forKey:@"wpcom_password_preference"]; if(isAuthenticated) [[NSUserDefaults standardUserDefaults] setObject:@"1" forKey:@"wpcom_authenticated_flag"]; else [[NSUserDefaults standardUserDefaults] setObject:@"0" forKey:@"wpcom_authenticated_flag"]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (void)clearLoginData { [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"wpcom_username_preference"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"wpcom_password_preference"]; [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"wpcom_authenticated_flag"]; [[NSUserDefaults standardUserDefaults] synchronize]; } - (BOOL)authenticate { if([[WPDataController sharedInstance] authenticateUser:WPcomXMLRPCUrl username:username password:password] == YES) { isAuthenticated = YES; [self saveLoginData]; } else { isAuthenticated = NO; [self clearLoginData]; } return isAuthenticated; }

As you can see from the saveLoginData method above, (NSUserDefaults *)standardUserDefaults is used to store the username, password, and an authenticated flag after the user successfully signs on. To see this code in action simply logon to the app then go to Settings -> WordPress on the device to see your stored credentials as shown in the following screen shot.


The problem is that standardUserDefaults stores information in a plist file in plain text.

If you're using the iOS Simulator to run the WordPress app the org.wordpress.plist file is stored at ~/Library/Application Support/iPhone Simulator/4.2/Applications/<APP_ID>/Library/Preferences where <APP_ID> is a randomly generated string.

If you're running the app on an actual iOS device and perform a backup (I did this with my iPad) the plist file is located
at ~/Library/Application Support/MobileSync/Backup/<DEVICE_ID> where <DEVICE_ID> is a random string.

The plist file will also have a random name but you can simply run grep wpcom_password_preference * to find the file that contains your password.


Figure 1: Screenshot showing the plaintext credentials stored in the plist file

Using the Keychain


Sensitive data like passwords and keys should be stored in the Keychain. Apple's Keychain Services Programming Guide states that a "keychain is an encrypted container that holds passwords for multiple applications and secure services. Keychains are secure storage containers, which means that when the keychain is locked, no one can access its protected contents". Moreover, in iOS, each application only has access to its own keychain items.

You interact with the Keychain by passing in a dictionary of key-value pairs that you want to find or create. Each key represents a search option or an attribute of the item in the keychain. For example, the following code shows how you can add a new item to the keychain.

NSMutableDictionary *query = [NSMutableDictionary dictionary]; [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; [query setObject:account forKey:(id)kSecAttrAccount]; [query setObject:(id)kSecAttrAccessibleWhenUnlocked forKey:(id)kSecAttrAccessible]; [query setObject:[inputString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData]; OSStatus error = SecItemAdd((CFDictionaryRef)query, NULL);

The code first creates a NSMutableDictionary then simply sets the appropriate key-value pairs in the dictionary. We start by adding the kSecClass key with the value kSecClassGenericPassword which, as the name implies, means that we are adding a generic password to the keychain. The kSecAttrAccount key specifies the account whose password we're storing. In our case it would be for the "iostesting" username we saw above. The kSecValueData key specifies the password we want to store in the keychain. In our case the inputString variable would contain the password "princess123" that is in the screen shot above. Finally, calling the SecItemAdd function adds the data to the keychain.

Retrieving, updating, and deleting items from the keychain can be done using other functions [1]. To see how it works feel free to use this sample code that is based on work by Michael Mayo.

Protection Attributes


When you are adding or updating keychain items via the SecItemAdd or SecItemUpdate functions you should also specify the appropriate protection attribute. You may have noticed the kSecAttrAccessible key with the value kSecAttrAccessibleWhenUnlocked in the code above. This specifies that the keychain item can only be accessed while the device is unlocked. In total, there are six constants which were introduced in iOS 4.0 that you can use to specify when an item in the keychain should be readable.


AttributeData is...
kSecAttrAccessibleWhenUnlockedOnly accessible when device is unlocked.
kSecAttrAccessibleAfterFirstUnlockAccessible while locked. But if the device is restarted it must first be unlocked for data to be accessible again.
kSecAttrAccessibleAlwaysAlways accessible.
kSecAttrAccessibleWhenUnlockedThisDeviceOnlyOnly accessible when device is unlocked. Data is not migrated via backups.
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnlyAccessible while locked. But if the device is restarted it must first be unlocked for data to be accessible again. Data is not migrated via backups.
kSecAttrAccessibleAlwaysThisDeviceOnlyAlways accessible. Data is not migrated via backups.

 

Never use kSecAttrAccessibleAlways. If you are storing user data in the keychain it's safe to assume that it should be protected.

In general, you should always use at least kSecAttrAccessibleWhenUnlocked to ensure that data is not accessible when the device is locked. If you have to access data while the device is locked use kSecAttrAccessibleAfterFirstUnlock.

The three ...ThisDeviceOnly attributes should only be used if you are sure that you never want to migrate data from one device to another.

Summary


Mobile development brings both familiar and new challenges for creating secure applications. The iOS platform provides a number of strong security features with the Keychain and protection attributes introduced in iOS 4.0. As usual, it is up to developers, development teams, and security professionals to make sure that user data is handled securely and appropriate APIs are used correctly.

About


Frank Kim is the curriculum lead for application security at the SANS Institute and the author of DEV541 Secure Coding in Java. If you liked this post check out SANS' new class on Secure iOS App Development.

[1] See the Keychain Services Reference for full details on the API

18 Comments

Posted January 05, 2011 at 10:39 PM | Permalink | Reply

Isaac Keyet

Thanks for taking the time to go through this. Looks like we overlooked this security flaw when building the app and didn't realize it.

We're marking this as a Blocker-type issue which means the next version of the app (2.6.4) won't be released until this is fixed.

Posted January 05, 2011 at 10:50 PM | Permalink | Reply

Frank Kim

Thanks Isaac, that's great to hear!

Posted January 06, 2011 at 12:46 AM | Permalink | Reply

Josh Straub

If you Encrypt your Backups in iTunes (and you definitely should), your password is not stored in plaintext in the MobileSync folder.

As a bonus, Encrypting your iTunes Backups also is the only way to transfer your saved passwords (including voicemail password) if you ever Restore your Backup onto a different iPhone (for instance if your phone dies and you get a replacement).

Posted January 06, 2011 at 4:54 AM | Permalink | Reply

Zaxxon

The sad truth is that other applications (and far more critical ones that provide access to online passwords!) like RoboForm on the iPhone do the same and store the Roboform Online account credentials in cleartext on the device.

You might want to install roboform (I think it is free) and do the same analysis. The Roboform Online username+password just sits there in one of the XML files unencrypted.

Posted January 06, 2011 at 5:07 AM | Permalink | Reply

Zaxxon

Bit more info:
Roboform stores the credentials in cleartext in this file:
./Library/Preferences/com.sibersystems.RoboForm.plist

And this is supposedly one of the leading password aggregators. What a shame.

Posted January 06, 2011 at 5:32 PM | Permalink | Reply

Jorge Bernal

Thanks a lot for the article. This will be fixed in the next released.

You can see the fix in http://ios.trac.wordpress.org/changeset/1357

Right now, we are using the SFHFKeychainUtils class, which doesn't set the kSecAttrAccessible attribute.
I can't fully understand from the docs what are the implications of using kSecAttrAccessibleAlways vs the other options. Do you have more insight on that?

Posted January 06, 2011 at 5:55 PM | Permalink | Reply

Frank Kim

Hi Jorge,

I definitely recommend setting the kSecAttrAccessible attribute to ensure that credentials are not accessible while the device is locked. The documentation isn't all that clear but check out Session 209 - Securing Application Data from WWDC 2010 at http://developer.apple.com/videos/wwdc/2010/

It's a great talk where they cover some other important topics and they do a good job explaining why you really need to use the kSecAttrAccessible attribute.

Thanks for fixing this so quickly!

Frank

Posted September 27, 2011 at 10:14 PM | Permalink | Reply

mark

How did you guys fix it? I'm writing my own app, and I like the interface that NSUserDefaults provides.
I would love to find a way for the user to enter the password through the NSUserDefaults plist file and then somehow catch that and put it into a secure place....
Can you point me to the new code you guys are using?

Posted December 17, 2011 at 6:08 PM | Permalink | Reply

Cullen Linn

Thanks for the great article Frank!

Posted February 08, 2012 at 3:07 PM | Permalink | Reply

Scott C

Nice post! The first one on the topic that I actually understood. Thanks!

I had a concern about the uniqueness of the key, so I followed the links to Mike's website; where I found the following question:

"What happens if two different apps use this code and save their passwords using the same key (that is, password)."

Basically, that was my concern with the code you've linked too. It appears Mike came out with another version with a "appName" method that is used in the setString and getStringForKey.

I don't full understand all the code but I think yours would need the same update, right? Or not?

What lead me to it was Apple's KeychainItemWrapper initializes their object this way, so that every keychain item is unique per app:

wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"Account Number" accessGroup:@"YOUR_APP_ID_HERE.com.yourcompany.GenericKeychainSuite"];

Could you explain if yours doesn't need this change? Or can you add it? Thanks for the nice post. I appreciate the clarification.

Posted February 08, 2012 at 4:49 PM | Permalink | Reply

Aurelien

Thanks for this nice post, simple and clear :). I have discovered some useful tips

I have a question: What are good practices when we have to embed an username/password in an application (from scratch) ?

Thanks

Aurelien

Posted April 05, 2012 at 5:55 AM | Permalink | Reply

software2007

I opened up a single view app in ios5, Xcode 4.3, ARC, the source code didn't work for me as I got lots of linking error when I tried to run the program. Any ideas? Is there an updated version of the code?

Posted July 20, 2012 at 5:18 PM | Permalink | Reply

hans

I wonder...what if the device is not passcode protected and remains unlocked all the time....would that mean that kSecAttrAccessibleAfterFirstUnlock and kSecAttrAccessibleWhenUnlocked behave the same exact way as kSecAttrAccessibleAlways i.e. are always vulnerable to the exploit??
I think so but can't confirm it anywhere....

What do you think?

Posted July 20, 2012 at 6:21 PM | Permalink | Reply

Frank Kim

Hi Hans,

If there's no passcode that means that Data Protection is not enabled and the data is not encrypted.

Posted August 09, 2012 at 1:02 AM | Permalink | Reply

John M

Hi Frank

Is there a way to detect if the user has Data Protection enabled?

Posted August 12, 2012 at 11:22 PM | Permalink | Reply

peter

Hans if the iphone is not locked using a passcode then the items in the keychain are not encrypted. The passcode is used as an encryption key when adding or modifying items that require the phone to be unlocked.

Posted November 19, 2012 at 8:38 AM | Permalink | Reply

Saba Kazi

If a user uninstalls my app , and downloads the app using another itunes account. Our the keychain values reset ?

Posted March 14, 2014 at 1:14 PM | Permalink | Reply

ravenmarx

Hi,

I read full blog, I found it interesting, but because of technical languages, some part i could not understand, but whatever i understood, its perfect, your blog indirectly force me to read more and more <a href="http://www.stopdatabreaches.org">Data Breach</a>

Thanks,
ravenmarx

Post a Comment






* Indicates a required field.