AeroGear Mobile Security
- 1. Mobile Security Recommendations Overview
- 2. Authentication and Session Management
- 3. Data Storage Summary
- 4. Network Security
- 5. Mobile App Self-Defence
- 6. Handling Sensitive Data Overview
- 7. Cryptography Summary
- 8. References
- 8.1. OWASP Top 10 Mobile Risks
- 8.2. Mobile Application Security Requirements
- 8.3. Secure Mobile Development Guidelines
- 8.4. OWASP Cheat Sheet Series
- 8.5. OWASP Mobile Verification Standard
- 8.6. OWASP Threat Model Project
- 8.7. OWASP M-Tools
- 8.8. iOS Security Whitepaper
- 8.9. Vetting the Security of Mobile Applications
- 8.10. NIAP Mobile App Vetting
- 8.11. Guidelines for Managing the Security of Mobile Devices in the Enterprise
- 8.12. Mobile Banking Applications Security Challenges
- 8.13. Secure Mobile Development Guide
2. Authentication and Session Management
| Do: | Do not: |
|---|---|
Perform Authentication on the Server Side. (The client side should never be trusted). |
Use the Fingerprint Scanner solely for Authentication. (Possible False Positives [1]). |
Use Randomly Generated access tokens instead of sending user credentials in requests. (At worst, a temporary session token can therefore only be stolen). |
Use Pattern Locks for Authentication. (Lack of Entropy, Vulnerable to Smudge Attacks [2]). |
A password policy is enforced at the remote endpoint. (Prevent easily guessable passwords from being used). |
Use a short Pin code solely authentication. (Easily brute forced and the end user may reuse the device security pin). |
Sessions are terminated at the remote endpoint after a predefined time. (Sessions token lifetimes should be kept short to limit the attack window if an valid session token is stolen). |
Do not use any values for authenticating a user like device identifiers or geo-location. (These can be spoofed easily). |
Temporarily lock out a user after a number of failed login attempts. (Prevent an endless brute force attack). |
Avoid relying on any roles or permission information that comes from the mobile device itself. (The client side and user input should not be trusted). |
Use contextual anomaly based detection to take into account the users IP, geographic location, time of day as part of the authentication phase. (Helps detect unusual login activity). |
Log user related information. (Personal information data leak) |
A second factor of authentication exists and is enforced. (Extra layer of protection in case another authentication factor has been compromised). |
Use OAuth1.0. (OAuth1.0 has been found to be vulnerable to session fixation. Use OAuth 1.0a or OAuth 2.0 instead). |
Step-up authentication is used for carrying out sensitive actions in an application. (Useful when a user’s device is stolen and they have already authenticated through the app). |
|
Allow the user to see their current login sessions and allow them to logout of other sessions via the app. (The end user will be able to detect rogue sessions with the most accuracy instead of using anomaly detection). |
|
Perform local integrity checks within the code to detect any unauthorized code changes before doing offline authentication. (Tampering may allow authentication bypasses). |
|
Where offline access to data is needed, perform an account/application lockout and/or application data wipe after X number of invalid password attempts. (Wipe application data when a threat is imminent). |
|
Perform a check at the start of each activity/screen to see if the user is in a logged in state and if not, switch to the login state. (A session could expire during use or over time. Also check that the user has a role that allows them to access to certain app view). |
|
When an application’s session is timed out, the application should discard and clear all memory associated with the user data, and any master keys used to decrypt the data. (Prevent data leakage after the user is no longer in an active session with the server). |
|
Store a masked user identifier instead of the actual username, and replace the user identifier value in authentication with a hash value. (If a username/id is being used in the mobile app for identification purposes only (not for display in the UI), then consider sending a related mapped token to the mobile device instead.). |
|
Implement Secure Password Recovery Mechanism. (To allow a user to recover their account using security questions only they should know the answers too and sending a recovery code using a side channel such as SMS). |
|
Fail authentication with a Generic Error. (The login screen should not show hints on the existence of a username/email address etc). |
2.1. References
-
The probability of a random person unlocking a given iPhone with Touch ID is 1 in 50,000. Apple Support
-
Penn State researchers managed to identify the pass code patterns on two smartphones, 68% of the time, using photographs taken under different lighting conditions, and camera positions. Smudge Attacks on Smartphone Touch Screens
2.2. Implementation
2.2.2. Native Android
An Authentication and Session Management implementation has been provided in our Mobile Security Android Template App.
The Application uses OpenID Connect for authentication and access control. The Open Source Identity and Access Management server, Keycloak, is being used as an Identity Provider. The Aerogear Auth SDK is being used for communicating with the Keycloak server.
The Keycloak server is enforcing a number of Security Controls on the mobile app.
-
Brute force detection on the login screen.
-
Account lockouts for failed login attempts.
-
2FA for mobile users.
-
Session management for mobile users.
-
Audit tracing of auth events.
2.2.3. Code Examples
The main code logic is found under mobile-security-android-template/…/authentication.
The following code snippets describe the main authentication code logic in the mobile app.
Configuration
The Keycloak configuration is saved in the mobile-services.json file.
+include::https://raw.githubusercontent.com/aerogear/android-showcase-template/eacf9f364346d3eaa0cfc40f37aa53ae2696e17e/app/src/main/assets/mobile-services.json[]+
The redirect url of the application is defined in android.defaultConfig.manifestPlaceholders
of the apps build.gradle file.
You will also need to define the URL in the SecureApplicationModule.
@Provides @Singleton
AuthService provideAuthService(Context context, MobileCore mobileCore) {
AuthService authService = mobileCore.getInstance(AuthService.class);
AuthServiceConfiguration authServiceConfig = new AuthServiceConfiguration.AuthConfigurationBuilder()
.withRedirectUri("com.feedhenry.securenativeandroidtemplate:/callback")
.build();
authService.init(context, authServiceConfig);
return authService;
}
Authentication
We can then perform the auth request and create a new intent which will handle the auth response from the system browser. The authentication phase will occur in a system browser in a safe context outside of the application.
/**
* Create the config for the initial Keycloak auth request to get a temporary token and create an intent to handle the response
*
* @param fromActivity the activity used to perform the login
* @param authCallback the authentication callback
*/
@Override
public void login(final Activity fromActivity, final Callback authCallback) {
// Build the options object and start the authentication flow. Provide an activity to handle the auth response.
DefaultAuthenticateOptions options = new DefaultAuthenticateOptions(fromActivity, LOGIN_RESULT_CODE);
authService.login(options, authCallback);
}
Handling the Authentication Response
Once the Authentication phase has ended, the user will be redirected back to the mobile app from the system browser. A check is performed in the MainActivity for incoming intents so the result can be handled by the authentication service.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == KeycloakAuthenticateProviderImpl.LOGIN_RESULT_CODE) {
// The core will return the same instance of the auth service as before
AuthService authService = MobileCore.getInstance().getService(AuthService.class);
authService.handleAuthResult(data);
}
}
Logout
To logout of the application, we must make a call to the logout endpoint on the OpenID Connect provider along with providing the identity token and a redirect URI.
/**
* Perform a logout request against the openid connect server
*
* @param logoutCallback the logout callback
*/
public void logout(final CallbackHandler logoutCallback) {
this.logoutCallback = logoutCallback;
UserPrincipal currentUser = authService.currentUser();
authService.logout(currentUser, new Callback<UserPrincipal>() {
@Override
public void onSuccess() {
logoutSuccess();
}
@Override
public void onError(Throwable error) {
logoutFailed(error);
}
});
}
2.2.4. Native iOS
An Authentication and Session Management implementation has been provided in our Mobile Security iOS Template App.
The Application uses OpenID Connect for authentication and access control. The Open Source Identity and Access Management server, Keycloak, is being used as an Identity Provider. The Aerogear Auth SDK is being used to communicate with the Keycloak server.
The Keycloak server config is defined in the mobile-services.json file.
+include::https://raw.githubusercontent.com/aerogear/ios-showcase-template/58efd7744fd57b27145d27f5ed4459dab89fc7fa/ios-showcase-template/mobile-services.json[]+
The startAuth()
function handles the user authentication browser flow and the back-channel token exchange.
func startAuth(presentingViewController: UIViewController) {
do {
try self.authService.login(presentingViewController: presentingViewController, onCompleted: onLoginCompleted)
} catch {
fatalError("Unexpected error: \(error)")
}
}
The resolveCurrentUser()
returns the user profile, which can be used to retrieve user information and the auth tokens.
func resolveCurrentUser() -> User? {
let authService = self.appComponents.resolveAuthService()
return try! authService.currentUser()
}
The following information can be retrieved from an authenticated user:
// Get the users username
let username = currentUser.userName
// Get the users first name
let firstName = currentUser.firstName
// Get the users last name
let lastName = currentUser.lastName
// Get the users full name
let fullName = currentUser.fullName
// Get the users email address
let emailAddress = currentUser.email
// Get the users access token
let accessToken = currentUser.accessToken
// Get the users identity token
let identityToken = currentUser.identityToken
Access Control is carried out by checking the access roles of the authenticated user.
func highlightUserRealmRoles(user: User) {
for realmRole in self.allRealmRoles {
if user.hasRealmRole(realmRole.roleName) {
setRoleAccessoryType(role: realmRole, type: UITableViewCellAccessoryType.checkmark)
} else {
setRoleAccessoryType(role: realmRole, type: UITableViewCellAccessoryType.none)
}
}
}
The logout()
function is used to logout the user from the Keycloak server. The local auth tokens are also deleted.
func logout() {
do {
try self.authService.logout(onCompleted: { error in
self.router?.leaveUserDetailsView(withError: error)
})
} catch {
fatalError("Unexpected error: \(error)")
}
}
2.2.5. Apache Cordova
The Keycloak JS library is being used to perform the OpenID Connect based authentication.
// Ensure that Keycloak is Initialised before Angular to prevent Redirect looping issues
KeycloakService.init()
.then(() => {
const platform = platformBrowserDynamic();
// Mamually intiliase angular
platform.bootstrapModule(AppModule);
})
.catch((err) => console.error("Error Initalizing Keycloak", err));
The OpenID Connect Configuration can be found in the /config/keycloak.json file.
+include::https://raw.githubusercontent.com/feedhenry/mobile-security-cordova-template/c24297cda240efa40aca292e7d0657d7432d1eba/src/config/keycloak.json[]+
The Keycloak JS adapter is initialised with the check-sso
config to automatically detect and authenticate the user if there is a valid session on app resume.
/**
* Initialise the Keycloak Client Adapter
*/
static init(): Promise<any> {
// Create a new Keycloak Client Instance
let keycloakAuth: any = new Keycloak(keycloakConfig);
return new Promise((resolve, reject) => {
keycloakAuth.init({onLoad: 'check-sso'}).success(() => {
KeycloakService.auth.authz = keycloakAuth;
resolve();
}).error((err) => {
reject(err);
});
});
}
The access token can be retrieved using the getToken()
function.
/**
* Get Access Token
*/
getToken(): string {
return KeycloakService.auth.authz.token;
}
The user can logout using the logout()
function.
/**
* Redirect to logout
*/
logout(): void {
KeycloakService.auth.authz.logout();
}
The user can be redirected to the login screen using the login()
function.
/**
* Redirect to Login
*/
login(): void {
KeycloakService.auth.authz.login();
}
The authentication state can ve cleared using the clearToken()
function.
/**
* Clears Authentication State
*/
clearToken(): void {
KeycloakService.auth.authz.clearToken();
}
The end users realm level roles can be retrieved using the getRealmRoles()
function.
/**
* Return the users realm level roles
*/
getRealmRoles(): void {
return KeycloakService.auth.authz.realmAccess.roles;
}
You can perform a check to see if the user has a given role using the hasRealmRole()
function.
/**
* Check if the user has a specified realm role
*/
hasRealmRole(role: String): boolean {
return KeycloakService.auth.authz.hasRealmRole(role);
}
Information about the authentication server can be carried out using the getConfiguration()
function.
/**
* Get Server/Open ID Connect specific server info
*/
getConfiguration(): object {
var notAvailable = "N/A";
return {
"authServerUrl": KeycloakService.auth.authz.authServerUrl ? KeycloakService.auth.authz.authServerUrl : notAvailable,
"openIdFlow": KeycloakService.auth.authz.flow ? KeycloakService.auth.authz.flow : notAvailable,
"openIdResponseMode": KeycloakService.auth.authz.responseMode ? KeycloakService.auth.authz.responseMode : notAvailable,
"openIdResponseType": KeycloakService.auth.authz.responseType ? KeycloakService.auth.authz.responseType : notAvailable,
"realm": KeycloakService.auth.authz.realm ? KeycloakService.auth.authz.realm : notAvailable,
"clientId": KeycloakService.auth.authz.clientId ? KeycloakService.auth.authz.clientId : notAvailable,
"timeSkew": KeycloakService.auth.authz.timeSkew ? KeycloakService.auth.authz.timeSkew : notAvailable
};
}
The user can be redirected to the Keycloak account management screen using the accountManagement()
function.
/**
* Redirects to the Account Management Console
*/
accountManagement(): void {
KeycloakService.auth.authz.accountManagement();
}
The users profile data can be retrieved using the loadUserProfile()
function.
/**
* Get the users profile
*/
loadUserProfile(): any {
// Retrieve User Profile
return new Promise((resolve, reject) => {
KeycloakService.auth.authz.loadUserProfile().success((profile) => {
resolve(<object>profile);
}).error(() => {
reject('Failed to retrieve user profile');
});
});
}
You can check if the user is authenticated using the isAuthenticated()
function.
/**
* Check if the user is authenticated
*/
isAuthenticated(): boolean {
return KeycloakService.auth.authz.authenticated;
}
3. Data Storage Summary
Do: |
Do not: |
Classify data storage according to sensitivity and apply controls accordingly (To understand what information should be protected) |
Store any sensitive data on a mobile device where possible (Prevent data leak). |
When storing sensitive data on the device, use a file encryption API provided by the OS or other trusted source (Secure Cryptography) |
Write data to persistent memory accessible to other applications without encryption (Shared storage is untrusted) |
Use file encryption APIs which use a secret key protected by the device unlock code and deletable on remote wipe if available (Protect secret key) |
Including the keys in the same attacker-readable directory as the encrypted content (Can be used to decrypt data) |
Make use of remote wipe and kill switch APIs to remove sensitive information from the device in the event of theft or loss (Prevent data leak if the device is lost) |
Take screenshots contain sensitive data (Avoid sensitive data is exposed through screenshots) |
Use a time based (expiry) type of control which will wipe sensitive data from the mobile device once the application has not communicated with its servers for a given period of time (Less chance to decrypt data by using brute force) |
Leak permission-protected data to other applications (Prevent other apps access sensitive data) |
Automatic application shutdown and/or lockout after X minutes of inactivity (Less chance of data leak) |
Allow third party keyboards to be used for inputs that may contain sensitive data (Prevent data leak) |
Verify that OS level storage encryption is enabled and the device is protected by a PIN or passphrase (Ensure data encryption by default) |
Backup or synchronize sensitive data (Prevent data leak) |
Restrict the data that is shared with other applications (Prevent other apps access sensitive data) |
Log sensitive information (Prevent data leak through logs) |
Nullify any variables that hold keys after use (Secure keys in memory) |
using immutable objects for sensitive keys or passwords such as in Android java.lang.String and use char array instead (Secure keys in memory) |
Restrict broadcast messages (e.g., Android Broadcast Intents) to authorized applications (Prevent other apps access sensitive data) |
|
Disable Auto Correction and Autosuggestion for inputs that contain sensitive data (Prevent data leak) |
|
Disable cut, copy and paste functionalities for inputs that may contain sensitive data or restrict the pasteboard to be accessible only from this application (Prevent data leak) |
|
Delete application caches on app termination (Prevent data leak) |
|
Clear cookies and data in Webview on app termination (Prevent data leak) |
|
Remove sensitive data from views when in background (Prevent data leak) |
3.1. Implementation
3.1.1. Overview
We have implemented a secure storage feature in the sample mobile templates to allow users to manage secret notes. This feature is used to showcase the best practices around secure data storage.
3.1.2. Native Android
The secret notes can be saved to either file system or database (SQLite). In both cases, the notes will be encrypted automatically when persisted, and decrypted automatically when loaded into the app.
Secret Key Management
Secret keys are being used to encrypt/decrypt the notes. To make sure the secret keys are securely persisted, in most cases, the OS’s keystore should be used to persist the secret key.
However, for Android, there is a difference between Android M and pre-Android M.
Android M and later
In Android M and later versions, the KeyStore API supports generating and persist secret key, so the implementation is quite straight forward:
/**
* Generate the AES key for encryption/decryption. The key will be 128bit and it can only be used with AES/GCM/NoPadding mode.
* @param keyAlias the key alias
* @throws GeneralSecurityException
* @throws IOException
*/
@Override
public void generateAESKey(String keyAlias) throws GeneralSecurityException, IOException {
KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
//TODO: further control if user authentication is required for accessing the keys
KeyGenParameterSpec keyGenerationParameters = new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(AES_KEYSIZE_128)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setRandomizedEncryptionRequired(true)
.build();
keyGenerator.init(keyGenerationParameters);
keyGenerator.generateKey();
}
Pre-Android M
However, in pre-Android M, the KeyStore API only supports generating public/private key pairs, so we can’t persist the secret key using the keystore.
Instead, we will have to:
-
Generate the secret key without using the KeyStore
Generate Secretprivate byte[] generateSecretKey(int size) throws NoSuchAlgorithmException { KeyGenerator keyGen = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); keyGen.init(size); SecretKey secretKey = keyGen.generateKey(); return secretKey.getEncoded(); }
-
Generate a public/private key pair using the KeyStore
Generate Keypair@Override public void generatePrivateKeyPair(String keyAlias) throws GeneralSecurityException, IOException { //pre android-M, the keystore only support RSA key generation. So here we will generate a RSA keypair first, then generate the AES key. //we then encrypt the AES key using the generated RSA public key, and save it using the SharedPreferences Calendar start = Calendar.getInstance(); Calendar end = Calendar.getInstance(); end.add(Calendar.YEAR, 99); KeyPairGeneratorSpec generatorSpec = new KeyPairGeneratorSpec .Builder(context) .setAlias(keyAlias) .setSubject(new X500Principal("CN=" + keyAlias)) .setSerialNumber(BigInteger.TEN) .setStartDate(start.getTime()) .setEndDate(end.getTime()) .build(); KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE); generator.initialize(generatorSpec); generator.generateKeyPair(); }
-
Encrypt the generated secret key with the generated public key and persist the encrypted secret key using private SharedPreferences.
Encrypt Secret@Override public void generateAESKey(String keyAlias) throws GeneralSecurityException, IOException { byte[] secretKey = generateSecretKey(AES_KEYSIZE_128); byte[] encryptedKey = rsaEncrypt(secretKey); String encodedSecretKey = Base64.encodeToString(encryptedKey, BASE64_FLAG); SharedPreferences.Editor editor = this.sharedPreferences.edit(); editor.putString(keyAlias, encodedSecretKey); editor.commit(); }
-
To load the secret key, the generated key pair needs to be loaded first, and then use the private key to decrypt the encrypted secret key.
Decrypt Secret@Override public Key getSecretKey(String keyAlias) throws GeneralSecurityException, IOException { String encodedKey = this.sharedPreferences.getString(keyAlias, null); if (encodedKey != null) { byte[] encryptedKeyBytes = Base64.decode(encodedKey, BASE64_FLAG); byte[] keyBytes = rsaDecrypt(encryptedKeyBytes); return new SecretKeySpec(keyBytes, KeyProperties.KEY_ALGORITHM_AES); } return null; }
The alternative option is to ask the user to provide a password. The password will be used to encrypt/decrypt the secret key. This does mean that the user will have to remember the password and enter it every time when using the app.
Encrypt/decrypt the secret key
RSA encryption/decryption is used to encrypt/decrypt the secret key. It can be used to encrypt/decrypt relatively small amount of data.
You can perform RSA encryption/decryption in Android like this:
/**
* Perform encryption using RSA
* @param mode the RSA encryption alg
* @param keyEntry the private/public key entry
* @param text the data to encrypt
* @return the encrypted ddata
* @throws GeneralSecurityException
* @throws IOException
*/
public static byte[] encrypt(String mode, KeyStore.PrivateKeyEntry keyEntry, byte[] text) throws GeneralSecurityException, IOException {
// Encrypt the text
Cipher inputCipher = Cipher.getInstance(mode);
inputCipher.init(Cipher.ENCRYPT_MODE, keyEntry.getCertificate().getPublicKey());
//The key to encrypt should be either 16 (128 bit) or 32 (256 bit) in size, well below the block size for RSA (should be around 214 bytes)
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, inputCipher);
cipherOutputStream.write(text);
cipherOutputStream.close();
byte[] vals = outputStream.toByteArray();
return vals;
}
/**
* Encrypt the given text.
* @param keyAlias The alias of the key in the keystore that will be used for the encryption.
* @param plainText the text to encrypt
* @return the encrypted data. The first 12 bytes will be the IV (initial vector) used for the encryption.
* @throws GeneralSecurityException
* @throws IOException
*/
public byte[] encrypt(String keyAlias, byte[] plainText) throws GeneralSecurityException, IOException {
SecretKey secretKey = loadOrGenerateSecretKey(keyAlias, true);
Cipher cipher = Cipher.getInstance(secureKeyStore.getSupportedAESMode());
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
//get the iv that is being used
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(plainText);
GCMEncrypted encryptedData = new GCMEncrypted(iv, encrypted);
return encryptedData.toByteArray();
}
Encrypt/decrypt the content of notes
Once we have the secret key, we can use it to encrypt/decrypt the content of the notes using AES.
You can perform data encryption/decryption in Android like this:
/**
* Encrypt the given text.
* @param keyAlias The alias of the key in the keystore that will be used for the encryption.
* @param plainText the text to encrypt
* @return the encrypted data. The first 12 bytes will be the IV (initial vector) used for the encryption.
* @throws GeneralSecurityException
* @throws IOException
*/
public byte[] encrypt(String keyAlias, byte[] plainText) throws GeneralSecurityException, IOException {
SecretKey secretKey = loadOrGenerateSecretKey(keyAlias, true);
Cipher cipher = Cipher.getInstance(secureKeyStore.getSupportedAESMode());
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
//get the iv that is being used
byte[] iv = cipher.getIV();
byte[] encrypted = cipher.doFinal(plainText);
GCMEncrypted encryptedData = new GCMEncrypted(iv, encrypted);
return encryptedData.toByteArray();
}
/**
* Decrypt the given encrypted data
* @param keyAlias The alias of the key in the keystore that will be used for the decryption.
* @param encryptedText the text to decrypt. The first 12 bytes should be the IV used for encryption.
* @return the plain text data
* @throws GeneralSecurityException
* @throws IOException
*/
public byte[] decrypt(String keyAlias, byte[] encryptedText) throws GeneralSecurityException, IOException {
GCMEncrypted encryptedData = GCMEncrypted.parse(encryptedText);
SecretKey secretKey = loadOrGenerateSecretKey(keyAlias, false);
Cipher cipher = Cipher.getInstance(secureKeyStore.getSupportedAESMode());
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(GCMEncrypted.GCM_TAG_LENGTH, encryptedData.iv));
byte[] plainText = cipher.doFinal(encryptedData.encryptedData);
return plainText;
}
Persist data using file system
When a secret note is created, it can be persisted to the file system. We should encrypt the file when it is being persisted, and decrypt it when it is being loaded:
/**
* Encrypt the file when saving to the file system
* @param fileName the name of the file
* @param fileContent the content of the file
* @throws IOException
* @throws GeneralSecurityException
*/
private void writeFileWithEncryption(String fileName, String fileContent) throws IOException, GeneralSecurityException {
File outputFile = new File(context.getFilesDir(), fileName);
if (!outputFile.exists()) {
outputFile.createNewFile();
}
OutputStream outStream = aesCrypto.encryptStream(fileName, new FileOutputStream(outputFile));
outStream.write(fileContent.getBytes("utf-8"));
outStream.flush();
outStream.close();
}
/**
* Read the content of the file, and decrypt it automatically
* @param fileName the name of the file
* @return the decrypted file content
* @throws IOException
* @throws GeneralSecurityException
*/
private String readFileWithDecryption(String fileName) throws IOException, GeneralSecurityException {
InputStream inputStream = context.openFileInput(fileName);
InputStream decryptedStream = aesCrypto.decryptStream(fileName, inputStream);
return StreamUtils.readStream(decryptedStream);
}
Persist data using database (SQLite)
To securely persist data to the database, a library called SqlCipher is used. The library will automatically manage the encryption/decryption of the sqlite database file. It is easy to use, and requires minimum changes to the code.
To encrypt/decrypt the database, a password is required. You can ask the user to provide the password. In our sample application, we generated a random password and then protected using a public/private key pair. The password is saved to the private SharedPreferences.
/**
* Get the password to protect the database
* @return the password
* @throws GeneralSecurityException
* @throws IOException
*/
private String getDbPassword() throws GeneralSecurityException, IOException {
String encryptedDbPass = this.sharedPreferences.getString(DB_KEY_PREF_NAME, null);
if (encryptedDbPass == null) {
String passwordToEncrypt = randomPassword();
encryptedDbPass = Base64.encodeToString(rsaCrypto.encrypt(ENCRYPT_KEY_ALIAS, passwordToEncrypt.getBytes("utf-8")), Base64.NO_WRAP);
SharedPreferences.Editor editor = this.sharedPreferences.edit();
editor.putString(DB_KEY_PREF_NAME, encryptedDbPass).commit();
}
String password = new String(rsaCrypto.decrypt(ENCRYPT_KEY_ALIAS, Base64.decode(encryptedDbPass, Base64.NO_WRAP)), "utf-8");
return password;
}
3.1.3. Native iOS
The secret notes are being persisted using an encrypted Realm Mobile DB.
Initialising Realm DB
The Realm DB storing the notes must be initialised using the encryption key.
/**
- Initilise the Realm Storage Service
- Parameter kcWrapper: the swift keychain wrapper
*/
init(kcWrapper: KeychainWrapper, encryptionKey: Data) {
self.keychainWrapper = kcWrapper
self.encryptionKey = encryptionKey
}
The encryption key is stored in the iOS Keychain.
/**
- Get the encryption key for the realm db
- Parameter kcWrapper: the keychain wrapper instance
- Parameter keychainAlias: the refernence alias for the encryption key stored in the keychain
- Returns: the encryption key
*/
class func getEncryptionKey(kcWrapper: KeychainWrapper, keychainAlias: String) -> Data? {
guard let encryptionKey = kcWrapper.data(forKey: keychainAlias) else {
let newEncryptionKey = generateEncryptionKey()
kcWrapper.set(newEncryptionKey, forKey: keychainAlias)
return newEncryptionKey
}
return encryptionKey
}
When the application is initialised for the first time, a new encryption key is randomly generated.
/**
- Generate an encryption key for the realm db
- Returns: An encryption key
*/
class func generateEncryptionKey() -> Data {
let byteLength = 64
let randomNumberSize = 1000000
let bytes = [UInt32](repeating: 0, count: byteLength).map { _ in arc4random_uniform(UInt32(randomNumberSize)) }
let data = Data(bytes: bytes, count: byteLength)
return data
}
Interacting with Realm DB
Interactions with the Realm DB are performed on background threads. Completion handlers are used to pass information to the view controllers on the Main thread.
Listing Notes
List all the notes.
/**
- List the stored entities from the realm db
- Parameter onComplete - a closure called after retrieval
*/
func list(onComplete: @escaping (Error?, [Note]?) -> Void) {
DispatchQueue.global(qos: .background).async {
do {
// create a new thread confined realm
let realm = try self.getRealmInstance()
// synchronization of Realm instance to the latest version on the background thread
realm.refresh()
// retieve the notes from realm
let notesResult = realm.objects(Note.self)
// create a new thread safe reference for these notes
let threadSafeNotes = ThreadSafeReference(to: notesResult)
// return note to the view controller on the main thread
DispatchQueue.main.async() {
let realm = try! self.getRealmInstance()
// get the notes using the thread safe reference
guard let notes = realm.resolve(threadSafeNotes) else {
return
}
// convert the notes to array format
let notesArray = notes.toArray()
onComplete(nil, notesArray)
}
} catch {
onComplete(error, nil)
}
}
}
Creating a Note
Create an individual note.
/**
- Create the stored entity
- Paramater title: the title of the note
- Paramater content: the content of the note
- Parameter onComplete - a closure called after creation
*/
func create(title: String, content: String, onComplete: @escaping (Error?, Note?) -> Void) {
var id = generateId()
let createdAt = getDate()
// ensure the ID is unique
while !isIdentifierUnique(identifier: id) {
id = generateId()
}
// create the note object
let note = Note()
note.id = id
note.title = title
note.content = content
note.createdAt = createdAt
note.storageProvider = "Realm"
DispatchQueue.global(qos: .background).async {
// create the note in the db
do {
// create a new thread confined realm
let realm = try self.getRealmInstance()
try realm.safeWrite {
realm.add(note)
try! realm.commitWrite()
onComplete(nil, note.clone())
}
} catch {
onComplete(error, nil)
}
}
}
Read a Note
Read an individual note.
/**
- Read the stored entity
- Parameter identifier: The identifier of the note
- Parameter onComplete - a closure called after retrieval
*/
func read(identifier: Int, onComplete: @escaping (Error?, Note?) -> Void) {
DispatchQueue.global(qos: .background).async {
do {
// create a new thread confined realm
let realm = try self.getRealmInstance()
// retrieve the notes from realm
let noteResult = realm.objects(Note.self).filter("id = \(identifier)")
// create a new thread safe reference for these notes
let threadSafeNotes = ThreadSafeReference(to: noteResult)
// return note to the view controller on the main thread
DispatchQueue.main.async() {
let realm = try! self.getRealmInstance()
// get the notes using the thread safe reference
guard let notes = realm.resolve(threadSafeNotes) else {
return
}
// extract the note from the list
let note = notes.first
onComplete(nil, note?.clone())
}
} catch {
onComplete(error, nil)
}
}
}
Edit a Note
Edit an individual note.
/**
- Edit the stored entity
- Parameter identifier: The identifier of the note
- Parameter title: The title of the note
- Parameter content: The content of the note
- Parameter onComplete - a closure called after editing
*/
func edit(identifier: Int, title: String, content: String, onComplete: @escaping (Error?, Note?) -> Void) {
DispatchQueue.global(qos: .background).async {
do {
// create a new thread confined realm
let realm = try self.getRealmInstance()
// get the existing note to update
let notes = realm.objects(Note.self).filter("id = \(identifier)")
let note = notes.first
// update the note with the given id
try realm.write {
note?.title = title
note?.content = content
}
onComplete(nil, note?.clone())
} catch {
onComplete(error, nil)
}
}
}
Delete a Note
Delete an individual note.
/**
- Delete the stored entity
- Parameter identifier: The identifier of the note
- Parameter onComplete - a closure called after deletion
*/
func delete(identifier: Int, onComplete: @escaping (Error?, Note?) -> Void) {
DispatchQueue.global(qos: .background).async {
do {
// create a new thread confined realm
let realm = try self.getRealmInstance()
// get the note to delete
let noteToDeleteResult = realm.objects(Note.self).filter("id = \(identifier)")
let note = noteToDeleteResult.first
let returnNote = note?.clone()
// delete the note
try realm.write {
realm.delete((note)!)
}
onComplete(nil, returnNote)
} catch {
onComplete(error, nil)
}
}
}
Delete all Notes
Delete all notes.
/**
- Delete all stored entitities
- Parameter onComplete - a closure called after deletion
*/
func deleteAll(onComplete: @escaping (Error?, Bool?) -> Void) {
DispatchQueue.global(qos: .background).async {
do {
// create a new thread confined realm
let realm = try self.getRealmInstance()
let notes = realm.objects(Note.self)
// delete all the notes
try realm.write {
realm.delete(notes)
}
onComplete(nil, true)
} catch {
onComplete(error, nil)
}
}
}
4. Network Security
| Do: | Do not: |
|---|---|
Include a Backup Cert Pin. (Retain app connectivity if new keys on server are used or the CA is changed). |
Pin the full certificate. (The full cert, and potentially backup certs are available for inspection, the certificate is bundled with the app meaning that when a cert expires the app must be rebuilt with a new cert). |
Perform Certificate Pinning using the hash of the public key - SubjectPublicKeyInfo of the X.509 certificate. (Allows you to anonymize a certificate or public key and ensures an attacker does not see the reserved certificate or public key in advance of its use). |
Send sensitive data without certificate pinning. (Creates higher risk as an attacker with network privileges, or who has compromised TLS, is better positioned to intercept data). |
Perform proper certificate validation if the platform doesn’t allow true certificate pinning. (Adds an extra precautionary check when sending sensitive data). |
Establish connections with endpoints that offer a different certificate or key, even if signed by a trusted CA. (The channel is not trusted.) |
Use TLS for network encryption. (SSL 2.0 and 3.0 have been deprecated by the IETF). |
Use Mixed SSL sessions. (The users session ID can be exposed over non HTTPS requests). |
Use a secure channel consistently throughout the app. (Ensure that some network calls are not sent in plaintext). |
Use localhost network ports for handling sensitive IPC. (The interface is accessible by other applications on the device). |
Verify that the app doesn’t rely on a single insecure communication channel such as email or SMS for critical operations, such as enrollments and account recovery. (Use a hardware token or secure channel as GCM or Apple Push notifications). |
Don’t rely on unauthenticated SMS data to perform sensitive commands. (The input may be malicious, be spoofed or sniffed using another app with SMS read permissions). |
Verify that the app uses some form of whitelisting to control access to external domains. (Limit the outbound requests to predefined trusted domains). |
Avoid sending crash logs over the network in plaintext. (Logs can contain sensitive or important information about the user and/or the application). |
Pay particular attention to validating all data received from and sent to non-trusted third party apps/plugins before incorporating their use into an application. (Some plugins can interact with ad networks etc). |
|
Consider using HTTP Strict Transport Security (HSTS). (Protect against protocol downgrade attacks and cookie hijacking). |
|
Make sure that you don’t trust data downloaded from HTTP or other insecure protocols. (Validate the data before using it in an application). |
|
Require User Approval before Executing a Function that Transmits PII over a Network. (Inform the user that their data is being transferred over a network or being sent to another location). |
|
For highly sensitive values, implement additional encryption in transit. (Improved protection in case the secure channel somehow compromised). |
4.1. Implementation
4.1.1. Overview
A number of features were added to enhance the security of the channel between the Mobile App and the Authentication or API Server.
4.1.2. Android
Certificate Pinning
The main code logic for certificate pinning is found under here in the CertPinningHelper class.
Certificate Pinning has been implemented in the Application using the SPKI technique. This ensures easier maintenance and improved security by anonymizing the certificate information in the application. The TrustKit Android library is being utilised to allow certificate pinning to work with older API levels.
The Android Network Security Configuration XML file defined the pins of the server you want to pin against.
+include::https://raw.githubusercontent.com/feedhenry/mobile-security-android-template/bbb41ca86b52b2e9fd380097580a8d946258827c/app/src/main/res/xml/network_security_config.xml[]+
You can get the pin for a website using OpenSSL:
openssl s_client -servername www.example.com -connect www.example.com:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
A HTTP request function has been created to perform requests to the Authentication servers. The createRequest()
functions performs pinning verification on the requests.
/**
* Make a request to a resource that requires the access token to be sent with the request
*
* @param requestUrl the request URL
* @param sendAccessToken boolean on whether to send the access token as part of the request
* @param callback the OkHTTP callback for the request
*/
public Call createRequest(final String requestUrl, final boolean sendAccessToken, final okhttp3.Callback callback) {
URL url = null;
try {
url = new URL(requestUrl);
} catch (MalformedURLException e) {
e.printStackTrace();
}
String serverHostname = url.getHost();
HttpsURLConnection connection = null;
try {
connection = (HttpsURLConnection) url.openConnection();
} catch (IOException e) {
e.printStackTrace();
}
SSLSocketFactory sslSocketFactory = TrustKit.getInstance().getSSLSocketFactory(serverHostname);
X509TrustManager trustManager = TrustKit.getInstance().getTrustManager(serverHostname);
connection.setSSLSocketFactory(sslSocketFactory);
OkHttpClient httpClient = HttpHelper.getHttpClient()
.sslSocketFactory(sslSocketFactory, trustManager)
.connectTimeout(10, TimeUnit.SECONDS)
.build();
Request request;
if (sendAccessToken) {
String accessToken = authService.currentUser().getAccessToken();
request = new Request.Builder()
.url(url)
.addHeader("Authorization", String.format("Bearer %s", accessToken))
.build();
} else {
request = new Request.Builder()
.url(url)
.build();
}
Call call = httpClient.newCall(request);
call.enqueue(callback);
return call;
}
A function has also been provided to check if the OkHTTP callback failure exception was due to a certificate validation issue.
/**
* Check if an exception is caused by a certificate verification error
*
* @param error the error exception from a failed request
*
* @return boolean based on whether or not certificate pinning has failed
*/
public boolean checkCertificateVerificationError(final Exception error) {
boolean certificateVerificationError = false;
if (error.getCause() != null &&
(error.getCause().toString().contains("Certificate validation failed") ||
error.getCause().toString().contains("Pin verification failed"))) {
certificateVerificationError = true;
}
return certificateVerificationError;
}
Invoking remote APIs
When remote APIs are being invoked, before sending the data, you should make sure the client does have the required permission. If the client doesn’t have the required permission, the client should not perform the request at all:
@Override
protected ClientStatus doInBackground(Void... voids) {
ClientStatus status = null;
try {
UserPrincipal user = authService.currentUser();
if (user != null ) {
boolean hasPermission = user.hasRealmRole(requiredRole);
long numberOfNotes = noteRepository.count();
status = new ClientStatus(hasPermission, numberOfNotes);
return status;
}
} catch (Exception e) {
Log.e(TAG, "Error - Exception", e);
this.error = e;
}
return status;
}
However, just checking the permission on the client is not enough. You should always check the user permission on the backend API server as well:
const keycloak = new Keycloak({ store: memoryStore }, config.keycloak);
app.use(keycloak.middleware());
app.use('/', keycloak.protect('realm:api-access'), routes);
You should also perform certificate pinning checks when calling remote APIs:
@Override
protected Long doInBackground(Void... voids) {
String apiUrl = apiServerConfiguration.getNoteAPIUrl();
UserPrincipal user = authService.currentUser();
if (user != null ) {
String accessToken = user.getAccessToken();
try {
List<Note> notes = noteRepository.listNotes();
long totalNumber = notes.size();
final AtomicLong currentCount = new AtomicLong(0);
for (Note note : notes) {
if (isCancelled() || this.error != null) {
break;
}
Note readNote = noteRepository.readNote(note.getId());
HttpRequest httpRequest = mobileCore.getHttpLayer().newRequest();
httpRequest.addHeader("Authorization", String.format("Bearer %s", accessToken));
httpRequest.post(apiUrl, readNote.toJson(true).toString().getBytes("UTF-8"));
HttpResponse httpResponse = httpRequest.execute();
httpResponse.onError(() -> {
UploadNotesTask.this.error = new Exception(httpResponse.stringBody());
});
httpResponse.onSuccess(() -> {
uploaded.incrementAndGet();
});
httpResponse.onComplete(() -> {
long progress = currentCount.incrementAndGet();
publishProgress(progress, totalNumber);
});
httpResponse.waitForCompletionAndClose();
}
} catch (Exception e) {
Log.e(TAG, "Error - Exception", e);
this.error = e;
}
}
return uploaded.longValue();
}
4.1.3. iOS
Certificate Pinning
The Certificate Pinning Service and the AppDelegate contain the main code for configuring and performing certificate pinning. The TrustKit library is being used to perform SPKI Certificate Pinning. The pin is a Base64 encoded Subject Public Key Information fingerprint from an X.509 Certificate.
You can get the pin for a website using OpenSSL:
openssl s_client -servername www.example.com -connect www.example.com:443 | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
TrustKit can be configured and initialised from the AppDelegate file. TrustKit is configured below to perform swizzling of the App’s NSURLConnection and NSURLSession delegates in order to automatically add pinning validation to the App’s HTTPS connections.
// Define TrustKit configuration
let trustKitConfig = [
kTSKSwizzleNetworkDelegates: true,
kTSKPinnedDomains: [
"security.feedhenry.org": [
kTSKIncludeSubdomains : true,
kTSKEnforcePinning : true,
kTSKPublicKeyAlgorithms: [kTSKAlgorithmRsa2048],
kTSKPublicKeyHashes: [
"trENjoQnbWupnAtu1/WagBE0RgJ+p7ke2ppWML8vAl0=",
"arENjoQnbWupnAtu1/WagBE0RgJ+p7ke2ppWML8vAl0="
],]]] as [String : Any]
// Init TrustKit with the above config
TrustKit.initSharedInstance(withConfiguration: trustKitConfig)
The performValidCertCheck()
function in the Certificate Pinning Service can be used to manually check if a request to a host fails due to certificate pinning validation issues.
/*
- Checks if the servers presented cert matches a pin from the pinset
- Parameter url - the url of the host to check. If no URL is provided, the auth server will be tested by default.
- Parameter onCompleted - a completion handler that returns the result of the cert check. A true value means that the cert pinning validated successfully. A false value means there was a validation issue which resulted in a pin verification failure.
*/
func performValidCertCheck(url:String? = nil, onCompleted: @escaping (Bool) -> Void) {
let serviceURL = AgsCore.instance.getConfiguration("keycloak")?.url
let requestURL = url ?? "\(serviceURL)/auth/"
Alamofire.request(requestURL).validate(statusCode: 200..<300).responseData(completionHandler: {response in
switch response.result {
case .success:
onCompleted(true)
case .failure(let error):
if(!self.checkPinningFailed(error: error)) {
// error is not cert pinning related
onCompleted(true)
} else {
onCompleted(false)
}
}
})
}
The outcome of the certificate checks can then be used to warn the user if a secure channel is not available. In the example below, pinning is performed when the authentication button is pressed. If there are certificate validation issues, the end user will not be allowed to authenticate and the UI is updated to warn them that their connection to the remote server is not secure.
@IBAction func onAuthButtonTapped(_ sender: UIButton) {
// perform cert pinning on the auth server when the auth button is pressed
if let listener = self.authListener {
listener.performPreCertCheck() {
validCert in
if(validCert) {
// cert is valid, continue with login
listener.startAuth(presentingViewController: self)
} else {
// pin validation issues, update the UI to notify the user and prevent authentication.
self.authenticationButton.isHidden = true
self.certPinningError.isHidden = false
self.logoImage.isHidden = true
self.dangerLogo.isHidden = false
self.backgroundImage.image = UIImage(named: "ic_error_background")
}
}
}
}
4.1.4. Apache Cordova
Certificate Pinning
Certificate Pinning is being performed on preflight requests to ensure that the channel is secure before sending sensitive data.
window.plugins.sslCertificateChecker.check(
function() {
// success
}.bind(this),
function(message) {
if (message == "CONNECTION_NOT_SECURE") {
let toast = this.toastCtrl.create({
message: 'Connection Not Secure. Preventing Authentication.',
duration: 10000,
position: 'bottom'
});
this.navCtrl.setRoot(HomePage);
toast.present();
}
}.bind(this),
server,
fingerprint);
The pinning fingerprint is defined in the /config/keycloak.json file.
+include::https://raw.githubusercontent.com/feedhenry/mobile-security-cordova-template/0c8ccb1e1b01336aba47b596102e89575740d6ce/src/config/keycloak.json[]+
5. Mobile App Self-Defence
| Do: | Do not: |
|---|---|
Detect compromised environments (rooted/jaibreak) at runtime and react accordingly (Ensure the environment is not tampered with) |
Obfuscation is not protection, assume everything in the code is public. (Application code can be deobfuscated) |
Integrity check of the binary (Ensure the app itself is not tampered with) |
|
Verify and check the integrity of dynamic resources (Ensure those resources are not tampered with) |
|
Disable debugging in the application settings (Make sure the app is not debuggable) |
|
Check if the device is in developer mode if supported by platform (Prevent app debugging) |
|
Check if debugger is attached and/or if the process is being traced (Prevent app debugging) |
|
Check lockscreen/passcode is enabled on the device (Make sure device is secure) |
|
Consider blocking access if devices are out of date (Ensure device has no known vulnerabilities) |
|
Verify that the app implements two or more functionally independent methods of root detection. (Use a dual verification approach). |
|
Verify that the app implements multiple functionally independent debugging defences. (Prevent a debugger from being attached that can view app data). |
|
Verify that the app detects, and response to, being run in an emulator using any method. (An emulator can be tampered with more easily than a hardware device). |
|
Verify that the app detects, and responds to, modifications of process memory, including relocation table patches and injected code. (Ensure that the operating environment of the App is safe before running it.) |
|
Verify all executable files and libraries belonging to the app are either encrypted on the file level and/or important code and data segments inside the executables are encrypted or packed. (Sensitive or secret code/resources should be protected.) |
|
Verify that the app implements a 'device binding' functionality when a mobile device is treated as being trusted. Verify that the device fingerprint is derived from multiple device properties. (Improve device identity/integrity). |
|
Verify that if the architecture requires sensitive computations be performed on the client-side, these computations are isolated from the operating system by using a hardware-based SE or TEE. (Sensitive operations should be carried out away from the main processor in a Trusted Execution Environment). |
|
Perform user interaction anomaly detection. (Allows the identification of abnormal app usage by a user that may be malicious). |
|
Consider only starting the app if it is running on the latest version or block requests from old versions of the app. (Older versions maybe have logic or security issues). |
|
Out-of-appstore security updates should be shipped using an encrypted connection. (Ensure that the security patch has not been tampered with in transit). |
|
Ensure that the installation package and its updates shall be digitally signed such that its platform can cryptographically verify them prior to installation. (Verify integrity before applying the update). |
5.1. Implementation
5.1.1. Overview
A number of checks were implemented to assess the security of the underlying device running both the Android and iOS template applications.
5.1.2. Android
The Android Template App is using the App Self-Defense Checks that are included in the AeroGear Android SDK.
The main code logic is found under here.
The Security Service is being initialised in the SecureApplicationModule.
@Provides @Singleton
SecurityService provideSecurityService() {
return MobileCore.getInstance().getService(SecurityService.class);
}
The following code snippets describe the main device trust detection logic in the mobile app.
Detecting Root Access
A number of different checks are being used to check if root access is present on the device.
/**
* Detect if the device is rooted.
*/
public void detectRoot() {
totalTests++;
SecurityCheckResult result = securityService.check(SecurityCheckType.IS_ROOTED);
if (result.passed()) {
setDetected(rootAccess, R.string.root_detected_positive);
}
}
Detecting if Lock Screen Set
Detecting if the Android device has a lock screen set (with pin, fingerprint, pattern etc).
/**
* Detect if the device has a lock screen setup (pin, password etc).
*/
public void detectDeviceLock() {
totalTests++;
SecurityCheckResult result = securityService.check(SecurityCheckType.SCREEN_LOCK_ENABLED);
if (!result.passed()) {
setDetected(lockScreenSetup, R.string.device_lock_detected_negative);
}
}
Detecting if Debugger is Attached
Detecting if an Android debugger is attached to the application.
/**
* Detect if a debugger is attached to the application.
*/
public void debuggerDetected() {
totalTests++;
SecurityCheckResult result = securityService.check(SecurityCheckType.IS_DEBUGGER);
if (result.passed()) {
setDetected(debuggerAccess, R.string.debugger_detected_positive);
}
}
Detecting Emulator Access
Detecting if the Application is being run on an emulator.
/**
* Detect if the application is being run in an emulator.
*/
public void detectEmulator() {
totalTests++;
SecurityCheckResult result = securityService.check(SecurityCheckType.IS_EMULATOR);
if (result.passed()) {
setDetected(emulatorAccess, R.string.emulator_detected_positive);
}
}
Detecting Hooking Framework Apps
There are some simple checks added to the application to check if the Xposed Framework or Cydia Substrate are installed on the device, which can be used to attack and tamper with logic in an Android applications.
/**
* Detect if a hooking framework application is installed on the device
*/
public void detectHookingFramework() {
totalTests++;
String xposedPackageName = "de.robv.android.xposed.installer";
String substratePackageName = "com.saurik.substrate";
if (checkAppInstalled(xposedPackageName) || checkAppInstalled(substratePackageName)) {
setDetected(hookingDetected, R.string.hooking_detected_positive);
}
}
Detecting App Data Backup Enabled
The application can check if the allowBackup
flag in the applications AndroidManifest.xml
file is set to true. If this flag is set to true, it is possible for an attacker to recover application data from the device without requiring root access.
/**
* Function to check if the backup flag is enabled in the application manifest file
*/
public void detectBackupEnabled() {
totalTests++;
SecurityCheckResult result = securityService.check(SecurityCheckType.ALLOW_BACKUP_ENABLED);
if (result.passed()) {
setDetected(allowBackup, R.string.allow_backup_detected_positive);
}
}
Detecting Out of Date OS Version
The devices OS version can be checked using the Build class to see if the device is not running the latest of Android.
/**
* Function to check if the device is running the latest Android OS
*/
public void detectLatestOS() {
// todo: find if there is a better way to define what the latest android version is
int latestOsApiLevel = Build.VERSION_CODES.M;
totalTests++;
if (Build.VERSION.SDK_INT < latestOsApiLevel) {
setDetected(deviceOS, R.string.device_os_latest_negative);
}
}
Detecting Developer Mode Enabled
The Developer Mode status can be checked to see if this is enabled.
/**
* Detect if the developer options mode is enabled on the device
*/
public void detectDeveloperOptions() {
totalTests++;
SecurityCheckResult result = securityService.check(SecurityCheckType.IS_DEVELOPER_MODE);
if (result.passed()) {
setDetected(developerOptions, R.string.developer_options_positive);
}
}
5.1.3. Native iOS
The following code snippets describe the main device trust detection logic in the iOS template app.
Detecting Device Lock
Detecting if the Device has a lock screen set.
/**
- Check if a lock screen is set on the device. (iOS 9 or higher).
- Returns: A detector object.
*/
fileprivate func detectDeviceLock() -> Detector {
let deviceLockSet = LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: nil)
if deviceLockSet == false {
return Detector(label: DETECTION_DEVICE_LOCK_POSITIVE, detected: true, description: DETECTION_DEVICE_LOCK_DESC)
} else {
return Detector(label: DETECTION_DEVICE_LOCK_NEGATIVE, detected: false, description: DETECTION_DEVICE_LOCK_DESC)
}
}
Detecting Jailbreak
Detecting if the Device is Jailbroken using the DTTJailbreakDetection library.
/**
- Check if the device running the application is jailbroken.
- Returns: A detector object.
*/
fileprivate func detectJailbreak() -> Detector {
if (DTTJailbreakDetection.isJailbroken()) {
return Detector(label: JAILBREAK_DETECTED_POSITIVE, detected: true, description: JAILBREAK_DETECTED_DESC)
} else {
return Detector(label: JAILBREAK_DETECTED_NEGATIVE, detected: false, description: JAILBREAK_DETECTED_DESC)
}
}
Detecting Debug Mode
Detecting if the device is running in Debug mode.
/**
- Check if the device running the application is jailbroken.
- Returns: A detector object.
*/
fileprivate func detectDebugabble() -> Detector {
#if DEBUG
return Detector(label: DEBUG_MODE_DETECTED_POSITIVE, detected: true, description: DEBUG_MODE_DETECTED_DESC)
#else
return Detector(label: DEBUG_MODE_DETECTED_NEGATIVE, detected: false, description: DEBUG_MODE_DETECTED_DESC)
#endif
}
Detecting Emulator Access
Detecting if the underlying device running the mobile application is an Emulator.
/**
- Check if the application is running in an emulator.
- Returns: A detector object.
*/
fileprivate func detectEmulator() -> Detector {
#if (arch(i386) || arch(x86_64)) && os(iOS)
return Detector(label: EMULATOR_DETECTED_POSITIVE, detected: true, description: EMULATOR_DETECTED_DESC)
#else
return Detector(label: EMULATOR_DETECTED_NEGATIVE, detected: false, description: EMULATOR_DETECTED_DESC)
#endif
}
Detecting Outdated OS Versions
Detecting if the underlying device running the mobile application is running an old version of iOS.
/**
- Check if the device is running the on the latest version of iOS.
- Returns: A detector object.
*/
fileprivate func detectLatestOS() -> Detector {
if #available(iOS 11.0, *) {
return Detector(label: LATEST_OS_DETECTED_NEGATIVE, detected: false, description: LATEST_OS_DETECTED_DESC)
} else {
return Detector(label: LATEST_OS_DETECTED_POSITIVE, detected: true, description: LATEST_OS_DETECTED_DESC)
}
}
5.1.4. Apache Cordova
The following code snippets describe the main device trust detection logic in the Cordova template app.
Detecting Emulator Access
Detecting if the underlying device running the mobile application is an Emulator.
/**
* Detect if the device is running on an emulator.
*/
detectEmulator(): void {
if(device.isVirtual) {
this.addDetection("Emulator Access Detected", true);
} else {
this.addDetection("Emulator Access Not Detected", false);
}
}
Detecting Root/Jailbreak
Detecting if the underlying device is Jailbroken or has Root Access.
/**
* Detect if the device is running Root.
*/
detectRoot(): void {
var self = this;
IRoot.isRooted(function(rooted) {
if(rooted) {
self.addDetection("Root Access Detected", true);
} else {
self.addDetection("Root Access Not Detected", false);
}
}, function(error) {
console.log(error);
});
}
Detecting Debug Access
Detecting if the application is built in debug mode.
/**
* Detect if the app is running in debug mode.
*/
detectDebug(): void {
var self = this;
cordova.plugins.IsDebug.getIsDebug(function(isDebug) {
if(isDebug) {
self.addDetection("Debug Access Detected", true);
} else {
self.addDetection("Debug Access Not Detected", false);
}
}, function(err) {
console.error(err);
});
}
Detecting Device Lock Set
Detecting if the underlying device has the device lock set.
/**
* Detect if a system device lock is set.
*/
detectDeviceLock() {
this.pinCheck.isPinSetup()
.then(
(success) => { this.addDetection("Device Lock Enabled", false)},
(error) => {this.addDetection("Device Lock Not Enabled", true)}
);
}
6. Handling Sensitive Data Overview
Do: |
Do not: |
Sensitive data should stay in RAM for as little time as possible (Prevent data leak) |
Send sensitive data over alternate channels (e.g, SMS, MMS, or notifications) (They are not encrypted) |
Address Space Layout Randomization (ASLR) should be taken advantage of (Limit the impact of attacks such as buffer overflow) |
Encryption keys remain in RAM during the instance lifecycle of the app. (Prevent the key from being read from memory) |
minimize the use of APIs that access sensitive or personal user data (Prevent data leak) |
Write data to log files (Other applications can access the log data) |
Consider using a hash or non-reversible form of the data (To protect the original data) |
Store/cache sensitive data on the device (It can be leaked) |
minimize the frequency of asking for user credentials. Instead use an authorization token and refresh it (Less chance of exposing user credentials) |
storing/logging GPS data (Protect user privacy) |
Use secure POST to send user data, with XSRF token protection (Query params in URL are logged) |
Sending crash logs over the network in plaintext (Can contain sensitive info) |
Add an additional layer of verified, third-party encryption (e.g., SQLCipher) to the data as device encryption is not sufficient (Extra layer of protection) |
7. Cryptography Summary
| Do: | Do not: |
|---|---|
Apply cryptographic standards that will withstand the test of time for at least 10 years into the future |
Including the keys in the same attacker-readable directory as the encrypted content |
Use NIST FIPS-validated crypto modules |
Use of hardcoded keys within the binary |
Use NIST approved or NSA approved key management technology and processes |
Creation and Use of Custom Encryption Protocols |
Recommended minimal key lengths and algorithms Key exchange : Diffie–Hellman with a minimum of 2048 bits Message Integrity: HMAC-SHA2 (or HMAC-SHA-256) Message Hash: SHA2 256 bits Asymetric encryption: RSA 2048 bits Symmetric-key algorithm: AES 128 bits (CBC or GCM) Password Hashing: PBKDF2, Scrypt, Bcrypt |
Use of Insecure and/or Deprecated Algorithms, like RC2, MD5, MD4, SHA1 |
Use the same cryptographic key for multiple purposes |
|
Random values are generated using a sufficiently secure random number generator |
8. References
8.1. OWASP Top 10 Mobile Risks
This is a summary of the OWASP Top 10 Mobile Risks Project for 2017.
8.1.1. M1 - Improper Platform Usage
Threat Agents
-
Misuse of a platform feature or failure to use platform security controls (Include Android intents, platform permissions, misuse of TouchID, the Keychain etc).
Weakness
-
The organization must expose a web service or API call that is consumed by the mobile app.
-
The exposed service or API call is implemented using insecure coding techniques that produce an OWASP Web Top Ten or Cloud Top Ten within the server.
Vulnerabilities
-
Violation of published guidelines
-
Violation of convention or common practice
-
Unintentional Misuse
Prevention Techniques
-
Secure coding and configuration practices must be used on server-side of the mobile application. See the OWASP Web Top Ten or Cloud Top Ten projects.
8.1.2. M2 - Insecure Data Storage
Threat Agents
-
An adversary that has attained a lost/stolen mobile device
-
Malware or another repackaged app acting on the adversary’s behalf that executes on the mobile device.
Weakness
-
Development teams assume that users or malware will not have access to a mobile device’s filesystem and subsequent sensitive information in data-stores on the device
-
Usage of poor encryption libraries
-
Rooting or jailbreaking a mobile device circumvents any encryption protections
Vulnerabilities
-
Data stored insecurely includes, but is not limited to, the following:
-
SQL databases
-
Log files
-
XML data stores ou manifest files
-
Binary data stores
-
Cookie stores
-
SD card
-
Cloud synced
-
-
Unintended data leakage includes, but is not limited to, vulnerabilities from:
-
The OS
-
Frameworks
-
Compiler environment
-
New hardware
-
Prevention Techniques
-
Understand the information assets the app processes and how the APIs handle those assets. E.g.
-
URL caching (both request and response)
-
Keyboard press caching
-
Copy/Paste buffer caching
-
Application backgrounding
-
Intermediate data
-
Logging
-
HTML5 data storage
-
Browser cookie objects
-
Analytics data sent to 3rd parties
-
8.1.3. M3 - Insecure Communication
Threat Agents
-
An adversary that shares your local network (compromised or monitored Wi-Fi)
-
Carrier or network devices (routers, cell towers, proxy’s, etc)
-
Malware on your mobile device
Weakness
-
Do not protect network traffic
-
Use SSL/TLS during authentication but not elsewhere
-
The use of transport security does not mean the app has implemented it correctly
Vulnerabilities
-
Lack of certificate inspection
-
the mobile app fails to inspect the certificate offered by the server and the mobile app unconditionally accepts any certificate offered to it by the server
-
-
Weak handshake negotiation
-
The client successfully negotiates with the server to use a weak cipher suite that results in weak encryption that can be easily decrypted by the adversary.
-
-
Privacy information leakage
-
The mobile app transmits personally identifiable information to an endpoint via non-secure channels instead of over SSL.
-
Prevention Techniques
-
General Best Practices
-
Assume that the network layer is not secure and is susceptible to eavesdropping.
-
Apply SSL/TLS to transport channels that the mobile app will use to transmit sensitive data to a backend API or web service.
-
Account for outside entities like third-party analytics companies, social networks, etc. by using their SSL versions when an application runs a routine via the browser/webkit. Avoid mixed SSL sessions as they may expose the user’s session ID.
-
Use strong, industry standard cipher suites with appropriate key lengths.
-
Use certificates signed by a trusted CA provider.
-
Never allow self-signed certificates, and consider certificate pinning for security conscious applications.
-
Always require SSL chain verification.
-
Only establish a secure connection after verifying the identity of the endpoint server using trusted certificates in the key chain.
-
Alert users through the UI if the mobile app detects an invalid certificate.
-
Do not send sensitive data over alternate channels (e.g, SMS, MMS, or notifications).
-
If possible, apply a separate layer of encryption to any sensitive data before it is given to the SSL channel. In the event that future vulnerabilities are discovered in the SSL implementation, the encrypted data will provide a secondary defense against confidentiality violation.
-
-
iOS Specific Best Practices
-
Ensure that certificates are valid and fail closed.
-
When using CFNetwork, consider using the Secure Transport API to designate trusted client certificates. In almost all situations, NSStreamSocketSecurityLevelTLSv1 should be used for higher standard cipher strength.
-
After development, ensure all NSURL calls (or wrappers of NSURL) do not allow self signed or invalid certificates such as the NSURL class method setAllowsAnyHTTPSCertificate.
-
Consider using certificate pinning by doing the following: export your certificate, include it in your app bundle, and anchor it to your trust object. Using the NSURL method connection:willSendRequestForAuthenticationChallenge: will now accept your cert.
-
-
Android Specific Best Practices
-
Remove all code after the development cycle that may allow the application to accept all certificates such as org.apache.http.conn.ssl.AllowAllHostnameVerifier or SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER. These are equivalent to trusting all certificates.
-
If using a class which extends SSLSocketFactory, make sure checkServerTrusted method is properly implemented so that server certificate is correctly checked.
-
8.1.4. M4 - Insecure Authentication
Weakness
-
Poor or missing authentication schemes
-
Authentication requirements for mobile apps can be quite different from traditional web authentication schemes due to availability requirements.
-
Mobile apps may have uptime requirements that require offline authentication
Vulnerabilities
-
The mobile app is able to anonymously execute a backend API service request without providing an access token
-
The mobile app stores passwords or shared secrets locally on the device
-
The mobile app uses a weak password policy to simplify entering a password
-
The mobile app uses a feature like TouchID
Prevention Techniques
-
Authentication requirements of mobile applications should match that of the web application component
-
Authenticating a user locally can lead to client-side bypass vulnerabilities.
-
Where possible, ensure that all authentication requests are performed server-side.
-
If client-side storage of data is required, the data will need to be encrypted using an encryption key that is securely derived from the user’s login credentials.
-
Persistent authentication (Remember Me) functionality implemented within mobile applications should never store a user’s password on the device
-
Ideally, mobile applications should utilize a device-specific authentication token that can be revoked within the mobile application by the user
-
Do not use any spoof-able values for authenticating a user (like device identifiers or geo-location)
-
Persistent authentication within mobile applications should be implemented as opt-in and not be enabled by default
-
If possible, do not allow users to provide 4-digit PIN numbers for authentication passwords
-
Developers should assume all client-side authorization and authentication controls can be bypassed by malicious users. Authorization and authentication controls must be re-enforced on the server-side whenever possible
-
Due to offline usage requirements, mobile apps may be required to perform local authentication or authorization checks within the mobile app’s code. If this is the case, developers should instrument local integrity checks within their code to detect any unauthorized code changes.
8.1.5. M5 - Insufficient Cryptography
Threat Agents
-
Anyone with physical access to data that has been encrypted improperly
-
Mobile malware acting on an adversary’s behalf
Vulnerabilities
-
The mobile app may use a process behind the encryption / decryption that is fundamentally flawed and can be exploited by the adversary to decrypt sensitive data
-
The mobile app may implement or leverage an encryption / decryption algorithm that is weak in nature and can be directly decrypted by the adversary
-
Poor Key Management Processes
-
Including the keys in the same attacker-readable directory as the encrypted content
-
Making the keys otherwise available to the attacker
-
Avoid the use of hardcoded keys within your binary
-
Keys may be intercepted via binary attacks
-
-
Creation and Use of Custom Encryption Protocols
-
Use of Insecure and/or Deprecated Algorithms
-
RC2
-
MD4
-
MD5
-
SHA1
-
8.1.6. M6 - Insecure Authorization
Weakness
-
Poor or missing authorization schemes
-
Authorization requirements are more vulnerable when making authorization decisions within the mobile device instead of through a remote server
Vulnerabilities
-
Presence of Insecure Direct Object Reference (IDOR) vulnerabilities
-
Hidden Endpoints
-
User Role or Permission Transmissions
Prevention Techniques
-
Verify the roles and permissions of the authenticated user using only information contained in backend systems. Avoid relying on any roles or permission information that comes from the mobile device itself
-
Backend code should independently verify that any incoming identifiers associated with a request (operands of a requested operation) that come along with the identify match up and belong to the incoming identity
8.1.7. M7 - Poor Code Quality
Threat Agents
-
Pass untrusted inputs to method calls made within mobile code
-
Poor code-quality issues are typically exploited via malware or phishing scams
Weakness
-
Memory leaks, buffer overflows, and other less severe issues that result in bad programming practice
Vulnerabilities
-
Using the wrong API, using an API insecurely, using insecure language constructs, or some other code-level issue
Prevention Techniques
-
Maintain consistent coding patterns that everyone in the organization agrees upon
-
Write code that is easy to read and well-documented
-
When using buffers, always validate that the the lengths of any incoming buffer data will not exceed the length of the target buffer
-
Via automation, identify buffer overflows and memory leaks through the use of third-party static analysis tools
-
Prioritize solving buffer overflows and memory leaks over other 'code quality' issues
8.1.8. M8 - Code Tampering
Threat Agents
-
Via malicious forms of the apps hosted in third-party app stores
-
Trick the user into installing the app via phishing attacks
Weakness
-
An attacker can either directly modify the code, change the contents of memory dynamically, change or replace the system APIs that the application uses, or modify the application’s data and resources.
8.1.9. M9 - Reverse Engineering
Threat Agents
-
Download the targeted app from an app store and analyze it within their own local environment using a suite of different tools.
-
Relatively affordable and well-understood tools
-
IDA Pro
-
Hopper
-
otool
-
strings
-
Weakness
-
Code written in languages / frameworks that allow for dynamic introspection at runtime (Java, .NET, Objective C, Swift) are particularly at risk for reverse engineering.
Vulnerabilities
-
Generally, most applications are susceptible to reverse engineering due to the inherent nature of code.
-
An app is said to be susceptible to reverse engineering if an attacker can do any of the following things
-
Clearly understand the contents of a binary’s string table
-
Accurately perform cross-functional analysis
-
Derive a reasonably accurate recreation of the source code from the binary
-
8.1.10. M10 - Extraneous Functionality
Threat Agents
-
An attacker seeks to understand extraneous functionality within a mobile app in order to discover hidden functionality in in backend systems
Weakness
-
Functionality that exposes information related to back-end test, demo, staging, or UAT environments should not be included in a production build
-
Administrative API endpoints, or unofficial endpoints should not be included in final production builds
Vulnerabilities
-
Developers include hidden backdoor functionality or other internal development security controls that are not intended to be released into a production environment
-
Leaving functionality enabled in the app that was not intended to be released
Prevention Techniques
-
Examine the app’s configuration settings to discover any hidden switches
-
Verify that all test code is not included in the final production build of the app
-
Examine all API endpoints accessed by the mobile app to verify that these endpoints are well documented and publicly available
-
Examine all log statements to ensure nothing overly descriptive about the backend is being written to the logs
8.2. Mobile Application Security Requirements
This is a review this the Mobile Application Security Requirements.
8.2.1. Requirements in Categories
The requirements can be grouped in the following categories
Design and High Level Requirement
-
The mobile app must enforce organization-defined limitations on the embedding of data types within other data types.
-
The mobile app must not record or forward sensor data unless explicitly authorized to do so.
-
The mobile app must initialize all parameter values on startup.
-
The mobile app must not be vulnerable to race conditions.
Authentication and Authorisation
-
The mobile app must implement organization-defined out-of-band authentication under organization-defined conditions.
-
The mobile app must electronically verify Personal Identity Verification (PIV) credentials.
-
The mobile app must fail to an initial state when the application unexpectedly terminates, unless it maintains a secure state at all times.
Secure Data Storage
-
The mobile app must remove temporary files when it terminates.
-
The mobile app must remove cookies or information used to track a users identity when it terminates.
-
The mobile app must not write data to persistent memory accessible to other applications.
-
The mobile app must not lock or set permissions on application files in a manner such that the operating system or an approved backup application cannot copy the files.
-
The mobile app must not change the file permissions of any files other than those dedicated to its own operation.
Secure Network Communication
-
The mobile app must protect the confidentiality and integrity of transmitted information.
-
The mobile app must not transmit error messages to any entity other than authorized audit logs, the MDM, or the device display.
-
The mobile app must utilize ports or protocols in a manner consistent with DoD Ports and Protocols guidance.
Trusted Third-party Libraries
-
The mobile app must not include source code, unreferenced code or subroutines that are never invoked during operation, except for software components and libraries from approved third-party products.
-
The mobile app source code must not contain adware or known malware.
Interaction with OS/Other Apps
-
The mobile app must not modify, request, or assign values for operating system parameters unless necessary to perform application functions.
-
The mobile app must not execute as a privileged operating system process unless necessary to perform any app functions.
-
The mobile app must not enable other applications or non-privileged processes to modify software libraries.
-
The mobile app must prevent organization-defined software from executing at higher privilege levels than users executing the software.
-
The mobile app must validate information output from software programs and/or applications defined in SI-15, CCI-0002770 to ensure the information is consistent with the expected content.
-
The mobile app must synchronize internal information system clocks to the MOS-based authoritative time source.
Safe App Memory Management
-
The mobile app must not call functions vulnerable to buffer overflows.
-
The mobile app must not be vulnerable to integer arithmetic vulnerabilities.
-
The mobile app must clear or overwrite memory blocks used to process potentially sensitive data. Sensitive data may include PII, a user’s location, or authentication credentials.
Secure Cryptography
-
Mobile apps involved in the production, control, and distribution of asymmetric cryptographic keys must use approved PKI Class 3 or class 4 certificates and hardware tokens that protect the user’s private key.
-
Mobile apps involved in the production, control, and distribution of asymmetric cryptographic keys must use approved PKI Class 3 certificates or prepositioned keying material.
-
If the underlying MOS does not provide NIST FIPS-validated crypto modules, the mobile app must implement NIST FIPS-validated cryptography for the following: to provision digital signatures; to generate cryptographic hashes; and to protect unclassified information requiring confidentiality and cryptographic protection in accordance with applicable federal laws, Executive Orders, directives, policies, regulations, and standards.
-
The mobile app must accept Public Key Infrastructure (PKI) credentials.
-
The mobile app must implement NSA-approved cryptography to protect classified information in accordance with applicable federal laws, Executive Orders, directives, policies, regulations, and standards.
-
Mobile apps involved in the production, control, and distribution of symmetric cryptographic keys must use NIST approved or NSA approved key management technology and processes.
8.3. Secure Mobile Development Guidelines
This is a summary of how to develop secure mobile applications. It is generated based on the contents form the following sources:
8.3.1. Authentication and Password Management
-
Instances where the mobile application requires a user to create a password or PIN (eg for offline access), the application should never use a PIN but enforce a password which follows a strong password policy.
-
Mobile devices may offer the possibility of using password patterns which are never to be utilized in place of passwords as sufficient entropy cannot be ensured and they are easily vulnerable to smudge-attacks.
-
Mobile devices may also offer the possibility of using biometric input to perform authentication which should never be used due to issues with false positives/negatives, among others.
-
Wipe/clear memory locations holding passwords directly after their hashes are calculated.
-
Based on risk assessment of the mobile application, consider utilizing two-factor authentication.
-
In scenarios where offline access to data is needed, add an intentional X second delay to the password entry process after each unsuccessful entry attempt (2 is reasonable, also consider a value which doubles after each incorrect attempt).
-
In scenarios where offline access to data is needed, perform an account/application lockout and/or application data wipe after X number of invalid password attempts (10 for example).
-
Based on a risk evaluation, consider adding context information (such as IP location, etc…) during authentication processes in order to perform Login Anomaly Detection.
-
Instead of passwords consider using longer term authorization tokens that can be securely stored on the device
-
Consider using asymmetric cryptography for authentication and authorization purposes.
-
If a password based authentication mechanism is used, ensure that a strong a password policy is being followed. Consider enforcing restrictions about password length and formation, reuse of old user passwords, use of common passwords, password duration, etc. However, do not maintain any representation of the password strength in application storage or the back-end server as it may expose the password in preimage attacks.
-
Do not reveal registered usernames and remove any fingerprint of their existence from verbose error messages.
-
Use authentication that ties back to the end user identity (rather than only to the device identity).
-
If the credentials or keys appear in the user interface (UI) components, try to release the UI frames immediately after use.
-
Ensure passwords and keys are not visible in cache or logs.
8.3.2. Communication Security
-
Ensure the application actually and properly validates (by checking the expiration date, issuer, subject, etc…) the server’s SSL certificate.
-
The application should only communicate with and accept data from authorized domain names/systems.
-
To protect against attacks which utilize software such as SSLStrip, implement controls to detect if the connection is not HTTPS with every request using HTTP Strict Transport Security (HSTS).
-
When using SSL/TLS, use certificates signed by trusted Certificate Authority (CA) providers.
-
Enforce secure TLS versions. Safely abort the connection, if this is not possible
-
Introduce certificate pinning.
-
Design the user interface in a way that warns the user if the peer certificate does not match the expected certificate and provide the ability to abort any further interaction.
-
SMS and MMS should not be used to send sensitive data
-
Always use platform supported or vetted frameworks for establishing secure communication channels.
-
Ensure adequate logs on the server are retained about established connections.
8.3.3. Data Storage and Protection
-
Classify data storage according to sensitivity and apply controls accordingly (e.g. passwords, personal data, location, error logs, etc.). Process, store and use data according to its classification
-
Store sensitive data on the server instead of the client-end device, whenever possible. Assume any data written to device can be recovered.
-
When storing sensitive data on the device, use a file encryption API provided by the OS or other trusted source.
-
Use file encryption APIs which use a secret key protected by the device unlock code and deletable on remote wipe if available.
-
Beyond the time required by the application, don’t store sensitive information on the device (e.g. GPS/tracking).
-
Do not store temp/cached data in a world readable directory. Assume shared storage is untrusted.
-
Use the PBKDF2 function to generate strong keys for encryption algorithms while ensuring high entropy as much as possible. The number of iterations should be set as high as may be tolerated for the environment (with a minimum of 1000 iterations) while maintaining acceptable performance.
-
Sensitive data (such as encryption keys, passwords, credit card #’s, etc…) should stay in RAM for as little time as possible.
-
Encryption keys should not remain in RAM during the instance lifecycle of the app. Instead, keys should be generated real time for encryption/decryption as needed and discarded each time.
-
Address Space Layout Randomization (ASLR) should be taken advantage of to limit the impact of attacks such as buffer overflows.
-
When displaying sensitive information (such as full account numbers), ensure that the sensitive information is cleared from memory (such as from the webView) when no longer needed/displayed.
-
Do not store sensitive data on external storage like SD cards if it can be avoided.
-
Make use of remote wipe and kill switch APIs to remove sensitive information from the device in the event of theft or loss.
-
Use a time based (expiry) type of control which will wipe sensitive data from the mobile device once the application has not communicated with its servers for a given period of time.
-
Automatic application shutdown and/or lockout after X minutes of inactivity (e.g. 5 mins of inactivity).
-
Avoid cached application snapshots in iOS: iOS can capture and store screen captures and store them as images when an application suspends. To avoid any sensitive data getting captured, use one or both of the following options: 1. Use the ‘willEnterBackground’ callback, to hide all the sensitive data. 2. Configure the application in the info.plist file to terminate the app when pushed to background (only use if multitasking is disabled).
-
Prevent applications from being moved and/or run from external storage such as via SD cards.
-
Verify that OS level storage encryption is enabled and the device is protected by a PIN or passphrase.
-
Consider the security of the whole data lifecycle in writing your application (collection over the wire, temporary storage, caching, backup, deletion, etc.).
-
Ensure that during application removal (uninstall operation), any confidential user data and the corresponding app-specific credentials are deleted from the execution environment, the device, and any other storage medium.
-
Apply the principle of minimal disclosure - only collect and disclose data which is required for business use of the application. Identify in the design phase what data is needed, its sensitivity and whether it is appropriate to collect, store and use each data type.
-
Use non-persistent identifiers which are not shared with other apps wherever possible (e.g., do not use the device unique hardware identifiers such as IMEI or UDID as an identifier).
-
Application developers may want to incorporate an application-specific "data kill switch" into their products, to allow the per-app deletion of their application’s sensitive data when needed (strong authentication is required to protect misuse of such a feature).
-
Do not leak permission-protected data to other applications. This occurs when specific permissions are required to access the data, however an app that has been granted these permissions makes the data available to all other apps without restrictions (e.g., over IPC)
-
Restrict the data that is shared with other applications (e.g., by implementing an Android Content Provider). This can be accomplished using fine-grained permissions (ensure permissions are protected using signature protection level on Android).
-
Restrict broadcast messages (e.g., Android Broadcast Intents) to authorized applications and audit the application’s broadcast messages for sensitive content.
-
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
-
Disable Auto Correction and Autosuggestion for inputs that contain sensitive data.
-
Disable cut, copy and paste functionalities for inputs that may contain sensitive data or restrict the pasteboard to be accessible only from this application.
-
Introduce input field masking for inputs that contain sensitive data (e.g., passwords).
-
Leverage the hardware-level encryption support for files at the highest supported security level.
-
If the application while the device is locked needs to write data to a file, use temporary caches instead of weakening the encryption mode. Swap the file content when the device is unlocked and the original file is accessible again.
-
Prefer using framework functionality (e.g., Android Content Provider) for data sharing instead of using file system permissions or a custom access scheme on platforms that support this (e.g., Android).
-
Inspect application-initiated custom notification messages for sensitive content
-
Exclude sensitive application files from device backups and cloud synchronization services.
-
If the application allows the arbitrary selection of files from the device storage, consider the use of a white-list to restrict access only to the intended (absolute) file paths
-
Delete application caches on app termination
-
Database files that contain sensitive data (e.g., iOS WebView caches) must be manually removed from the file system. Deleting records using the database API will not necessary lead to complete data removal from database structure.
-
Disable application logging and debug messages in production releases. All exceptions should be handled securely.
-
In the case that the application includes embedded web browsing capabilities (e.g., WebViews), clear stored cookies on app termination or use in-memory cookie storage
8.3.4. Session Management
-
Do not rely on client side security controls. Both authentication and authorization controls should be implemented on the server side.
-
Ensure that the session management is handled securely after the initial authentication, using appropriate secure protocols.
-
Perform a check at the start of each activity/screen to see if the user is in a logged in state and if not, switch to the login state.
-
When an application’s session is timed out, the application should discard and clear all memory associated with the user data, and any master keys used to decrypt the data.
-
Session tokens should be revocable (particularly on the server side).
-
Use lower timeout values to invalidate expired sessions (in contrast to the typical timeout values on traditional (non-mobile) applications).
-
Use unpredictable session identifiers with high entropy.
-
Clear any maintained sensitive data on session termination. Reset the application state and request for user re-authentication.
8.3.5. Secure the backend services and the platform server and APIs
-
Carry out a specific check of your code for sensitive data unintentionally transferred between the mobile device and web-server back-ends and other external interfaces
-
All back-end services (web services) for mobile apps should be tested for vulnerabilities periodically
-
Disable metadata publishing (e.g., metadata for WSDL documents and for WSDL derived objects), in order to prevent unintentional disclosure of potentially sensitive service metadata
-
Ensure that the back-end platform (server) is running with a hardened configuration with the latest security patches applied to the OS, web server and other application components.
-
Ensure adequate logs are retained on the back-end in order to detect and respond to incidents and perform forensics
-
Protect the back-end from client initiated log injections that may corrupt or forge the history of events2
-
Employ rate limiting and throttling on a per-user/IP basis
8.3.6. 3rd Party libraries
-
Vet the security/authenticity of any third party code/libraries used in your mobile application (e.g. making sure they come from a reliable source, will continue to be supported, contain no backdoors) and ensure that adequate internal approval is obtained to use the code/library.
-
Track all third party frameworks/APIs used in the mobile application for security patches and perform upgrades as they are released.
-
Pay particular attention to validating all data received from and sent to non-trusted third party apps (e.g. ad network software) before incorporating their use into an application.
-
Avoid using third-party libraries that contain main processor-only cryptographic implementations. Prefer using cryptographic framework provided by a platform supported secure hardware (e.g. TEE, SE).
-
Software components that are no longer supported by the vendor or developer must not be used
8.3.7. Consent and privacy protection
-
Check whether your application is collecting personal data
-
Create a privacy policy covering the usage of personal data and make it available to the user especially prior to making consent choices
-
Prior to using personal data consent should be obtained.
-
Consent may be collected in 3 main ways:
-
At install time.
-
At run-time when data is sent.
-
Via “opt-in” mechanisms where a user has to explicitly turn on a setting
-
-
It should be possible for the user to withdraw consent at any time in the application
-
Audit communication mechanisms to check for unintended leaks
-
Keep a record of user consent for the processing of different types of personal data
-
Check whether data collection (from the user’s device) is not excessive with regard to the consent that has been granted by the user
-
Consider taking advantage of built-in features to require access to device sensors and data
-
Minimize access to sensor data whenever possible
-
Reduce data granularity and anonymize data on the device instead of remotely
-
Require consent prior to providing user data to third parties
-
Reduce retention period on the mobile or remotely to the minimum amount of time needed to provide the service
-
Use privacy enhancing technologies, that support data minimization, anonymization and security of personal data
-
The default settings of the application should provide maximum privacy and security protection for the user
8.3.8. Protect paid resources
-
Maintain logs of access to paid-for resources in non-repudiable format
-
Check for anomalous usage patterns in paid-for resources usage and trigger re-authentication
-
Consider a white-list model by default for paid-for resources addressing
-
Warn user and obtain consent for any cost implications for app behaviour
-
Follow the OS/device vendor guidelines for implementing In-App payment:
-
Implement validation of payment receipts on the backend server not on the device
-
Pay specific attention when integrating payment acceptance from a third party wallet
-
8.3.9. Secure software distribution
-
Applications must be designed and provisioned to allow updates for security patches, taking into account the requirements for approval by app-stores and the extra delay this may imply
-
Distributing apps through official app-stores
-
Provide feedback channels for users to report security problems with apps such as a security email address
-
If an enterprise app store is used, protect the application signing key with the utmost care
-
Out-of-appstore security updates should be shipped using an encrypted connection and their content should be verified before applying the update
-
Resources used by apps that are updated outside of the app-store normal mechanism must be signed. Apps must verify the signature before accepting the updated resource
-
Do not deploy apps with ad-hoc signing certificates used for development and testing
-
Do not generate one application for multiple environments
8.3.10. Handle runtime code interpretation correctly
-
Filter user data passed to interpreters.
-
Define comprehensive escape syntax as appropriate.
-
Do not reveal sensitive information such as usernames, personal data and others through error messages.
-
Deny interpreted code direct access to user data and encrypted storage.
-
Strip unused functionalities from interpreters.
-
Limit size of input data passed to interpreters.
8.3.11. Check device and application integrity
-
Check the device/platform integrity to ensure that the device is not modified.
-
Check the application integrity, check that the application and its resources are not modified
-
Disable developer features
-
Disable debugging in the application settings.
-
Check if the device is in developer mode if supported by platform (e.g., Android).
-
Check if debugger is attached and/or if the process is being traced. On platforms with managed code check for managed and native code debuggers
-
8.3.12. Code Obfuscation
-
Obfuscate all sensitive application code where feasible by running an automated code obfuscation program.
-
For applications containing sensitive data, implement anti-debugging techniques (e.g. prevent a debugger from attaching to the process; android:debuggable=”false”).
-
Address Space Layout Randomization (ASLR) should be taken advantage of to hide executable code which could be used to remotely exploit the application and hinder the dumping of application’s memory.
8.3.13. Protect the application from client side injections
-
In the case that the application includes embedded web browsing capabilities (e.g., WebViews), restrict access to third party domains that do not comply with the required security standards, disable any unused platform supported functionalities, such as the plugins, local file accessibility, local content provider (content URL) accessibility and the dynamic code (e.g., JavaScript)execution support. Furthermore, avoid using full screen web interfaces since these can be abused from attackers to create fake application screens
-
Avoid using API calls that provide bridging of dynamic code (e.g., JavaScript) with native code (e.g., Objective-C) since an injection in the dynamic code will lead to native code execution
-
In the case that the application uses JavaScript code running in the context of a file scheme URL, it is recommended to disable any unused platform supported attributes, such as accessing content from other file scheme URL and content from any origin
-
Prevent interaction events when the application is obscured by another interface in the presentation layer in order to mitigate tapjacking attacks
-
In the case that the application requests custom permissions, and older platforms are supported (e.g., earlier than Android 5.0), always verify on the first run of the app that no other application has previously requested the same permissions
-
Always follow the domain name registration infrastructure to declare a custom permission, in order to avoid any collisions with other apps
-
Restrict what apps can cause an application component (e.g., Android Activity) to start or are able to interact with it (e.g., Android Service and Content Provider)
-
Restrict the third party applications whose broadcast messages will be accepted by the application.
-
In the case that the application utilizes a platform provided download manager, always verify that the received manager’s notifications are related to application’s initiated downloads
-
Always verify dynamic code downloads and application updates at the client side.
-
Always validate server responses when using backend APIs.
-
Mitigate SQL injections, local file inclusion, JavaScript injections, XML injections. When dealing with dynamic queries (e.g., SQL queries with untrusted inputs) or Content-Providers ensure you are using parameterized queries. Always validate user provided inputs that will be used for file accessing purposes or as part of a dynamic code execution. Use a vetted framework for XML operations.
-
Protect from memory corruptions in applications that are developed using a programming language which supports explicit memory management (e.g., Objective-C, C, C++).
-
Do not use insecure cached data in HTTP connections and in embedded web browsing capabilities (e.g., WebViews).
-
In platforms that support custom applications with accessibility permissions (e.g., Android), exclude sensitive user interface elements from being accessed by accessibility applications
-
Avoid populating webviews loaded from the file URI scheme with user supplied DOM input
8.3.14. Ensure correct usage of biometric sensors and secure hardware
-
Always verify that there is a biometric sensor (e.g., Fingerprint reader) present and available on the device before using the API for authentication purposes
-
Always verify that the biometric sensor/secure hardware authentication policy of the in-use platform complies with the application’s authentication policy (passcode required after cold boot, biometric sensor authentication expiration, adding a fingerprint requires pin/passcode/biometric authentication, requirement for biometric sensor being individually paired with secure hardware)
-
Ensure that there are enrolled data using the biometric sensor (e.g., user’s fingerprints and/or user’s iris are registered) before using the API for authentication purposes
-
Ensure that the enrolled biometric data has not been changed since the activation of the authentication control using the biometric sensor
-
The application should not use the biometric sensor for just verifying user presence (e.g., iOS LocalAuthentication). Instead, the application should use the biometric sensor to access keys stored using a hardware backed keystore/keychain and protected with keychain access control lists (ACL).
-
Ensure that the key material is bound to the secure hardware (e.g., TEE, SE) in platforms that this is optional (e.g., Android)
-
For keys whose key material is inside a secure hardware (e.g., TEE, SE), ensure that cryptographic and user authentication authorizations are also enforced by secure hardware, in platforms that this is optional (e.g., Android).
-
The application should avoid using temporal validity interval authorizations, since they are unlikely to be enforced by the secure hardware because it normally does not have an independent secure real-time clock.
-
Verify that the application’s authentication policy complies with the possibility that different people may enroll for biometric authentication in the same device.
8.4. OWASP Cheat Sheet Series
This is a summary of notes taken from the OWASP Cheat Sheet Series
8.4.1. Cryptographic Requirements
The recommended minimal key lengths and algorithms by OWASP are outlined below.
Key exchange |
Diffie–Hellman with a minimum of 2048 bits |
Message Integrity |
HMAC-SHA2 |
Message Hash |
SHA2 256 bits |
Asymetric encryption |
RSA 2048 bits |
Symmetric-key algorithm |
AES 128 bits |
Password Hashing |
PBKDF2, Scrypt, Bcrypt |
8.4.2. Certificate Pinning
Pinning is the process of associating a host with their expected X509 certificate or public key. Once a certificate or public key is known or seen for a host, the certificate or public key is associated or 'pinned' to the host. If more than one certificate or public key is acceptable, then the program holds a pinset. In this case, the advertised identity must match one of the elements in the pinset.
Pinning Methods
There are a number of ways you can pin the certificate to the client: the whole certificate, the public key of the certificate or a hash of the information.
Certificate
The certificate is the easiest to pin to the client. However, one of the main downsides is having to update the app with a new certificate every time the cert expires, cert rotation or a CA is compromised.
Public Key
Public key pinning is more flexible. As with a certificate, the program checks the extracted public key with its embedded copy of the public key. This will mean that if a cert is rotated or expires, and the public key doesn’t change in the process, you don’t need to update your applications with new certificates, as we are only concerned with a public key. There are some extra steps necessary to extract the public key from a certificate and as the key is static and may violate key rotation policies.
HPKP (HTTP Public Key Pinning)
With HPKP, a number of public keys are pinned on the server. When a client visits the website for the first time, the client will store these pins. On every future visit to the website, the client will make sure the public keys match the ones that they have stored locally. If the keys don’t match, it could mean that the CA was compromised or the keys have been rotated. This is an example of dynamic pinning, where the entity to pin is retrieved dynamically from the server, rather than being packaged or stored locally at development/build time.
A valid HPKP configuration must also include at least one backup key. Therefore, if something goes wrong, you can use the backup key instead.
The following is an example of the Public-Key-Pins HPKP Header:
Public-Key-Pins:
pin-sha256="cUPcTAZWKaASuYWhhneDttWpY3oBAkE3h2+soZS7sWs=";
pin-sha256="M8HztCzM3elUxkcjR2S5P4hhyBNf6lHkmjAHKhpGPWE=";
max-age=5184000; includeSubDomains;
report-uri="https://www.example.org/hpkp-report"
-
The first
pin-sha256
is the production public key of the server. -
The second
pin-sha256
is the backup key. -
The
max-age
tells the client how long to store the keys for. -
The
includeSubDomains
definition means that the key pinning is also valid for all subdomains. -
The
report-uri
defines where you can report pin validation failures.
Some potential issues with HPKP have been pointed out by Qualys SSL Labs.
An example of a helmet.js implementation guide for Node/Express is also available here.
TrustKit
TrustKit for iOS and TrustKit-Android for Android allow SSL pinning for native mobile apps.
8.4.3. Jailbreaking
Jailbreaking or rooting is the process of gaining unauthorized access or elevated privileges on a system.
Detecting Jailbreak
-
Identify 3rd party app stores (e.g. Cydia for iOS) or popular superuser applications.
-
Attempt to identify modified kernels by comparing certain system files that the application would have access to on a non-jailbroken device to known good file hashes. This technique can serve as a good starting point for detection.
-
Attempt to write a file outside of the application’s root directory. The attempt should fail for non-jailbroken devices as they don’t have the necessary permissions.
-
Generalizing, attempt to identify anomalies in the underlying system or verify the ability to execute privileged functions or methods.
8.5. OWASP Mobile Verification Standard
This is a fork of the OWASP Mobile Verification Standard.
8.5.1. Verification Levels
-
MASVS-L1: Standard Security
-
lists security best practices
-
all mobile apps
-
-
MASVS-L2: Defense-in-Depth
-
introduces advanced security controls
-
should be used by apps that store personally identifiable information, or highly sensitive information, like apps for health-care industry and financial industry
-
-
MASVS-R: Resiliency Against Reverse Engineering and Tampering
-
the app has state-of-the-art security, and is also resilient against specific client side attacks, such as tampering, modding or reverse engineering
-
can be used in combination with the other 2 levels. E.g. MASVS-L1 + R or MASVS-L2 + R
-
8.5.2. Table Keys
Category - The type of work required for each item. Some enhancements have been made.
-
Manual Observation - A task that may only require manual observation. eg. confirming that no sensitive information is being stored in log files.
-
Tooling - A task that may require the use of tooling to carry out the task. eg. pentesting or creating threat models
-
Process - A task that may require some processes to be setup. eg. dependency checks.
-
Coding - A task that requires coding.
-
Keycloak - A task that can be carried out using Keycloak.
OS/Framework Columns - A column is allocated for each Mobile OS/Framework for any specific details for each item.
8.5.3. Architecture, Design and Threat Modelling
| V1 | Description | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
1.1 |
Verify all application components are identified and are known to be needed. |
Manual/Tooling Observation |
Check the config.xml for unrequired components. This also includes any JS files required from a CDN or elsewhere in the index.html or equivalent of an application. |
L1+L2 |
|||
1.2 |
Verify all third party components used by the mobile app, such as libraries and frameworks, are identified, and checked for known vulnerabilities. |
Manual/Tooling Observation |
Check the config.xml for vulnerable components manually or using dependency checker. |
L1+L2 |
|||
1.3 |
Verify that security controls are never enforced only on the client side, but on the respective remote endpoints. |
Keycloak/Server Task |
L1+L2 |
||||
1.4 |
Verify that a high-level architecture for the mobile app and all connected remote services has been defined and security has been addressed in that architecture. |
Manual Observation |
See 1.7 |
L11+L2 |
|||
1.5 |
Verify that data considered sensitive in the context of the mobile app is clearly identified. |
Manual Observation |
L1+L2 |
||||
1.6 |
Verify all app components are defined in terms of the business functions and/or security functions they provide. |
Manual Observation |
L2 |
||||
1.7 |
Verify that a threat model for the mobile app and the associated remote services, which identifies potential threats and countermeasures, has been produced. |
Tooling Observation |
A threat model diagram could be created (perhaps using OWASP Threat Dragon) where we can map out all the potential threats. |
L2 |
|||
1.8 |
Verify all third party components have been assessed (associated risks) before being used or implemented. Additionally verify that a process is in place to ensure that each time a security update for a third party component is published, the change is inspected and the risk evaluated. |
Manual/Tooling/Process Observation |
We could also look at using subresource integrity checks for requiring JS libraries from the internet/CDN within an index.html. |
L2 |
|||
1.9 |
Verify that all security controls have a centralized implementation. |
Manual/Keycloak Observation |
L2 |
||||
1.10 |
Verify that all components that are not part of the application but that the application relies on to operate, are clearly identified and the security implications of using those components are known. |
Manual/Keycloak Observation |
L2 |
||||
1.11 |
Verify that there is an explicit policy for how cryptographic keys (if any) are managed, and the lifecycle of cryptographic keys is enforced. Ideally, follow a key management standard such as NIST SP 800-57. |
Coding/Observation Task |
L2 |
||||
1.12 |
Verify that remote endpoints ensure that connecting clients use the current version of the mobile app. |
Coding Task |
L2 |
||||
1.13 |
Verify that security testing is performed as part of the development lifecycle. If some or all of the testing is automated, the configuration of the testing tools must be tailored to the specific app. |
Manual/Tooling Task/Observation |
L2 |
8.5.4. Data Storage and Privacy
| V2 | Data Storage and Privacy | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
2.1 |
Verify that system credential storage facilities are used appropriately to store sensitive data, such as user credentials or cryptographic keys. |
Coding Task |
Ie. Securely storing content on the device, but also only storing data in the correct features on the OS, and not putting sensitive content where you shouldn’t or using an OS feature for the wrong purpose. |
For Android we should use the Android Keystore. |
We could look at using the following cordova plugin to provide secure storage. |
L1+L2 |
|
2.2 |
Verify that no sensitive data is written to application logs. |
Coding/Observation Task |
L1+L2 |
||||
2.3 |
Verify that no sensitive data is shared with third parties unless it is a necessary part of the architecture. |
Coding/Observation Task |
L1+L2 |
||||
2.4 |
Verify that the keyboard cache is disabled on text inputs that process sensitive data. |
Coding Task |
L1+L2 |
||||
2.5 |
Verify that the clipboard is deactivated on text fields that may contain sensitive data. |
Coding Task |
L1+L2 |
||||
2.6 |
Verify that no sensitive data is exposed via IPC mechanisms. |
Coding/Observation Task |
L1+L2 |
||||
2.7 |
Verify that no sensitive data, such as passwords and credit card numbers, is exposed through the user interface or leaks to screenshots. |
Coding Task |
We should ensure that no sensitive information is shown as default in the mobile. This should be blurred/hidden behind asterisks instead. We should also ensure that screenshotting functionality is being blocked by the app. |
L1+L2 |
|||
2.8 |
Verify that no sensitive data is included in backups. |
Coding/Observation Task |
We should ensure that the the allowBackup setting is disabled to prevent app data recovery via backups. |
We should ensure that the the allowBackup setting is disabled to prevent app data recovery via backups. |
L2 |
||
2.9 |
Verify that the app removes sensitive data from views when backgrounded. |
Coding Task |
Disabling snapshotting might be sufficient. This will blur/blank-out the screenshot of the application when displayed in the app switcher on the phone. |
This could be disabled for iOS using the following cordova plugin. |
L2 |
||
2.10 |
Verify that the app does not hold sensitive data in memory longer than necessary, and memory is cleared explicitly after use. |
Coding/Observation Task |
L2 |
||||
2.11 |
Verify that the app enforces a minimum device-access-security policy, such as requiring the user to set a device passcode. |
Coding Task |
We could use the following cordova plugin to check if a device passcode/lock screen has been set. |
L2 |
|||
2.12 |
Verify that the app educates the user about the types of personally identifiable information processed, as well as security best practices the user should follow in using the app. |
Manual Observation |
L2 |
8.5.5. Cryptography
| V3 | Cryptography | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
3.1 |
Verify that the app does not rely on symmetric cryptography with hardcoded keys as a sole method of encryption. |
Manual Observation |
This should be covered by 2.1 |
L1+L2 |
|||
3.2 |
Verify that the app uses proven implementations of cryptographic primitives. |
Coding Task |
L1+L2 |
||||
3.3 |
Verify that the app uses cryptographic primitives that are appropriate for the particular use-case, configured with parameters that adhere to industry best practices. |
Coding Task |
L1+L2 |
||||
3.4 |
Verify that the app does not use cryptographic protocols or algorithms that are widely considered deprecated for security purposes. |
Coding Task |
L1+L2 |
||||
3.5 |
Verify that the app doesn’t re-use the same cryptographic key for multiple purposes. |
Coding/Observation Task |
L1+L2 |
||||
3.6 |
Verify that all random values are generated using a sufficiently secure random number generator. |
Coding Task |
L1+L2 |
8.5.6. Authentication and Session Management
| V4 | Authentication and Session Management | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
4.1 |
Verify that if the app provides users with access to a remote service, an acceptable form of authentication such as username/password authentication is performed at the remote endpoint. |
Keycloak Task |
L1+L2 |
||||
4.2 |
Verify that the remote endpoint uses randomly generated access tokens to authenticate client requests without sending the user’s credentials. |
Keycloak Task |
L1+L2 |
||||
4.3 |
Verify that the remote endpoint terminates the existing session when the user logs out. |
Keycloak Task |
Keycloak has its own logout flow on the server to terminate a user’s session. |
L1+L2 |
|||
4.4 |
Verify that a password policy exists and is enforced at the remote endpoint. |
Keycloak Task |
Keycloak has an in-depth password policy manager. |
L1+L2 |
|||
4.5 |
Verify that the remote endpoint implements an exponential back-off, or temporarily locks the user account, when incorrect authentication credentials are submitted an excessive number of times. |
Keycloak Task |
Keycloak will allow you to both temporarily and permanently lock out a user after a specified number of failed login attempts. |
L1+L2 |
|||
4.6 |
Verify that biometric authentication, if any, is not event-bound (i.e. using an API that simply returns "true" or "false"). Instead, it is based on unlocking the keychain/keystore. |
Coding/Observation Task |
L2 |
||||
4.7 |
Verify that sessions are terminated at the remote endpoint after a predefined period of inactivity. |
Keycloak Task |
L2 |
||||
4.8 |
Verify that a second factor of authentication exists at the remote endpoint and the 2FA requirement is consistently enforced. |
Keycloak Task |
Keycloak can be setup to enforce 2FA. This can also be done on user creation so when the new user logs in for the first time, the second layer of protection will already be enforced. |
L2 |
|||
4.9 |
Verify that step-up authentication is required to enable actions that deal with sensitive data or transactions. |
Coding/Keycloak Task |
We should ensure that Keycloak authentication should be performed again when carrying out a sensitive/important action in the mobile app. |
L2 |
|||
4.10 |
Verify that the app informs the user of all login activities with his or her account. Users are able view a list of devices used to access the account, and to block specific devices. |
Keycloak/Coding Task |
Keycloak supports viewing of sessions/IP addresses, but doesn’t currently allow to see user-agents/geo locations of the device. |
L2 |
8.5.7. Network Communication
| V5 | Network Communication | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
5.1 |
Verify that data is encrypted on the network using TLS. The secure channel is used consistently throughout the app. |
Coding Task |
L1+L2 |
||||
5.2 |
Verify that the TLS settings are in line with current best practices, as far as they are supported by the mobile operating system. |
Coding/Observation Task |
L1+L2 |
||||
5.3 |
Verify that the app verifies the X.509 certificate of the remote endpoint when the secure channel is established. Only certificates signed by a valid CA are accepted. |
Keycloak Task |
This could be carried out with client cert auth in keycloak. |
L1+L2 |
|||
5.4 |
Verify that the app either uses its own certificate store, or pins the endpoint certificate or public key, and subsequently does not establish connections with endpoints that offer a different certificate or key, even if signed by a trusted CA. |
Keycloak/Coding Task |
Public key pinning might be better than full certificate pinning as handling cert renewals, compromises may be easier to work with, but requires more investigation. We should also consider HPKP. |
TrustKit for Android can be used for certificate pinning. |
TrustKit for iOS can be user for certificate pinning. |
The Secure HTTP cordova plugin will allow certificate pinning. |
L2 |
5.5 |
Verify that the app doesn’t rely on a single insecure communication channel (email or SMS) for critical operations, such as enrollments and account recovery. |
Keycloak Task |
Keycloak has a credentials reset flow which allows you to configure how password/credential resets should be carried out. |
L2 |
|||
5.6 |
Verify that the app uses some form of whitelisting to control access to external domains. |
Coding Task |
We should follow the whitelist guide for controlling external domain access in Cordova. |
L1+L2 |
8.5.8. Environmental Interaction
| V6 | Environmental Interaction | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
6.1 |
Verify that the app only requires the minimum set of permissions necessary. |
Coding/Process Task |
Check the AndroidManifest.xml for unrequired permissions.. |
Check the config.xml for unrequired permissions. |
L1+L2 |
||
6.2 |
Verify that all inputs from external sources and the user are validated and if necessary sanitized. This includes data received via the UI, IPC mechanisms such as intents, custom URLs, and network sources. |
Coding Task |
We should ensure that we are HTML encoding data that is coming from the server if it’s being rendered as html. |
L1+L2 |
|||
6.3 |
Verify that the app does not export sensitive functionality via custom URL schemes, unless these mechanisms are properly protected. |
Coding Task |
L1+L2 |
||||
6.4 |
Verify that the app does not export sensitive functionality through IPC facilities, unless these mechanisms are properly protected. |
Coding/Observation Task |
L1+L2 |
||||
6.5 |
Verify that JavaScript is disabled in WebViews unless explicitly required. |
Coding/Observation Task |
L1+L2 |
||||
6.6 |
Verify that WebViews are configured to allow only the minimum set of protocol handlers required (ideally, only https). Potentially dangerous handlers, such as file, tel and app-id, are disabled. |
Coding/Observation Task |
L1+L2 |
||||
6.7 |
Verify that the app does not load user-supplied local resources into WebViews. |
Coding/Observation Task |
L1+L2 |
||||
6.8 |
Verify that if Java objects are exposed in a WebView, verify that the WebView only renders JavaScript contained within the app package. |
Coding/Observation Task |
Some content security policies might be of use here to limit what javascript can be executed from sources. |
L1+L2 |
|||
6.9 |
Verify that object serialization, if any, is implemented using safe serialization APIs. |
Coding/Observation Task |
L1+L2 |
||||
6.10 |
Verify that the app detects whether it is being executed on a rooted or jailbroken device. Depending on the business requirement, users are warned, or the app is terminated if the device is rooted or jailbroken. |
Coding Task |
For both iOS and Android we could use the cordova-plugin-iroot. |
L2 |
8.5.9. Code Quality and Build Settings
| V7 | Code Quality and Build Settings | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
7.1 |
Verify that the app is signed and provisioned with valid certificate. |
Coding/Process/Observation Task |
L1+L2 |
||||
7.2 |
Verify that the app has been built in release mode, with settings appropriate for a release build (e.g. non-debuggable). |
Coding Task |
L1+L2 |
||||
7.3 |
Verify that debugging symbols have been removed from native binaries. |
Coding Task |
L1+L2 |
||||
7.4 |
Verify that debugging code has been removed, and the app does not log verbose errors or debugging messages. |
Coding/Observation Task |
L1+L2 |
||||
7.5 |
Verify that the app catches and handles possible exceptions. |
Coding Task |
L1+L2 |
||||
7.6 |
Verify that error handling logic in security controls denies access by default. |
Coding Task |
L1+L2 |
||||
7.7 |
Verify that in unmanaged code, memory is allocated, freed and used securely. |
Coding/Observation Task |
L1+L2 |
||||
7.8 |
Free security features offered by the toolchain, such as byte-code minification, stack protection, PIE support and automatic reference counting, are activated. |
Coding/Observation Task |
L1+L2 |
8.5.10. Resiliency Against Reverse Engineering Requirements
| V8 | Resiliency Against Reverse Engineering Requirements | Category | Comments | Android | iOS | Cordova | Level |
|---|---|---|---|---|---|---|---|
8.1 |
Verify that the app implements two or more functionally independent methods of root detection and responds to the presence of a rooted device either by alerting the user or terminating the app. |
Coding Task |
Implementation will be covered by 6.10 |
R |
|||
8.2 |
Verify that the app implements multiple functionally independent debugging defenses that, in context of the overall protection scheme, force adversaries to invest significant manual effort to enable debugging. All available debugging protocols must be covered (e.g. JDWP and native). |
Coding Task |
It would be a good idea here to crash the debugger/browser where possible. |
R |
|||
8.3 |
Verify that the app detects, and responds to, tampering with executable files and critical data. |
Coding Task |
R |
||||
8.4 |
Verify that the app detects the presence of widely used reverse engineering tools, such as code injection tools, hooking frameworks and debugging servers. |
Coding Task |
R |
||||
8.5 |
Verify that the app detects, and response to, being run in an emulator using any method. |
Coding Task |
R |
||||
8.6 |
Verify that the app detects, and responds to, modifications of process memory, including relocation table patches and injected code. |
Coding Task |
R |
||||
8.7 |
Verify that the app implements multiple different responses to tampering, debugging and emulation (requirements 9.2 - 9.6), including stealthy responses that don’t simply terminate the app. |
Coding Task |
R |
||||
8.8 |
Verify all executable files and libraries belonging to the app are either encrypted on the file level and/or important code and data segments inside the executables are encrypted or packed. Trivial static analysis should not reveal important code or data. |
Coding Task |
R |
||||
8.9 |
Verify that obfuscating transformations and functional defenses are interdependent and well-integrated throughout the app. |
Coding Task |
R |
||||
8.10 |
Verify that the app implements a 'device binding' functionality when a mobile device is treated as being trusted. Verify that the device fingerprint is derived from multiple device properties. |
Coding Task |
R |
||||
8.11 |
Verify that the app uses multiple functionally independent means of emulator detection that, in context of the overall protection scheme, force adversaries to invest significant manual effort to run the app in an emulator (supersedes requirement 8.5). |
Coding Task |
R |
||||
8.12 |
Verify that if the architecture requires sensitive information be stored on the device, the app only runs on operating system versions and devices that offer hardware-backed key storage. Alternatively, the information is protected using obfuscation. Considering current published research, the obfuscation type and parameters are sufficient to cause significant manual effort to reverse engineers seeking to comprehend or extract the sensitive data. |
Coding Task |
R |
||||
8.13 |
Verify that if the architecture requires sensitive computations be performed on the client-side, these computations are isolated from the operating system by using a hardware-based SE or TEE. Alternatively, the information is protected using obfuscation. Considering current published research, the obfuscation type and parameters are sufficient to cause significant manual effort to reverse engineers seeking to comprehend the sensitive portions of the code and/or data. |
Coding Task |
R |
8.5.11. Other Mobile Vulnerabilities