[Weekly Reading] #1 Essential iOS App Security Cases Developers Shouldn't Overlook
Although iOS is less vulnerable than Android, it still faces security issues. It is the responsibility of the developer to secure code and data communication
Here are some preliminary-level threats that every iOS developer should take care of:
1. Screen Recording and Screen Capturing
An attacker can record sensitive screens such as login pages to capture username and password. Also possible to record paid video content
To fix that, we can observe the UIScreen.capturedDidChangeNotification
and check for UIScreen.main.isCaptured
Observe changes to isCaptured with NotificationCenter:
NotificationCenter.default.addObserver(self, selector: #selector(screenCaptureDidChange),
name: UIScreen.capturedDidChangeNotification,
object: nil)
Handle the notification:
@objc func screenCaptureDidChange() {
print("screenCaptureDidChange.. isCapturing: \\(UIScreen.main.isCaptured)")
if UIScreen.main.isCaptured {
//TODO: They started capturing..
print("screenCaptureDidChange - is recording screen")
} else {
//TODO: They stopped capturing..
print("screenCaptureDidChange - screen recording stoped")
}
}
2. Weak Jail Break Detection
Application logic and behavior can be compromised on JailBroken devices, which can expose the application to attacks. Relying solely on jailbreak detection methods may not be sufficient to ensure the safety of your app
3 ways to identify whether a device is jailbroken or not:
- Looking for unique files and applications that are installed on jailbroken devices
private var filesPathToCheck: [String] {
return ["/private/var/lib/apt",
"/Applications/Cydia.app",
"/private/var/lib/cydia",
"/private/var/tmp/cydia.log",
"/Applications/RockApp.app",
"/Applications/Icy.app",
"/Applications/WinterBoard.app",
"/Applications/SBSetttings.app",
"/Applications/blackra1n.app",
"/Applications/IntelliScreen.app",
"/Applications/Snoop-itConfig.app",
"/usr/libexec/cydia/",
"/usr/sbin/frida-server",
"/usr/bin/cycript",
"/usr/local/bin/cycript",
"/usr/lib/libcycript.dylib",
"/bin/sh",
"/usr/libexec/sftp-server",
"/usr/libexec/ssh-keysign",
"/Library/MobileSubstrate/MobileSubstrate.dylib",
"/bin/bash",
"/usr/sbin/sshd",
"/etc/apt",
"/usr/bin/ssh",
"/bin.sh",
"/var/checkra1n.dmg",
"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
"/Library/MobileSubstrate/DynamicLibraries/Veency.plist"]
}
func isJailBrokenFilesPresentInTheDirectory() -> Bool{
var checkFileIfExist: Bool = false
filesPathToCheck.forEach {
checkFileIfExist = fm.fileExists(atPath: $0) ? true : false
if checkFileIfExist{
return
}
}
return checkFileIfExist
}
- Checking if a file can be modified outside the application bundle. Developers can use this test to check if their application follows sandboxing rules
func canEditSandboxFilesForJailBreakDetection() -> Bool {
let jailBreakTestText = "Test for JailBreak"
do {
try jailBreakTestText.write(toFile:"/private/jailBreakTestText.txt", atomically:true, encoding:String.Encoding.utf8)
return true
} catch {
let resultJailBroken = isJailBrokenFilesPresentInTheDirectory()
return resultJailBroken
}
}
- Calling the Cydia URL scheme (Cydia://) from an application successfully, it means the device is jailbroken
// Protocol function extended for JailBreak detection
func assignJailBreakCheckType() -> Bool {
// If it is run on simulator follow the regular flow of the app
if !isSimulator {
// Check if Cydia app is installed on the device
guard UIApplication.shared.canOpenURL(URL(string: "cydia://")!) else {
return false
}
return true
}
return true
}
3. Keychain Data Protection
On JailBroken devices, a keychain item with a vulnerable accessibility option can be easily exposed to other applications or attackers with physical access
However, developers have multiple actions to mitigate this security risk:
kSecAttrAccessibleWhenUnlocked
kSecAttrAccessibleAfterFirstUnlock
kSecAttrAccessibleAlways
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
kSecAttrAccessibleAlwaysThisDeviceOnly
Choose the easiest or more prone to vulnerable options like kSecAttrAccessibleWhenUnlocked
may lead to potential security risk
The 'kSecAttrAccessibleAfterFirstUnlock
' or 'kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
' modes should only be used when the application requires a KeyChain item for background processing.
4. File Data Protection
When a file is to be saved, a developer can choose from these options to better use data protection
atomic: An option to write data to an auxiliary file first and then replace the original file with the auxiliary file when the write completes.
**withoutOverwriting:**An option that attempts to write data to a file and fails with an error if the destination file already exists.
noFileProtection: An option to not encrypt the file when writing it out.
completeFileProtection: An option to make the file accessible only while the device is unlocked.
completeFileProtectionUnlessOpen: An option to allow the file to be accessible while the device is unlocked or the file is already open.
completeFileProtectionUntilFirstUserAuthentication: An option to allow the file to be accessible after a user first unlocks the device.
fileProtectionMask: An option the system uses when determining the file protection options that the system assigns to the data.
Choosing noFileProtection should lead to potential security risks.
You should use ‘completeFileProtectionUnlessOpen’ and ‘completeFileProtectionUntilFirstUserAuthentication’ to have data protection on all files.
Encrypting a file on the first write
do {
try data.write(to: fileURL, options: .completeFileProtection)
}
catch {
// Handle errors.
}
For an existing file, you can use either NSFileManager
/FileManager
or NSURL
:
try FileManager.default.setAttributes([.protectionKey: .completeFileProtection], ofItemAtPath: fileURL.path)
// Or
// cast as `NSURL` because the `fileProtection` property of `URLResourceValues` is read-only.
try (fileURL as NSURL).setResourceValue(URLFileProtection.complete, forKey: .fileProtectionKey)
With Core Data, you can pass the protection type when adding the persistent store:
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: [NSPersistentStoreFileProtectionKey: .completeFileProtection])
5. Show/Hide password field
When you identify that the screen is being recorded, use a mask over the password or any sensitive text field to protect data security. Also, hide/dismiss the keyboard while the app is being recorded
if(isRecording){
let maskView = UIView(frame: CGRect(x: 64, y: 0, width: 128, height: 128))
maskView.backgroundColor = .blue
maskView.layer.cornerRadius = 64
yourView.mask = maskView
}
6. HTTP Request
If the application uses an insecure communication channel (HTTP), means that an attacker on the same network as the victim could carry on a man-in-the-middle attack by injecting a 301 HTTP redirection response to an attacker-controlled server
7. Privacy Resources Access
The application can access user resources like contacts, location, Bluetooth device ID, camera, photos
However, this could lead to a data leak if the data is transmitted insecurely
The data must be encrypted before transmitting to the server to prevent this. In addition, it’s crucial to verify that there are no third-party libraries in use that access resources insecurely
8. Debug Logs Enabling
Developer should avoid printing method completion details and sensitive information in release builds
Use #if DEBUG
when we want to log information for only debug builds
let logger = Logger(subsystem: "com.yourdomain.yourapp", category: "yourcategory")
#if DEBUG
logger.info("This is an info message")
// logger.debug("This is a debug message")
#endif
You can check OSLog and Unified logging as recommended by Apple
References
Thanks for Reading! ✌️
If you have any questions or corrections, please leave a comment below or contact me via my LinkedIn account Pham Trung Huy.
Happy coding 🍻