My name is Daria Chastokolenko, and I have been in iOS development for four years. I work in the Geniusee team, and among the company's latest favorite projects is the development of a neobank for our partner. I want to share our team's recommendations on the security of mobile applications in 2021, which we used in this project. I will also be happy to hear your tips on mobile application security.
iOS App Security: Essential Steps to Take
The case extracts
First, I will talk a little about the project. Our client is a US-based fintech company that develops neobank for gamers, and Generation Z. Its primary mission is to create a digital alternative to the traditional financial system and enable each client to have more control over their finances.
The project started almost two years ago, and the Geniusee team joined a year ago in the Dedicated Team format. When the stages of business analysis and the Discovery phase ended, the solution's active transformation began. We took the first steps to create iOS and Android mobile applications and to form a new team.
Our team included two iOS developers, two Android developers, three DevOps engineers, three backend developers, a business analyst, two project managers, three QA engineers, a technologist, and a CTO. We worked with our client's in-house team based in California. By the way, the locations of the teams helped us follow the “Follow the sun” model, with which product development is carried out almost 24 hours a day :)
Security risks in iOS
Problem
60% of financial companies now develop applications for phones, as most mobile users spend 90% of their time in mobile applications. Thus, it is becoming increasingly important to ensure the iOS App security and guarantee that user's information remains protected.
Any violations in the mobile application can potentially damage the entire system, so it is essential to know how to ensure security. Of course, the process of identifying all threats and determining the level of security of a huge application can be complicated. However, given the seriousness of this issue, we have created a list of potential threats and methods to address them.
According to OWASP statistics, there are ten main security risks for mobile applications:
- Improper Platform Usage;
- Insecure Data Storage;
- Insecure Communication;
- Insecure Authentication;
- Insufficient Cryptography;
- Insecure Authorization;
- Client Code Quality;
- Code Tampering;
- Reverse Engineering;
- Extraneous Functionality.
To comply with all elements of application security, our team used the MASVAS (Mobile App Security Verification Guide) system. This standard defines security requirements that can be applied to mobile apps on both iOS and Android. All requirements here are divided into two levels, L1 and L2. L1 is the basic level of requirements that absolutely all applications must meet, regardless of their purpose. The L2 includes applications that have highly sensitive data (this is our application). In addition to the security requirements standard, this guide also includes a checklist for testing all MSTG (Mobile Security Testing Guide) requirements, which tester teams can use.
Our tips on how to make an iOS app secure
To cover all the security issues of the mobile application, we conducted a special workshop, wrote a list of all possible iOS security issues and solutions for iOS-applications. Below I suggest you read about ios app security best practices and go through each item in more detail.
Secure Data Storing
Issue: Both iOS and Android support copying/pasting data via the clipboard. Confidential data can be stored, restored, or modified on the clipboard, regardless of whether the data source was initially encrypted. If it is in plain text when the user copies it, it will also be in plain text when other applications access the clipboard.
Solution: When entering sensitive information, such as passwords, credit card information, etc., be sure to mask the input fields and prevent this data from being cached.
textField.isSecureTextEntry = true
textField.autocorrectionType = .no
SSL Pinning
Problem: By default, when establishing an HTTPS SSL connection, the client verifies the server certificate. But the client does not check whether this certificate is precisely the certificate used by your server. The client's mapping of SSL certificates in the device's trusted certificate store to those used on the remote server opens a potential security hole. Most iOS applications use the TLS (Transport Layer Security) protocol to communicate with the server. Applications usually do not specify which certificates to trust and rely on the iOS certificate. However, the certificate store on the device can be easily compromised: the user can install a dangerous certificate and thus allow man-in-the-middle attacks when an attacker replaces the certificate and gains access to traffic. Attackers can use MitM attacks to steal login credentials or personal information to sabotage communications or corrupt data.
Solution: If HTTP traffic is not encrypted, anyone on your network can see it. So how can you check if it is allowed? This can be checked in the info.plist file:

SSL-pinning is a process that allows you to associate a server with its certificate or public key. In this case, the application rejects all certificates, except those "pinned," i.e., when communication with the server begins, the server certificate is checked with a "pinned" certificate/key. If they match, a client-server connection is established. There are two ways to implement pinning: through a certificate or a public key hash. Apple recommends that you use the CA (certificate authority) key hashes not to have to deploy the application again if you change the keys.

You can find the solution to this problem in the official guide from Apple.
Data leakage
Problem: there is no denying the fact that with the increase in the number of mobile applications, attackers have also improved the ways of hacking and stealing users' confidential information. Data leakage can occur during accidental data sharing, such as screening to multitasking or when the application goes into the background when the system snapshots the screen and saves it to disk.
Solution: possible solutions to the problem of data leakage may be:
1. Overlay screen, i.e., adding a logo or blue screen image to the background.
2. For better UX, you can also clear fields that contain security information.
3. Redirect the user to the previous screen.
var splash: UIImageView?
func sceneWillResignActive(_ scene: UIScene) {
splash = UIImageView(frame: UIScreen.main.bounds)
splas ?.image = UIImage(named: "splash")
splas?.isUserInteractionEnabled = false
UIApplication.shared.windows.first?.addSubview(splash ?? UIImageView())
}
func sceneDidBecomeActive(_ scene: UIScene) {
splash?.removeFromSuperview()
splash = nil
}
Face ID / Touch ID
Problem: Data theft due to physical theft of the phone. For criminals, the use of stolen information becomes as attractive as the sale of the device itself. The hijacker can access your personal information, so you must securely protect it.
Solution: in this case, we implemented a biometric login with a PIN code for each application launch.
But if you misuse this API, attackers can easily pass a biometric check. An example of a similar case is shown in the Dropbox and Evernote applications.
Storage API Overview:
1. UserDefaults - non-encrypted storage, available even on a locked device. No sensitive data can be stored in user defaults.
2. Keychain - encrypted SQLite database. It is decrypted when unlocking the device.
3. Secure Enclave - isolated from the central processor, used for an additional level of security, key storage for encryption Keychain entries.
An important point when using Keychain is access policies, i.e., the conditions under which the data will be decrypted.
An example of biometric login implementation from Apple documentation:

This approach is vulnerable, as it is easy for attackers to simply check the Boolean check and replace the value, which returns to true.
A safer way to implement this is to store the password in Keychain with specific access control that allows you to bind biometrics.
When you create an access control, you specify two conditions under which data from the Keychain can be retrieved.
1. This is the access level:

For example, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly means that data can only be accessed when the device is unlocked and only when the code is flooded on the device.
ThisDeviceOnly means that this data will not be shared via iCloud Keychain or backup.
2. Authentication level. This flag allows you to set up a biometric check to access data from Keychain.
Creating access control is as follows:

The second parameter just determines the level of access we need. The third parameter determines the authentication level, and you can specify a more flexible version of SecAccessControlCreateFlags.userPresence. This also provides for biometric verification, but in case of a failed attempt, a fullback is automatically performed before checking the poster.
Adding data:

Getting data:


Jailbreak Detection
Problem: The user can gain root access on a jailbroken device, which means installing software that is not verified by Apple and the malicious software may appear. Therefore, attackers can steal or sell sensitive data, passwords, and other authentication details.
Solution: possible solutions to the problem of data leakage may be:
1. Check the existence of paths, for example /bin/bash.
private func checkPathExists() -> Bool {
return access("/Applications/Cydia.app", F_OK) != -1 || access("/bin/bash", F_OK) != -1
}
private func checkPathExists() -> Bool {
let paths = [
"/usr/sbin/frida-server",
"/bin/bash",
"/Applications/Cydia.app"
]
let pathExists = paths.map { FileManager.default.fileExists(atPath: $0) }
return !pathExists.contains(false)
}
2. Check for dynamic libraries in memory via _dyld_image_count() та _dyld_get_image_name().
private func checkDyld() -> Bool {
let libs = [
"frida",
"cynject",
"libcycript"
]
for libraryIndex in 0..<_dyld_image_count() {
guard let loadedLibrary = String(validatingUTF8: _dyld_get_image_name(libraryIndex)) else {
return false
}
let validation = libs.map { loadedLibrary.lowercased().contains($0.lowercased()) }
return !validation.contains(false)
}
return false
}
3. Call fork() or popen(). third-party that can be used to fix the jailbroken device.
Conclusion
Creating safe and reliable applications for iOS is not an easy task. Mobile applications with all their functions are an integral part of almost everyone's life, so it is crucial that we, the developers, treat security and, consequently, user data with the utmost attention.
In this project, we managed to develop a strategy for the protection of mobile applications from the very beginning and consider as many vulnerabilities as possible, which could further become real threats and security problems. That is why we recommend thinking over the security system at the stage of creating the project architecture. And we took all the nuances into account before writing the code.
What practices do you use to protect mobile applications?