使用 swift 获得 Mac 应用程序的管理权限

Gain administration privileges with swift for a Mac Application

我正在编写一个需要经常 运行 具有 root 权限的命令的软件。

现在,我通过询问用户密码一次、保存密码然后将该密码作为参数与 with administrator privileges 一起提供给 NSAppleScript

这对用户来说显然是不安全的,因为有人可以访问他们的密码。

我已经搜索了一周的大部分时间,但找不到解决方案。

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)")

显然,有些地方不对劲。这是各自的 plists

服务信息 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>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>

应用信息 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>

我看了 at this Whosebug post 很多人都喜欢。据我了解,我的 plists 设置正确。我做错了什么?

你正朝着正确的方向前进。目前特权助手工具是在特权模式下执行任务的最佳实践。为此,您也可以使用 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

但我从未在 Internet 上看到过特权帮助工具的示例...所以您需要查看 Objective C 代码。幸好 Obj C 代码不多。

此方法的关键部分在 ReadMe.txt 的“工作原理”下的“属性 列表”部分进行了描述:

[…] when you sign the helper tool with a Developer ID, Xcode automatically sets the helper tool's designated requirement like this, and that's what you should use for SMPrivilegedExecutables. Moreover, this is what the "setreq" command shown above does: extracts the designated requirement from the built tool and put it into the app's Info.plist source code.

由于您没有签署产品(至少,没有使用示例中描述的证书),此过程将始终失败。

如果您不在 Developer Program 中,您可以 create a self-signed certificate 进行签名。然而,这或多或少地违背了签名要求的目的。如果您不打算注册开发者计划,您应该能够将流程简化如下:

  1. 在您应用的 Info.plist 中,将 SMPrivilegedExecutables 下的要求缩写为仅匹配助手的标识符:

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

  • 在您的助手 Info.plist 中,将 SMAuthorizedClients 下的要求缩写为仅匹配应用程序的标识符:

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

  • 忽略 ReadMe.txt 的“构建和 运行 示例”说明,而是照常构建和 运行 项目。

我当然不能说我推荐这个;这些签名要求的存在是有充分理由的。它至少比最终的替代方案要好,后者将使用 NSAppleScript 通过 chmodchown.

为辅助可执行文件提供根 setuid 位

附录,详细说明此处使用的一些概念:

运行 特权代码带有很多潜在的安全漏洞;安全地验证用户身份只是第一步。将所有特权操作委托给一个单独的进程是另一个强有力的步骤,但剩下的主要问题是如何确保您的应用程序——用户实际授予特权的应用程序——是唯一能够利用特权访问的实体。

苹果的例子演示了使用代码签名来解决这个问题。以防万一你不熟悉:代码签名涉及以加密方式标记你的最终产品,这样 OS X 可以验证你的程序没有被受损版本替换。原始示例的 SMAuthorizedClientsSMPrivilegedExecutables 中那些额外的“证书叶”引用专门用于此;它们描述了您的应用程序和帮助程序必须签署的证书才能相互交互。 被记录为用于在交互过程中验证代码签名以外的其他目的。正如 this blog post and the corresponding CVE, client programmers are responsible for ensuring cryptographicly safe inter-process communication between helper tool and app, e.g. using the SecCode API.

所指出的

为了稍微描绘一下画面,这里有一个粗略的运行结果说明:

  1. 您的用户授权启动以安装标记为 com.domain.AppName.SampleService 的助手守护程序。
  2. launchd 在您的应用 Info.plist 中找到 SMPrivilegedExecutables 下的 com.domain.AppName.SampleService 条目;这描述了助手的二进制文件应该用来签名的证书。 (如果它们不匹配,那么理论上攻击者已经用他们自己的版本替换了您的帮助工具,以便 运行 它成为 root。)请注意 documentation 具体描述了密钥SMPrivilegedExecutables 仅与更新目的相关:

The calling application's Info.plist must include a "SMPrivilegedExecutables" dictionary of strings. Each string is a textual representation of a code signing requirement used to determine whether the application owns the privileged tool once installed (i.e. in order for subsequent versions to update the installed version). 3. With the valid helper tool installed, your app makes a request to launchd to spawn the helper under your control. At this point, launchd consults the SMAuthorizedClients section of your helper tool's Info.plist to ensure the app actually does have the right to run the tool. And, of course, it verifies your app's signature to ensure it hasn't been tampered with. Note, again, that the documentation specifically describes the key SMAuthorizedClients to be only relevant for updating purposes: The helper tool must have an embedded Info.plist containing an "SMAuthorizedClients" array of strings. Each string is a textual representation of a code signing requirement describing a client which is allowed to add and remove the tool.

回到您的场景,您的产品当前的工作方式是消除签名步骤。您已指示 launchd 检查的唯一一件事是您应用的 Info.plist 是否将其 ID 列为“com.domain.AppName”。由于没有什么可以阻止攻击者改变他们的 Info.plist 来表达这一点,您寄希望于他们一旦控制了您的辅助工具就无法使用您的辅助工具造成任何伤害。

概述备选方案的附加附录:

除了已经说过的内容之外,让这些代码签名要求以实际提供有意义的安全性的方式工作 并非易事。虽然在某些方面相当复杂,但 SMJobBless 的要求在实践中实际上非常薄弱 - 如图所示,您可以只提供应用程序包标识符(其他任何人都可以重用,它们没有唯一性限制)。

签署您的代码并使用叶证书是必不可少的,但如果在您允许与您的帮助工具通信的应用程序中发现安全漏洞,它仍然会留下相当大的漏洞。除非您的帮助工具需要允许与之通信的应用程序的 最低版本 ,否则可以在攻击者加载您的应用程序的旧版本然后继续的情况下使用“降级攻击”利用它。

我认为确保需求得到一致应用的最佳方式是通过构建脚本。我让 PropertyListModifier.swift as part of SwiftAuthorizationSample 做到了这一点。