hacktricks/macos-hardening/macos-security-and-privilege-escalation/mac-os-architecture/macos-ipc-inter-process-communication/macos-xpc-authorization.md

17 KiB
Raw Blame History

macOS XPC 授权

☁️ HackTricks 云 ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

XPC 授权

苹果还提出了另一种方法来验证连接的进程是否具有调用公开的 XPC 方法的权限

当应用程序需要以特权用户身份执行操作时,通常不会将应用程序作为特权用户运行,而是将 HelperTool 作为 XPC 服务以 root 用户身份安装,应用程序可以从中调用以执行这些操作。但是,调用服务的应用程序应具有足够的授权。

ShouldAcceptNewConnection 总是 YES

EvenBetterAuthorizationSample 中可以找到一个示例。在 App/AppDelegate.m 中,它尝试连接HelperTool。而在 HelperTool/HelperTool.m 中,函数**shouldAcceptNewConnection** 不会检查之前提到的任何要求。它将始终返回 YES

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
// Called by our XPC listener when a new connection comes in.  We configure the connection
// with our protocol and ourselves as the main object.
{
assert(listener == self.listener);
#pragma unused(listener)
assert(newConnection != nil);

newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(HelperToolProtocol)];
newConnection.exportedObject = self;
[newConnection resume];

return YES;
}

有关如何正确配置此检查的更多信息,请参阅:

{% content-ref url="macos-xpc-connecting-process-check.md" %} macos-xpc-connecting-process-check.md {% endcontent-ref %}

应用程序权限

然而,当调用 HelperTool 的方法时,会进行一些授权操作。

App/AppDelegate.m 中的 applicationDidFinishLaunching 函数将在应用程序启动后创建一个空的授权引用。这应该总是有效的。
然后,它将尝试通过调用 setupAuthorizationRights 向该授权引用添加一些权限:

- (void)applicationDidFinishLaunching:(NSNotification *)note
{
[...]
err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef);
if (err == errAuthorizationSuccess) {
err = AuthorizationMakeExternalForm(self->_authRef, &extForm);
}
if (err == errAuthorizationSuccess) {
self.authorization = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)];
}
assert(err == errAuthorizationSuccess);

// If we successfully connected to Authorization Services, add definitions for our default
// rights (unless they're already in the database).

if (self->_authRef) {
[Common setupAuthorizationRights:self->_authRef];
}

[self.window makeKeyAndOrderFront:self];
}

Common/Common.m中的setupAuthorizationRights函数将应用程序的权限存储在/var/db/auth.db的授权数据库中。请注意,它只会添加尚未存在于数据库中的权限:

+ (void)setupAuthorizationRights:(AuthorizationRef)authRef
// See comment in header.
{
assert(authRef != NULL);
[Common enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) {
OSStatus    blockErr;

// First get the right.  If we get back errAuthorizationDenied that means there's
// no current definition, so we add our default one.

blockErr = AuthorizationRightGet([authRightName UTF8String], NULL);
if (blockErr == errAuthorizationDenied) {
blockErr = AuthorizationRightSet(
authRef,                                    // authRef
[authRightName UTF8String],                 // rightName
(__bridge CFTypeRef) authRightDefault,      // rightDefinition
(__bridge CFStringRef) authRightDesc,       // descriptionKey
NULL,                                       // bundle (NULL implies main bundle)
CFSTR("Common")                             // localeTableName
);
assert(blockErr == errAuthorizationSuccess);
} else {
// A right already exists (err == noErr) or any other error occurs, we
// assume that it has been set up in advance by the system administrator or
// this is the second time we've run.  Either way, there's nothing more for
// us to do.
}
}];
}

函数enumerateRightsUsingBlock用于获取应用程序的权限,这些权限在commandInfo中定义:

static NSString * kCommandKeyAuthRightName    = @"authRightName";
static NSString * kCommandKeyAuthRightDefault = @"authRightDefault";
static NSString * kCommandKeyAuthRightDesc    = @"authRightDescription";

+ (NSDictionary *)commandInfo
{
static dispatch_once_t sOnceToken;
static NSDictionary *  sCommandInfo;

dispatch_once(&sOnceToken, ^{
sCommandInfo = @{
NSStringFromSelector(@selector(readLicenseKeyAuthorization:withReply:)) : @{
kCommandKeyAuthRightName    : @"com.example.apple-samplecode.EBAS.readLicenseKey",
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
kCommandKeyAuthRightDesc    : NSLocalizedString(
@"EBAS is trying to read its license key.",
@"prompt shown when user is required to authorize to read the license key"
)
},
NSStringFromSelector(@selector(writeLicenseKey:authorization:withReply:)) : @{
kCommandKeyAuthRightName    : @"com.example.apple-samplecode.EBAS.writeLicenseKey",
kCommandKeyAuthRightDefault : @kAuthorizationRuleAuthenticateAsAdmin,
kCommandKeyAuthRightDesc    : NSLocalizedString(
@"EBAS is trying to write its license key.",
@"prompt shown when user is required to authorize to write the license key"
)
},
NSStringFromSelector(@selector(bindToLowNumberPortAuthorization:withReply:)) : @{
kCommandKeyAuthRightName    : @"com.example.apple-samplecode.EBAS.startWebService",
kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow,
kCommandKeyAuthRightDesc    : NSLocalizedString(
@"EBAS is trying to start its web service.",
@"prompt shown when user is required to authorize to start the web service"
)
}
};
});
return sCommandInfo;
}

+ (NSString *)authorizationRightForCommand:(SEL)command
// See comment in header.
{
return [self commandInfo][NSStringFromSelector(command)][kCommandKeyAuthRightName];
}

+ (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block
// Calls the supplied block with information about each known authorization right..
{
[self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
#pragma unused(key)
#pragma unused(stop)
NSDictionary *  commandDict;
NSString *      authRightName;
id              authRightDefault;
NSString *      authRightDesc;

// If any of the following asserts fire it's likely that you've got a bug
// in sCommandInfo.

commandDict = (NSDictionary *) obj;
assert([commandDict isKindOfClass:[NSDictionary class]]);

authRightName = [commandDict objectForKey:kCommandKeyAuthRightName];
assert([authRightName isKindOfClass:[NSString class]]);

authRightDefault = [commandDict objectForKey:kCommandKeyAuthRightDefault];
assert(authRightDefault != nil);

authRightDesc = [commandDict objectForKey:kCommandKeyAuthRightDesc];
assert([authRightDesc isKindOfClass:[NSString class]]);

block(authRightName, authRightDefault, authRightDesc);
}];
}

这意味着在此过程结束时,commandInfo中声明的权限将存储在/var/db/auth.db中。请注意,您可以在其中找到每个需要身份验证的方法权限名称和**kCommandKeyAuthRightDefault。后者指示谁可以获得此权限**。

有不同的范围来指示谁可以访问权限。其中一些在AuthorizationDB.h中定义(您可以在此处找到所有内容),但总结如下:

名称描述
kAuthorizationRuleClassAllowallow任何人
kAuthorizationRuleClassDenydeny无人
kAuthorizationRuleIsAdminis-admin当前用户需要是管理员(在管理员组内)
kAuthorizationRuleAuthenticateAsSessionUserauthenticate-session-owner要求用户进行身份验证。
kAuthorizationRuleAuthenticateAsAdminauthenticate-admin要求用户进行身份验证。他需要是管理员(在管理员组内)
kAuthorizationRightRulerule指定规则
kAuthorizationCommentcomment在权限上指定一些额外的注释

权限验证

HelperTool/HelperTool.m中,函数**readLicenseKeyAuthorization检查调用者是否被授权执行此方法**,调用函数**checkAuthorization。此函数将检查调用进程发送的authData是否具有正确的格式**,然后将检查获取权限所需的内容以调用特定方法。如果一切顺利,返回的error将为nil

- (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command
{
[...]

// First check that authData looks reasonable.

error = nil;
if ( (authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm)) ) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil];
}

// Create an authorization ref from that the external form data contained within.

if (error == nil) {
err = AuthorizationCreateFromExternalForm([authData bytes], &authRef);

// Authorize the right associated with the command.

if (err == errAuthorizationSuccess) {
AuthorizationItem   oneRight = { NULL, 0, NULL, 0 };
AuthorizationRights rights   = { 1, &oneRight };

oneRight.name = [[Common authorizationRightForCommand:command] UTF8String];
assert(oneRight.name != NULL);

err = AuthorizationCopyRights(
authRef,
&rights,
NULL,
kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed,
NULL
);
}
if (err != errAuthorizationSuccess) {
error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil];
}
}

if (authRef != NULL) {
junk = AuthorizationFree(authRef, 0);
assert(junk == errAuthorizationSuccess);
}

return error;
}

请注意,要检查调用该方法的权限,函数authorizationRightForCommand将仅检查先前的注释对象commandInfo。然后,它将调用AuthorizationCopyRights来检查是否有权调用该函数(请注意,标志允许与用户交互)。

在这种情况下,要调用函数readLicenseKeyAuthorizationkCommandKeyAuthRightDefault被定义为@kAuthorizationRuleClassAllow。因此,任何人都可以调用它

数据库信息

提到这些信息存储在/var/db/auth.db中。您可以使用以下命令列出所有存储的规则:

sudo sqlite3 /var/db/auth.db
SELECT name FROM rules;
SELECT name FROM rules WHERE name LIKE '%safari%';

然后,您可以通过以下方式查看谁可以访问权限:

security authorizationdb read com.apple.safaridriver.allow

宽松的权限

你可以在这里找到所有权限配置,但是不需要用户交互的组合是:

  1. 'authenticate-user': 'false'
  • 这是最直接的键。如果设置为false,表示用户无需提供身份验证即可获得此权限。
  • 这与下面的两个键之一结合使用,或者指示用户必须属于的组。
  1. 'allow-root': 'true'
  • 如果用户作为具有提升权限的root用户操作并且此键设置为true则root用户可能无需进一步身份验证即可获得此权限。然而通常情况下要达到root用户状态已经需要进行身份验证所以对于大多数用户来说这不是一个“无需身份验证”的情况。
  1. 'session-owner': 'true'
  • 如果设置为true,会话的所有者(当前登录的用户)将自动获得此权限。如果用户已经登录,则可能绕过其他身份验证。
  1. 'shared': 'true'
  • 此键不会在没有身份验证的情况下授予权限。相反,如果设置为true,意味着一旦权限得到验证,它可以在多个进程之间共享,而无需每个进程重新进行身份验证。但是,初始授予权限仍然需要进行身份验证,除非与其他键(如'authenticate-user': 'false')结合使用。

你可以使用这个脚本获取有趣的权限:

Rights with 'authenticate-user': 'false':
is-admin (admin), is-admin-nonshared (admin), is-appstore (_appstore), is-developer (_developer), is-lpadmin (_lpadmin), is-root (run as root), is-session-owner (session owner), is-webdeveloper (_webdeveloper), system-identity-write-self (session owner), system-install-iap-software (run as root), system-install-software-iap (run as root)

Rights with 'allow-root': 'true':
com-apple-aosnotification-findmymac-remove, com-apple-diskmanagement-reservekek, com-apple-openscripting-additions-send, com-apple-reportpanic-fixright, com-apple-servicemanagement-blesshelper, com-apple-xtype-fontmover-install, com-apple-xtype-fontmover-remove, com-apple-dt-instruments-process-analysis, com-apple-dt-instruments-process-kill, com-apple-pcastagentconfigd-wildcard, com-apple-trust-settings-admin, com-apple-wifivelocity, com-apple-wireless-diagnostics, is-root, system-install-iap-software, system-install-software, system-install-software-iap, system-preferences, system-preferences-accounts, system-preferences-datetime, system-preferences-energysaver, system-preferences-network, system-preferences-printing, system-preferences-security, system-preferences-sharing, system-preferences-softwareupdate, system-preferences-startupdisk, system-preferences-timemachine, system-print-operator, system-privilege-admin, system-services-networkextension-filtering, system-services-networkextension-vpn, system-services-systemconfiguration-network, system-sharepoints-wildcard


Rights with 'session-owner': 'true':
authenticate-session-owner, authenticate-session-owner-or-admin, authenticate-session-user, com-apple-safari-allow-apple-events-to-run-javascript, com-apple-safari-allow-javascript-in-smart-search-field, com-apple-safari-allow-unsigned-app-extensions, com-apple-safari-install-ephemeral-extensions, com-apple-safari-show-credit-card-numbers, com-apple-safari-show-passwords, com-apple-icloud-passwordreset, com-apple-icloud-passwordreset, is-session-owner, system-identity-write-self, use-login-window-ui
☁️ HackTricks云 ☁️ -🐦 推特 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥