[Weekly Reading] #1 Essential iOS App Security Cases Developers Shouldn't Overlook

[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:

  1. 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
    }
  1. 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
    }
}
  1. 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:

  1. kSecAttrAccessibleWhenUnlocked

  2. kSecAttrAccessibleAfterFirstUnlock

  3. kSecAttrAccessibleAlways

  4. kSecAttrAccessibleWhenUnlockedThisDeviceOnly

  5. kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly

  6. 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

  1. 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.

  2. **withoutOverwriting:**An option that attempts to write data to a file and fails with an error if the destination file already exists.

  3. noFileProtection: An option to not encrypt the file when writing it out.

  4. completeFileProtection: An option to make the file accessible only while the device is unlocked.

  5. completeFileProtectionUnlessOpen: An option to allow the file to be accessible while the device is unlocked or the file is already open.

  6. completeFileProtectionUntilFirstUserAuthentication: An option to allow the file to be accessible after a user first unlocks the device.

  7. 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

IOS Mobile App Security: Best Practices for iOS Mobile Developers. | by Munendra Pratap Singh | Feb, 2024 | Medium


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 🍻