【问题标题】:Gain administration privileges with swift for a Mac Application使用 swift 获得 Mac 应用程序的管理权限
【发布时间】:2016-01-22 04:51:48
【问题描述】:

我正在编写一个需要经常以 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)")

显然,有什么地方不对劲。以下是相应的 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>

应用信息列表

<?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 stackoverflow post 和许多其他人都喜欢它。据我了解,我的 plist 设置正确。我做错了什么?

【问题讨论】:

    标签: xcode swift macos smjobbless


    【解决方案1】:

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

    [...] 当您使用 Developer ID 签署帮助工具时,Xcode 会自动设置帮助工具的指定要求,就像这样,这就是您应该为 SMPrivilegedExecutables 使用的内容。而且,这就是上面显示的“setreq”命令的作用:从构建的工具中提取指定的需求,并将其放入应用程序的 Info.plist 源代码中。

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

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

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

    &lt;string&gt;identifier "com.domain.AppName.SampleService"&lt;/string&gt;

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

    &lt;string&gt;identifier "com.domain.AppName"&lt;/string&gt;

    • 忽略 ReadMe.txt 中的“构建和运行示例”说明,而是像往常一样简单地构建和运行项目。

    我当然不能说我推荐这个;这些签名要求的存在是有充分理由的。然而,它至少比最终的替代方案要好,后者将使用 NSAppleScript 通过chmodchown 为帮助程序可执行文件提供root setuid 位。


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

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

    Apple 的示例演示了使用代码签名来解决此问题。以防万一您不熟悉:代码签名涉及以加密方式标记您的最终产品,以便 OS X 可以验证您的程序没有被受损版本替换。原始示例的SMAuthorizedClientsSMPrivilegedExecutables 中那些额外的“证书叶”引用专门用于此;它们描述了您的应用和助手必须签署的证书才能相互交互。 被记录用于在交互期间验证代码签名之外的其他目的。正如this blog post and the corresponding CVE 所指出的,客户端程序员负责确保助手工具和应用程序之间的加密安全进程间通信,例如使用SecCode API。

    为了帮助描绘这幅画,以下是它如何发挥作用的粗略纲要:

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

    调用应用程序的 Info.plist 必须包含一个“SMPrivilegedExecutables”字符串字典。每个字符串都是代码签名要求的文本表示,用于确定应用程序安装后是否拥有特权工具(即,以便后续版本更新已安装的版本)。 3. 安装了有效的帮助工具后,您的应用会向launchd 发出请求,以在您的控制下生成帮助工具。 此时,launchd 会参考您的帮助工具 Info.plist 的 SMAuthorizedClients 部分,以确保应用确实有权运行该工具。当然,它还会验证您的应用程序的签名以确保它没有被篡改。 再次注意,documentation 专门描述了密钥 SMAuthorizedClients 仅与更新目的相关: 帮助工具必须有一个嵌入的 Info.plist,其中包含一个“SMAuthorizedClients”字符串数组。每个字符串都是代码签名要求的文本表示,描述了允许添加和删除工具的客户端。

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

    概述替代方案的附加附录:

    【讨论】:

    • 我无意保留赏金。你在 Reddit 上的帖子的问题在于它说的是字符串。我确实随机遇到了排列。但是你的回答已经足够好了。我确实建议您编辑您的问题,以合并原始问题所需的逐步更改。我被困了好几天,我仍然不明白不完整的身份线是如何足够的,而苹果的字符串要长得多
    • 好吧,现在我不觉得自己很傻。同样,奖励不是必需的,但非常感谢......我已经更新了我的答案以尝试解释整体原理以及您询问的细节;如果没有涵盖它,请不要犹豫。
    • "安装了有效的帮助工具后,您的应用程序会向 launchd 发出请求,以在您的控制下生成帮助程序。此时,launchd 会咨询帮助工具的 Info.plist 的 SMAuthorizedClients 部分,以确保“应用程序实际上确实有权运行该工具。当然,它会验证您的应用程序的签名以确保它没有被篡改。”这不是真的。您有责任限制哪些客户可以使用该工具。见blog.obdev.at/what-we-have-learned-from-a-vulnerability
    【解决方案2】:

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

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

    我的观点是确保一致应用需求的最佳方式是通过构建脚本。我将PropertyListModifier.swift 作为SwiftAuthorizationSample 的一部分来做到这一点。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-27
        • 2011-07-31
        • 2015-07-05
        • 2013-07-20
        • 1970-01-01
        • 1970-01-01
        • 2017-06-19
        • 1970-01-01
        相关资源
        最近更新 更多