првиет хабр! на момент написание данной статьи четыре уязвимости о котрых пойдет речь уже залатали в IOS 27 beta 5 для разработчиков а сами уязвимости уже слиты разработчиком johnny (@0xjohnny в X). всего вышло три уязвимости среди кокторых:
Матчасть, без которой дальше нечитаемо
Прежде чем про баги — четыре понятия
Контейнеры и containermanagerd (MCM). На iOS у каждого приложения свой каталог данных и, возможно, общие «app groups». Раздаёт их системный демон containermanagerd (framework — MobileContainerManager, клиентская либа — libmcm). Приложение не лезет в файловую систему само — оно через XPC просит у MCM «дай мне мой контейнер», а тот возвращает путь и sandbox extension к нему.
Sandbox extension. Это токен. Процесс с нужным правом может «выписать» токен на путь (sandbox_extension_issue_file), а другой процесс — «погасить» его (sandbox_extension_consume) и получить доступ к пути вне своей обычной песочницы. MCM именно так и работает: выдал контейнер = выписал extension на его путь. Ключевой момент: если MCM выпишет extension не на тот путь — процесс легально получит доступ туда, куда не должен.
Классы контейнеров (container class). Внутри MCM контейнеры разложены по числовым «классам»: данные приложений, app groups, системные группы, контейнеры расширений и т.д. В writeup фигурируют class-2 (данные приложения), class-7 (app group), class-12 (некий built-in-разрешённый), class-13 (кэш MobileGestalt). Класс определяет, какое семейство путей и какая политика доступа применяется. [номера классов — как у автора; при публикации сверить с containermanagerd на конкретном билде]
Идентичность кода (CodeDirectory identifier vs cdhash). У каждого подписанного бинаря есть CodeDirectory. В нём два разных поля:
identifier — строка (обычно как bundle id), которую при dev-подписи разработчик задаёт сам. Это фактически произвольный ввод.
cdhash — криптографический хеш содержимого, подделать нельзя.
Запомните различие — на нём стоит первый баг.
Часть первая. MobileHouseArrest identity trust (class-2 / class-7) — patched
Подсистема. house_arrest — это lockdown-сервис, через который Finder/iTunes лезут в папку Documents приложения (file sharing). Право com.apple.mobile.MobileHouseArrest позволяет процессу просить у MCM доступ к контейнерам приложений «от имени» механизма file sharing.
Корень бага. MCM при выдаче чужого контейнера использовал CodeDirectory identifier вызывающего как ключ авторизации — то есть верил строке, которую dev-подписант контролирует сам. А identifier, как мы разобрали в матчасти, при dev-подписи задаётся произвольно.
Что это даёт. Dev-signed приложение с этим entitlement могло попросить контейнер чужого приложения (class-2) и чужую app group (class-7), подставив нужный identifier — и MCM выписывал sandbox extension на путь жертвы:
/private/var/mobile/Containers/Data/Application// ← данные чужого приложения /private/var/mobile/Containers/Shared/AppGroup// ← чужая app group
Почему это серьёзно. В числе доступных целей — app group приложения «Заметки» и её NoteStore.sqlite. Эприватной пользовательской базе заметок. Плюс отдельная class-13 ветка вела к каталогу кэшаMobileGestalt.
Класс уязвимости. Confused deputy: авторизация по подделываемому идентификатору вместо криптостойкого (cdhash) или вместо явной проверки entitlement на конкретную цель.
Как чинится. Не доверять identifier как ключу доступа. Проверять либо cdhash, либо — правильнее — что у вызывающего есть entitlement именно на запрашиваемую цель, а не «вообще на house arrest».
Часть вторая. geod class-12 traversal → MobileGestalt R/W — patched
Подсистема. com.apple.geod — демон геосервисов. У него свой контейнер.
Корень бага. Класс 12 держал com.apple.geod как «built-in allowed container» — то есть контейнер, котоённым. Дальше в дело вступал partDomain — параметр запроса, который выбирает под-домен/под-путь внутриконтейнера. Его не валидировали. [partDomain — терминология автора; сверить с PoC/дизассемблером]
Что происходило. Непроверенный partDomain перенаправлял sandbox extension (уже с правами чтения/записи, потому что geod-контейнер built-in-разрешён) в системную группу MobileGestalt:
/private/var/containers/Shared/SystemGroup/systemgroup.com.apple.mobilegestaltcache/Library/Caches/ └─ com.apple.MobileGestalt.plist
Итог. Чтение и запись живого MG-кэша и его plist. Не произвольный доступ к ФС — именно к MG-кэшу. Но эileGestalt меняет то, что система «думает» о возможностях устройства.
Класс уязвимости. Path traversal / directory redirection через невалидированный параметр, усиленный тенаделён R/W-правами.
Как чинится. Валидировать partDomain по allow-list разрешённых под-доменов для класса; не давать builtводить extension за пределы своего собственного дерева.
Часть третья. InstallCoordination persisted state — entry patched, логика осталась
Подсистема. installcoordinationd координирует установки/обновления. Он умеет работать с «promise graph» — отложенным графом обещанных операций (устанавливаем/материализуем позже). При материализации локализаций он раскладывает InfoPlist.strings.
Корень бага — две части.
Дыра в авторизации class-13 + traversal через partDomain открывали доступ к каталогам состояния демона: …/Library/InstallCoordination/PromiseStaging/ …/Library/InstallCoordination/DataPromises/ …/Library/InstallCoordination/Coordinators/
Доверие к сохранённому графу + следование за финальным симлинком. Приложение подкладывало свой promise graph в эти каталоги. Демон при следующем проходе восстанавливал граф как доверенный и при материализации InfoPlist.strings шёл по финальному симлинку локализации — то есть предоставлял примитив «демон с высокими правами идёт по контролируемому мной симлинку»
Часть четвертая. cfprefsd missing-file creation — still present
Подсистема. cfprefsd — демон настроек CoreFoundation, создаёт/читает plist-файлы preferences.
Корень бага. Гонка на «сыром» пути контейнера (raw-container path race) + несоответствие финального сить один выбранный отсутствующий файл.
Границы примитива — и это надо честно написать в статье, иначе перехвалишь:
создаётся новый файл, режим 0644, нулевого размера;
нельзя перезаписать или обрезать существующий файл;
нет контроля над содержимым нового файла (он пустой).
То есть это «создать пустышку по выбранному пути», а не запись данных. Сам по себе слабый примитив; ценность — только как звено в более длинной цепочке (например, создать файл-маркер там, где его наличие что-то меняет).
Статус. Логика всё ещё присутствует в образе CoreFoundation в iOS 27 beta 5. Runtime-подтверждение на этом конкретном билде автор не сделал — так и напиши, не выдавай за подтверждённое.
Класс уязвимости. TOCTOU на пути + symlink mismatch → controlled file creation (без контроля содержимого).
Часть пятая. Что здесь общего — и это главный вывод статьи
Четыре разных демона, один архитектурный мотив: containermanagerd (и то, что вокруг него) авторизует в признаку, которому доверять нельзя.
Bug 1 — доверие подделываемому identifier вместо cdhash/entitlement.
Bug 2 — доверие built-in-статусу класса + невалидированный partDomain.
Bug 3 — доверие persisted graph + следование за симлинком.
Bug 4 — гонка + symlink mismatch на пути.
Три из четырёх так или иначе включают невалидированный partDomain / симлинк на финальном сегменте пути. Это и есть сюжет: не четыре случайных бага, а один кластер вокруг того, как MCM решает «куда указывает этот запрос». Отсюда и «Red Wedding» — их и закрыли группой, потому что чинится общий корень: жёсткая валидация целевого пути и отификаторам.
спасибо за внимание хабр! наши соц. сети:
телеграмм канал: https://t.me/darkgraam
гитхаб автора проекта: https://github.com/nepcukk
гитхаб второго из девелоперов DarkGram: https://github.com/murk-sus
телеграмм чат (для бета версий): https://t.me/jailbreak_iPhone_iPad_android