sensepost / objection

📱 objection - runtime mobile exploration
GNU General Public License v3.0
7.33k stars 840 forks source link

[bug] iOS Keychain dump false positive (with source code snippet) #494

Open BreakfastSerial opened 2 years ago

BreakfastSerial commented 2 years ago

Describe the bug During an iOS app penetration test, I dumped all keychain entries of the app and noticed "accessible_attribute": "kSecAttrAccessibleAlways", which is deprecated and should be considered a vulnerability for sensitive information in the keychain. After feedback from the developers I've been given the code-snippet where the entry is generated/stored and they undoubtedly configure kSecAttrAccessibleWhenUnlockedThisDeviceOnly for the entry.

I assume this frida/objection reports the wrong accessibility attribute.

To Reproduce Due to an NDA I can not share the application. The relevant source code (redacted/slightly modified):

private func genSav() throws -> SecKey {
    var error: Unmanaged<CFError>?

    guard let accCtrl = SecAccessControlCreateWithFlags(
        kCFAllocatorDefault,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        [],
        &error
    )

    var attribs: [String: Any] = [
        kSecAttrKeyType as String: kSecAttrKeyTypeEC,
        kSecAttrKeySizeInBits as String: 256,
        kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
        kSecPrivateKeyAttrs as String: [
            kSecAttrIsPermanent as String: true,
            kSecAttrApplicationTag as String: tag,
            kSecAttrAccessControl as String: accCtrl
        ]
    ]

    guard let genKey = SecKeyCreateRandomKey(attribs as CFDictionary, &error)

    return genKey
}

Expected behavior I would assume the keychain entry should have been reported as kSecAttrAccessibleWhenUnlockedThisDeviceOnly NOT kSecAttrAccessibleAlways.

Evidence / Logs / Screenshots

objection --debug --gadget "target.app" explore
[debug] Agent path is: /usr/local/lib/python3.9/dist-packages/objection/agent.js
[debug] Injecting agent...
Using USB device `iOS Device`
[debug] Attempting to attach to process: `target.app`
[debug] Unable to find process: `target.app`, attempting spawn
[debug] PID `2825` spawned, attaching...
[debug] Resuming PID `2825`
Agent injected and responds ok!
[...]
target.app on (iPhone: 14.4) [usb] # ios keychain dump
Note: You may be asked to authenticate using the devices passcode or TouchID
Save the output by adding `--json keychain.json` to this command
Dumping the iOS keychain...
Created                    Accessible  ACL  Type          Account  Service  Data
-------------------------  ----------  ---  ------------  -------  -------  ------------------------
2021-datetime +0000  Always           kSecClassKey                    (Key data not displayed)

Environment (please complete the following information):

Application Unfortunately I can't due to the NDA, see the code-snippet above.

leonjza commented 2 years ago

What does a dump with --raw look like?

leonjza commented 2 years ago

I'm not sure where this is going wrong to be honest. Printing the raw values of the constants using this snippet, I get

NSLog(@"kSecAttrAccessibleAlwaysThisDeviceOnly: %@", kSecAttrAccessibleAlwaysThisDeviceOnly);
NSLog(@"kSecAttrAccessibleAlways              : %@", kSecAttrAccessibleAlways);
2021-10-07 11:22:24.771696+0200 PewPew[38505:4210962] kSecAttrAccessibleAlwaysThisDeviceOnly: dku
2021-10-07 11:22:24.771820+0200 PewPew[38505:4210962] kSecAttrAccessibleAlways              : dk

That matches what we have for kSecAttrAccessibleAlwaysThisDeviceOnly and kSecAttrAccessibleAlways.

BreakfastSerial commented 2 years ago

I assume with --raw you mean dump_raw?

Sorry for all the redacting, I'm not sure which of the reported values are confidential, but I recon the interesting attribute here is accc = "<SecAccessControlRef.

Note: You may be asked to authenticate using the devices passcode or TouchID
Dumping the iOS keychain...
{
    UUID = "<UUID>";
    accc = "<SecAccessControlRef: aku;dacl(true)>";
    agrp = "<SOME-ID>.target.app";
    atag = {length = 62, bytes = <HEX-VALUES> };
    bsiz = 256;
    cdat = "2021-datetime";
    class = keys;
    crtr = 0;
    decr = 0;
    drve = 1;
    edat = "2001-datetime";
    encr = 0;
    esiz = 0;
    extr = 0;
    kcls = 1;
    klbl = {length = 20, bytes = <HEX-VALUE>};
    mdat = "2021-datetime";
    modi = 0;
    musr = {length = 0, bytes = 0x};
    next = 1;
    pdmn = dk;
    perm = 1;
    persistref = {length = 0, bytes = 0x};
    priv = 1;
    sdat = "2001-datetime";
    sha1 = {length = 20, bytes = <HEX-VALUE>};
    sign = 1;
    sync = 0;
    tkid = "com.apple.setoken";
    tomb = 0;
    type = 73;
    "v_Ref" = "<SecKeyRef:('com.apple.setoken') <HEX-VALUE>>";
    vrfy = 0;
    wrap = 0;
}

I'm definitely no iOS developer, but the code the developers provided seems sound and I've even tested multiple builds of the same application to make sure. I've also ran frida/objection on different hosts with the same result.

leonjza commented 2 years ago

This is useful thanks. Look like decodeAcl() has a parsing issue here.

BreakfastSerial commented 2 years ago

Thank you very much for your hard work, let me know if I can provide further insight or should test anything else.