Получите привилегии администратора с помощью Swift для приложения Mac

Я пишу кусок программного обеспечения, которому часто нужно запускать команду с привилегиями root.

Сейчас я делаю это, запрашивая у пользователя пароль один раз, сохраняя его и затем предоставляя этот пароль NSAppleScript в качестве аргумента наряду с with administrator privileges,

Это, очевидно, действительно небезопасно для пользователя, поскольку кто-то может получить доступ к его паролю.

Я искал лучшую часть недели и не могу найти решение.

SMJobBless, кажется, позволяет устанавливать ваше приложение с более высокими привилегиями.

Я последовал примеру приложения и получаю сообщение об ошибке из скрипта SMJobBlessUtil.

Вот ошибка:

SMJobBlessUtil.py: tool designated requirement (identifier "com.domain.AppName.SampleService" and anchor apple generic and certificate leaf[subject.CN] = "Mac Developer: firstName lastName (XXXXXXXXXX)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */) doesn't match entry in 'SMPrivilegedExecutables' (anchor apple generic and identifier "com.domain.AppName.SampleService" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.CN] = "Mac Developer: firstName lastName (XXXXXXXXXX)")

Очевидно, что-то не так. Вот соответствующие списки

Услуги Инфо Плист

<?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>CFBundleIdentifier</key>
    <string>com.domain.AppName.SampleService</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>SampleService</string>
    <key>CFBundleVersion</key>
    <string>6</string>
    <key>SMAuthorizedClients</key>
    <array>
        <string>anchor apple generic and identifier "com.domain.AppName" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = xxxxxxxxxx)</string>
    </array>
</dict>
</plist>

Apps Info plist

<?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>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleDisplayName</key>
    <dict/>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleGetInfoString</key>
    <dict/>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>Away</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0.99</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>9</string>
    <key>LSApplicationCategoryType</key>
    <string>public.app-category.utilities</string>
    <key>LSMinimumSystemVersion</key>
    <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
    <key>LSUIElement</key>
    <true/>
    <key>NSHumanReadableCopyright</key>
    <string>Copyright © 2016 firstName lastName. All rights reserved.</string>
    <key>NSMainStoryboardFile</key>
    <string>Main</string>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <key>SMPrivilegedExecutables</key>
    <dict>
        <key>com.domain.AppName.SampleService</key>
        <string>anchor apple generic and identifier "com.domain.AppName.SampleService" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.CN] = "Mac Developer: firstName lastName (XXXXXXXXXX)"</string>
    </dict>
</dict>
</plist>

Я посмотрел на этот пост stackru и многим другим нравится. Как я понимаю, мои списки настроены правильно. Что я делаю неправильно?

3 ответа

Решение

Ключевая часть этого подхода описана в разделе "СПИСКИ СОБСТВЕННОСТИ" в разделе "Как это работает" файла ReadMe.txt:

[…] Когда вы подписываете вспомогательный инструмент с помощью идентификатора разработчика, XCode автоматически устанавливает назначенное требование вспомогательного инструмента, как это, и это то, что вы должны использовать для SMPrivilegedExecutables. Более того, это то, что делает команда "setreq", показанная выше: извлекает указанное требование из встроенного инструмента и помещает его в исходный код приложения Info.plist.

Поскольку вы не подписываете продукты (по крайней мере, не с сертификатом, описанным в ваших примерах), этот процесс всегда будет неудачным.

Если вы не участвуете в Программе разработчика, вы можете создать самозаверяющий сертификат для подписи. Однако это более или менее противоречит цели требования подписи. Если вы не планируете регистрироваться в Программе разработчика, вы сможете сократить процесс следующим образом:

  1. В Info.plist вашего приложения сокращайте требование под SMPrivilegedExecutables чтобы просто соответствовать идентификатору помощника:

    <string>identifier "com.domain.AppName.SampleService"</string>

    • В Info.plist вашего помощника, сокращайте требование под SMAuthorizedClients чтобы просто соответствовать идентификатору приложения:

    <string>identifier "com.domain.AppName"</string>

    • Игнорируйте инструкции "Построение и запуск примера" файла ReadMe.txt и intead просто соберите и запустите проект как обычно.

Я не могу сказать, что рекомендую это, конечно; эти требования подписи существуют по уважительной причине. Это, по крайней мере, лучше, чем последняя альтернатива, которая будет использовать этот NSAppleScript, чтобы дать исполняемому файлу помощника корневой бит setuid через chmod а также chown,


Приложение, чтобы развить некоторые из понятий в игре здесь:

Запуск привилегированного кода сопряжен с множеством потенциальных пробелов в безопасности; Безопасная аутентификация пользователя - это только первый шаг. Передача всех привилегированных операций отдельному процессу - еще один важный шаг, но основная проблема, которая остается, заключается в том, как обеспечить, чтобы ваше приложение - то, для которого пользователь фактически предоставил привилегии - было единственным объектом, способным использовать привилегированный доступ.

Пример Apple демонстрирует использование подписи кода для решения этой проблемы. На всякий случай, если вы не знакомы: подписывание кода подразумевает криптографическую маркировку ваших конечных продуктов таким образом, чтобы OS X могла убедиться, что ваши программы не были заменены скомпрометированными версиями. Эти дополнительные ссылки на "листок сертификата" приведены в оригинальном примере SMAuthorizedClients а также SMPrivilegedExecutables специально для этого; они описывают сертификат, с которым ваше приложение и помощник должны быть подписаны, чтобы взаимодействовать друг с другом.

Чтобы помочь немного нарисовать картину, вот краткое изложение того, как это заканчивается:

  1. Ваш пользователь предоставляет разрешение на launchd для установки демона-помощника с пометкой com.domain.AppName.SampleService,
  2. launchd находит com.domain.AppName.SampleService запись под SMPrivilegedExecutables в Info.plist вашего приложения; это описывает сертификат, с которым двоичный файл помощника должен быть подписан. (Если они не совпадают, то теоретически злоумышленник заменил ваш вспомогательный инструмент своей собственной версией, чтобы запустить его как root.)
  3. Установив действительный помощник, ваше приложение отправляет запрос на launchd, чтобы вызвать помощника под вашим контролем. На этом этапе launchd консультируется с SMAuthorizedClients раздел Info.plist вашего вспомогательного инструмента, чтобы убедиться, что приложение действительно имеет право на запуск инструмента. И, конечно, он проверяет подпись вашего приложения, чтобы убедиться, что он не был подделан.

Возвращаясь к вашему сценарию, ваши продукты работают в настоящее время, исключив шаги подписи. Единственное, что вы указали launchd для проверки, - содержит ли Info.plist вашего приложения идентификатор "com.domain.AppName". Поскольку ничто не мешает злоумышленнику изменить свой Info.plist, чтобы сказать это, вы рассчитываете на то, что они не смогут использовать ваш вспомогательный инструмент, чтобы причинить какой-либо вред, когда они его контролируют.

Дополнительное приложение с изложением альтернатив:

Чтобы добавить к тому, что уже было сказано, заставить эти требования подписи кода работать и таким образом, чтобы фактически обеспечить значимую безопасность, нетривиально. Хотя в некотором роде это довольно сложно, SMJobBlessна практике требования довольно слабые - как показано, вы можете просто предоставить идентификаторы пакетов приложений (которые могут повторно использоваться кем угодно, для них нет ограничений на уникальность).

Подписание кода и использование листовых сертификатов имеет важное значение, но это все равно оставляет довольно большую дыру, если уязвимость безопасности когда-либо обнаруживается в приложении, которое вы позволяете взаимодействовать с вашим вспомогательным инструментом. Если вашему вспомогательному инструменту не требуется минимальная версия вашего приложения, которой разрешено взаимодействовать с ним, можно использовать «атаку перехода на более раннюю версию», когда злоумышленник загружает старую версию вашего приложения, а затем продолжает использовать ее.

На мой взгляд, лучший способ обеспечить последовательное применение требований - это сценарий сборки. Для этого я создал PropertyListModifier.swift как часть SwiftAuthorizationSample .

Вы движетесь в правильном направлении. В настоящее время привилегированный вспомогательный инструмент является наилучшей практикой для выполнения задач в привилегированном режиме. Для этого вы также можете использовать Swift, но просто замените C-версию вызова функций на Swift. (Apple представила альтернативы в 10.11 SDK) Например, вместо

Boolean SMJobBless( CFStringRef domain, CFStringRef executableLabel, AuthorizationRef auth, CFErrorRef *outError);

ты можешь использовать:

SMJobBless(_: CFString!, _: CFString, _: AuthorizationRef, _: UnsafeMutablePointer<Unmanaged<CFError>?>) -> UInt8

Но я никогда не видел примеров привилегированного вспомогательного инструмента в Интернете... Так что вам нужно заглянуть в код Objective C. К счастью, Obj C кода не так много.

Другие вопросы по тегам