Tags:
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.
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.
Attribute | Data is... |
kSecAttrAccessibleWhenUnlocked | Only accessible when device is unlocked. |
kSecAttrAccessibleAfterFirstUnlock | Accessible while locked. But if the device is restarted it must first be unlocked for data to be accessible again. |
kSecAttrAccessibleAlways | Always accessible. |
kSecAttrAccessibleWhenUnlockedThisDeviceOnly | Only accessible when device is unlocked. Data is not migrated via backups. |
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly | Accessible 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. |
kSecAttrAccessibleAlwaysThisDeviceOnly | Always 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.
[1] See the Keychain Services Reference for full details on the API