macOS XPC Authorization

XPC Authorization

Apple also proposes another way to authenticate if the connecting process has permissions to call the an exposed XPC method.

When an application needs to execute actions as a privileged user, instead of running the app as a privileged user it usually installs as root a HelperTool as an XPC service that could be called from the app to perform those actions. However, the app calling the service should have enough authorization.

ShouldAcceptNewConnection always YES

An example could be found in EvenBetterAuthorizationSample. In App/AppDelegate.m it tries to connect to the HelperTool. And in HelperTool/HelperTool.m the function shouldAcceptNewConnection won't check any of the requirements indicated previously. It'll always return 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;
}

For more information about how to properly configure this check:

macOS XPC Connecting Process Check

Application rights

However, there is some authorization going on when a method from the HelperTool is called.

The function applicationDidFinishLaunching from App/AppDelegate.m will create an empty authorization reference after the app has started. This should always work. Then, it will try to add some rights to that authorization reference calling setupAuthorizationRights:

The function setupAuthorizationRights from Common/Common.m will store in the auth database /var/db/auth.db the rights of the application. Note how it will only add the rights that aren't yet in the database:

The function enumerateRightsUsingBlock is the one used to get applications permissions, which are defined in commandInfo:

This means that at the end of this process, the permissions declared inside commandInfo will be stored in /var/db/auth.db. Note how there you can find for each method that will require authentication, permission name and the kCommandKeyAuthRightDefault. The later one indicates who can get this right.

There are different scopes to indicate who can access a right. Some of them are defined in AuthorizationDB.h (you can find all of them in here), but as summary:

Name
Value
Description

kAuthorizationRuleClassAllow

allow

Anyone

kAuthorizationRuleClassDeny

deny

Nobody

kAuthorizationRuleIsAdmin

is-admin

Current user needs to be an admin (inside admin group)

kAuthorizationRuleAuthenticateAsSessionUser

authenticate-session-owner

Ask user to authenticate.

kAuthorizationRuleAuthenticateAsAdmin

authenticate-admin

Ask user to authenticate. He needs to be an admin (inside admin group)

kAuthorizationRightRule

rule

Specify rules

kAuthorizationComment

comment

Specify some extra comments on the right

Rights Verification

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:

Note that to check the requirements to get the right to call that method the function authorizationRightForCommand will just check the previously comment object commandInfo. Then, it will call AuthorizationCopyRights to check if it has the rights to call the function (note that the flags allow interaction with the user).

In this case, to call the function readLicenseKeyAuthorization the kCommandKeyAuthRightDefault is defined to @kAuthorizationRuleClassAllow. So anyone can call it.

DB Information

It was mentioned that this information is stored in /var/db/auth.db. You can list all the stored rules with:

Then, you can read who can access the right with:

Permissive rights

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

  1. 'authenticate-user': 'false'

    • This is the most direct key. If set to false, it specifies that a user does not need to provide authentication to gain this right.

    • This is used in combination with one of the 2 below or indicating a group the user must belong to.

  2. 'allow-root': 'true'

    • If a user is operating as the root user (which has elevated permissions), and this key is set to true, the root user could potentially gain this right without further authentication. However, typically, getting to a root user status already requires authentication, so this isn't a "no authentication" scenario for most users.

  3. 'session-owner': 'true'

    • If set to true, the owner of the session (the currently logged-in user) would automatically get this right. This might bypass additional authentication if the user is already logged in.

  4. 'shared': 'true'

    • This key doesn't grant rights without authentication. Instead, if set to true, it means that once the right has been authenticated, it can be shared among multiple processes without each one needing to re-authenticate. But the initial granting of the right would still require authentication unless combined with other keys like 'authenticate-user': 'false'.

You can use this script to get the interesting rights:

Reversing Authorization

Checking if EvenBetterAuthorization is used

If you find the function: [HelperTool checkAuthorization:command:] it's probably the the process is using the previously mentioned schema for authorization:

Thisn, if this function is calling functions such as AuthorizationCreateFromExternalForm, authorizationRightForCommand, AuthorizationCopyRights, AuhtorizationFree, it's using EvenBetterAuthorizationSample.

Check the /var/db/auth.db to see if it's possible to get permissions to call some privileged action without user interaction.

Protocol Communication

Then, you need to find the protocol schema in order to be able to establish a communication with the XPC service.

The function shouldAcceptNewConnection indicates the protocol being exported:

In this case, we have the same as in EvenBetterAuthorizationSample, check this line.

Knowing, the name of the used protocol, it's possible to dump its header definition with:

Lastly, we just need to know the name of the exposed Mach Service in order to stablish a communication with it. There are several ways to find this:

  • In the [HelperTool init] where you can see the Mach Service being used:

  • In the launchd plist:

Exploit Example

In this example is created:

  • The definition of the protocol with the functions

  • An empty auth to use to to ask for access

  • A connection to the XPC service

  • A call to the function if the connection was successful

Other XPC privilege helpers abused

References

Last updated