Apple announced macOS Monterey (macOS 12) this week at WWDC, and one of its new features that caught my eye is Shortcuts. It’s already available on iOS, but it made its way to macOS. My security focused brain immediately thought about how cool this feature could be for red teamers or pentesters to persist on macOS :) So I decided to take a quick look on the new functionality, focusing on how it works. All of the below information is based on macOS Monterey Developer Beta 1.

The Shortcuts.app Link to heading

Shortcuts can be created by the Shortcuts.app. I created a new shortcut, named it “testcut” and added a shell script as an action, which will create the file /tmp/testcut. This is shown below.

I will use this example along the way, when I explore the internals of Shortcuts.

Before moving on, here are the code signing properties of the application.

csaby@macos12 ~ % codesign -dv --entitlements :- /System/Applications/Shortcuts.app                     
Executable=/System/Applications/Shortcuts.app/Contents/MacOS/Shortcuts
Identifier=com.apple.shortcuts
Format=app bundle with Mach-O universal (x86_64 arm64e)
CodeDirectory v=20400 size=6636 flags=0x0(none) hashes=197+7 location=embedded
Platform identifier=13
Signature size=4442
Signed Time=2021. Jun 2. 6:56:13
Info.plist entries=54
TeamIdentifier=not set
Sealed Resources version=2 rules=2 files=0
Internal requirements count=1 size=68
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>com.apple.CompanionLink</key>
		<true/>
		<key>aps-environment</key>
		<string>production</string>
		<key>com.apple.developer.siri</key>
		<true/>
		<key>com.apple.sharing.Client</key>
		<true/>
		<key>com.apple.coreduetd.allow</key>
		<true/>
		<key>com.apple.private.homekit</key>
		<true/>
		<key>com.apple.developer.homekit</key>
		<true/>
		<key>com.apple.developer.healthkit</key>
		<true/>
		<key>com.apple.private.corerecents</key>
		<true/>
		<key>com.apple.shortcuts.ActionKit</key>
		<true/>
		<key>com.apple.private.cloudkit.spi</key>
		<true/>
		<key>com.apple.security.app-sandbox</key>
		<true/>
		<key>com.apple.shortcuts.mac-helper</key>
		<true/>
		<key>com.apple.fileprovider.enumerate</key>
		<true/>
		<key>com.apple.private.swc.system-app</key>
		<true/>
		<key>com.apple.proactive.eventtracker</key>
		<true/>
		<key>com.apple.security.network.client</key>
		<true/>
		<key>com.apple.siri.VoiceShortcuts.xpc</key>
		<true/>
		<key>com.apple.homekit.private-spi-access</key>
		<true/>
		<key>com.apple.locationd.effective_bundle</key>
		<true/>
		<key>com.apple.rootless.storage.shortcuts</key>
		<true/>
		<key>com.apple.runningboard.launchprocess</key>
		<true/>
		<key>com.apple.fileprovider.extension-host</key>
		<true/>
		<key>com.apple.intents.extension.discovery</key>
		<true/>
		<key>com.apple.private.cloudkit.masquerade</key>
		<true/>
		<key>com.apple.developer.associated-domains</key>
		<array></array>
		<key>com.apple.private.suggestions.contacts</key>
		<true/>
		<key>com.apple.frontboard.launchapplications</key>
		<true/>
		<key>com.apple.intents.uiextension.discovery</key>
		<true/>
		<key>com.apple.runningboard.terminateprocess</key>
		<true/>
		<key>com.apple.private.cloudkit.setEnvironment</key>
		<true/>
		<key>com.apple.private.network.socket-delegate</key>
		<true/>
		<key>com.apple.security.automation.apple-events</key>
		<true/>
		<key>com.apple.developer.homekit.background-mode</key>
		<true/>
		<key>com.apple.private.security.system-application</key>
		<true/>
		<key>com.apple.managedconfiguration.profiled-access</key>
		<true/>
		<key>com.apple.avfoundation.allows-set-output-device</key>
		<true/>
		<key>com.apple.private.coreservices.canmaplsdatabase</key>
		<true/>
		<key>com.apple.avfoundation.allow-system-wide-context</key>
		<true/>
		<key>com.apple.security.personal-information.location</key>
		<true/>
		<key>com.apple.security.files.user-selected.read-write</key>
		<true/>
		<key>com.apple.security.personal-information.calendars</key>
		<true/>
		<key>com.apple.application-identifier</key>
		<string>com.apple.shortcuts</string>
		<key>com.apple.avfoundation.allows-access-to-device-list</key>
		<true/>
		<key>com.apple.rootless.storage.coreduet_knowledge_store</key>
		<true/>
		<key>com.apple.security.personal-information.addressbook</key>
		<true/>
		<key>com.apple.private.hid.client.event-dispatch.internal</key>
		<true/>
		<key>com.apple.security.personal-information.photos-library</key>
		<true/>
		<key>com.apple.developer.icloud-container-environment</key>
		<string>Production</string>
		<key>com.apple.developer.icloud-services</key>
		<array>
			<string>CloudDocuments</string>
			<string>CloudKit</string>
		</array>
		<key>com.apple.avfoundation.allow-identifying-output-device-details</key>
		<true/>
		<key>com.apple.private.healthkit.source.identities</key>
		<array>
			<string>com.apple.shortcuts</string>
		</array>
		<key>com.apple.security.system-groups</key>
		<array>
			<string>systemgroup.com.apple.configurationprofiles</string>
		</array>
		<key>com.apple.developer.icloud-container-identifiers</key>
		<array>
			<string>iCloud.is.workflow.my.workflows</string>
		</array>
		<key>com.apple.developer.ubiquity-container-identifiers</key>
		<array>
			<string>iCloud.is.workflow.my.workflows</string>
		</array>
		<key>com.apple.security.application-groups</key>
		<array>
			<string>group.is.workflow.my.app</string>
			<string>group.is.workflow.shortcuts</string>
		</array>
		<key>com.apple.private.donotdisturb.settings.request.client-identifiers</key>
		<string>com.apple.focus.activity-manager</string>
		<key>com.apple.security.temporary-exception.sbpl</key>
		<array>
			<string>(allow appleevent-send)</string>
			<string>(allow distributed-notification-post)</string>
		</array>
		<key>com.apple.security.temporary-exception.shared-preference.read-write</key>
		<array>
			<string>com.apple.shortcuts</string>
			<string>com.apple.siri.shortcuts</string>
			<string>pbs</string>
		</array>
		<key>keychain-access-groups</key>
		<array>
			<string>V568VXD5P8.is.workflow.my.app</string>
			<string>com.apple.MediaRemote.pairing</string>
			<string>com.apple.sharing.appleidauthentication</string>
		</array>
		<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
		<array>
			<string>/Library/Shortcuts/</string>
			<string>/Library/SyncedIntentDefinitions/</string>
		</array>
		<key>com.apple.security.temporary-exception.files.home-relative-path.read-only</key>
		<array>
			<string>/Library/CoreBehavior/</string>
			<string>/Library/UserConfigurationProfiles/</string>
		</array>
		<key>com.apple.private.appleevents.allowedtosend</key>
		<dict>
			<key>com.apple.private.appleevents.allowed.INec.INei</key>
			<true/>
			<key>com.apple.private.appleevents.allowed.aevt.quit</key>
			<true/>
		</dict>
		<key>com.apple.private.tcc.allow</key>
		<array>
			<string>kTCCServiceAddressBook</string>
			<string>kTCCServiceAppleEvents</string>
			<string>kTCCServiceCalendar</string>
			<string>kTCCServiceCamera</string>
			<string>kTCCServiceMediaLibrary</string>
			<string>kTCCServiceMicrophone</string>
			<string>kTCCServicePhotos</string>
			<string>kTCCServicePhotosAdd</string>
			<string>kTCCServiceReminders</string>
			<string>kTCCServiceSpeechRecognition</string>
			<string>kTCCServiceWillow</string>
		</array>
		<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
		<array>
			<string>com.apple.Music.MPMusicPlayerControllerInternal</string>
			<string>com.apple.SharingServices</string>
			<string>com.apple.amp.library.framework</string>
			<string>com.apple.coreduetd.knowledge</string>
			<string>com.apple.donotdisturb.service</string>
			<string>com.apple.donotdisturb.service.non-launching</string>
			<string>com.apple.locationd.desktop.registration</string>
			<string>com.apple.locationd.desktop.synchronous</string>
			<string>com.apple.photos.service</string>
			<string>com.apple.siri.VoiceShortcuts.xpc</string>
		</array>
	</dict>
</plist>

The above XML has been beautified, as the embedded one, has no new line characters or spaces, and it’s unreadable. As we can see, the application is very rich in entitlements, most notably it’s sandboxed (com.apple.security.app-sandbox), has quite a few TCC exceptions (kTCCServiceAddressBook,….) and also one that suggest it has access to a SIP protected location com.apple.rootless.storage.shortcuts. This is what I will cover next.

Shortcuts Database & Configuration Link to heading

All of the Shortcuts files are stored under the user’s HOME folder at ~/Library/Shortcuts. This location is protected by the Sandbox (SIP), as we can’t access it unless we have Full Disk Access permissions granted or the entitlement com.apple.rootless.storage.shortcuts.

We find the following files in this location:

csaby@macos12 ~ % ls -l Library/Shortcuts
total 4696
-rw-r--r--@ 1 csaby  staff      237 Jun  8 22:59 SecuredPreferences.plist
-rw-r--r--@ 1 csaby  staff   262144 Jun  8 10:04 Shortcuts.sqlite
-rw-r--r--@ 1 csaby  staff    32768 Jun  8 22:55 Shortcuts.sqlite-shm
-rw-r--r--@ 1 csaby  staff  1400832 Jun 10 22:20 Shortcuts.sqlite-wal
-rw-r--r--  1 csaby  staff       60 Jun 10 22:20 Spotlight.dat

Let’s start with SecuredPreferences.plist. This file contains the “advanced” configuration option for Shortcuts. The file is very short.

<?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>WFAllowDeletingLargeAmountsOfDataKey</key>
	<true/>
	<key>WFAllowDeletingWithoutConfirmationKey</key>
	<true/>
	<key>WFAllowSharingLargeAmountsOfDataKey</key>
	<true/>
	<key>WFScriptingActionEnabledKey</key>
	<true/>
</dict>
</plist>

It can be mapped one-to-one to the options on the advanced configuration preferences pane.

Let’s take a look on the database. I’m sure Sarah Edwards will come up with some awesome queries for her Apollo 4n6 project. :) I won’t go too crazy here.

csaby@macos12 ~ % sqlite3 Library/Shortcuts/Shortcuts.sqlite
SQLite version 3.35.3 2021-03-26 16:31:39
Enter ".help" for usage hints.
sqlite> .tables
ZACCESSRESOURCEPERMISSION                  
ZCLOUDKITSYNCTOKEN                         
ZCOLLECTION                                
ZSHORTCUT                                  
ZSHORTCUTACTIONS                           
ZSHORTCUTBOOKMARK                          
ZSHORTCUTICON                              
ZSHORTCUTQUARANTINE                        
ZSHORTCUTRUNEVENT                          
ZSMARTPROMPTPERMISSION                     
ZTRIGGER                                   
ZTRIGGEREVENT                              
ZTRUSTEDDOMAIN                             
ZVCVOICESHORTCUTMANAGEDOBJECT              
ZVCVOICESHORTCUTSUGGESTIONLISTMANAGEDOBJECT
ZVCVOICESHORTCUTSYNCSTATEMANAGEDOBJECT     
Z_3PARENTS                                 
Z_3SHORTCUTS                               
Z_METADATA                                 
Z_MODELCACHE                               
Z_PRIMARYKEY    

The database have quite a few tables, but what I found most interesting is ZSHORTCUT and ZSHORTCUTACTIONS. From the former we can get the shortcut names, and their description.

sqlite> select ZNAME,ZACTIONSDESCRIPTION from ZSHORTCUT;
ZNAME|ZACTIONSDESCRIPTION
testcut|Run Shell Script

There are many other metadata columns, like time creation, modification, and so on, but I don’t show it here.

ZSHORTCUTACTIONS will contain information about the configured actions.

sqlite> select * from ZSHORTCUTACTIONS;
Z_PK|Z_ENT|Z_OPT|ZSHORTCUT|ZDATA
3|5|9|3|bplist00??_WFWorkflowActionIdentifier_WFWorkflowActionParameters_"is.workflow.actions.runshellscript?   VScriptTUUID_touch /tmp/testcut_$2252E11A-5DEF-446E-BC5D-F053391273CC
,Insz?

As we can find in the above query, the actions is stored as a binary plist. Unfortunatley plutil will fail converting it. :-/

csaby@macos12 ~ % sqlite3 Library/Shortcuts/Shortcuts.sqlite "select ZDATA from ZSHORTCUTACTIONS where Z_PK=3" | plutil -convert xml1 - -o -
<stdin>: Property List error: Unexpected character b at line 1 / JSON error: JSON text did not start with array or object and option to allow fragments not set. around line 1, column 0.

Nevertheless, we can still spot our touch /tmp/testcut command inside. So if ever someone wants to persist using shortcuts, this is the table to use, and cross reference entries with other tables.

Forensics note: deleted shortcut actions will remain in the database!

shortcuts CLI Link to heading

Shortcuts also has a command line utility, located at /usr/bin/shortcuts. We can use it to list, view and execute shortcuts. Listing will display the available shortcuts.

csaby@macos12 ~ % shortcuts list
testcut

It will only list the active ones, and omit any deleted.

Viewing will simply open the main Shortcuts.app and load the given shortcut.

Before moving moving on to execution, let’s take a quick look at its entitlements.

csaby@macos12 ~ % codesign -dv --entitlements :- /usr/bin/shortcuts
Executable=/usr/bin/shortcuts
Identifier=com.apple.shortcuts.ShortcutsCommandLine
Format=Mach-O universal (x86_64 arm64e)
CodeDirectory v=20400 size=5409 flags=0x0(none) hashes=158+7 location=embedded
Platform identifier=13
Signature size=4442
Signed Time=2021. Jun 2. 6:56:12
Info.plist entries=19
TeamIdentifier=not set
Sealed Resources=none
Internal requirements count=1 size=88
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>com.apple.siri.VoiceShortcuts.xpc</key>
		<true/>
		<key>com.apple.rootless.storage.shortcuts</key>
		<true/>
		<key>com.apple.application-identifier</key>
		<string>com.apple.shortcuts.ShortcutsCommandLine</string>
		<key>com.apple.security.temporary-exception.sbpl</key>
		<array>
			<string>(allow distributed-notification-post)</string>
		</array>
		<key>com.apple.security.temporary-exception.files.home-relative-path.read-write</key>
		<array>
			<string>/Library/Shortcuts/</string>
		</array>
		<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
		<array>
			<string>com.apple.siri.VoiceShortcuts.xpc</string>
		</array>
	</dict>
</plist>

It also has the com.apple.rootless.storage.shortcuts entitlement, along with com.apple.security.temporary-exception.files.home-relative-path.read-write rule which allows it to access the shortcut database.

Next let’s take a look what happens when we execute our shortcut.

Execution Link to heading

I used Patrick Wardle’s ProcessMonitor to watch what happens when I run shortcuts run testcut.

The relevant events are the following:

{"event":"ES_EVENT_TYPE_NOTIFY_EXEC","process":{"pid":27552,"path":"/usr/bin/shortcuts","uid":501,"arguments":["shortcuts","run","testcut"],"ppid":19388,"ancestors":[19388,19387,19385,1],"signing info":{"csFlags":570522385,"signatureIdentifier":"com.apple.shortcuts.ShortcutsCommandLine","cdHash":"89E43245F73BDFB56F80B23B43353C519D1AE2","isPlatformBinary":1}}}

{"event":"ES_EVENT_TYPE_NOTIFY_EXEC","process":{"pid":27553,"path":"/System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/BackgroundShortcutRunner.xpc/Contents/MacOS/BackgroundShortcutRunner","uid":501,"arguments":["/System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/BackgroundShortcutRunner.xpc/Contents/MacOS/BackgroundShortcutRunner"],"ppid":1,"ancestors":[1],"signing info":{"csFlags":570522385,"signatureIdentifier":"com.apple.WorkflowKit.BackgroundShortcutRunner","cdHash":"DF1F3A29EA01E827CDFDA696A05AE0C1C2B8B9","isPlatformBinary":1}}}

{"event":"ES_EVENT_TYPE_NOTIFY_EXEC","process":{"pid":27556,"path":"/System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/MacHelper.xpc/Contents/MacOS/MacHelper","uid":501,"arguments":["/System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/MacHelper.xpc/Contents/MacOS/MacHelper"],"ppid":1,"ancestors":[1],"signing info":{"csFlags":570522385,"signatureIdentifier":"com.apple.WorkflowKit.MacHelper","cdHash":"F3A1B2D858F28373BE6F52CF4C5E2DF19B65D","isPlatformBinary":1}}}

{"event":"ES_EVENT_TYPE_NOTIFY_FORK","process":{"pid":27558,"path":"/System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/MacHelper.xpc/Contents/MacOS/MacHelper","uid":501,"arguments":[],"ppid":27556,"ancestors":[27556],"signing info":{"csFlags":570522385,"signatureIdentifier":"com.apple.WorkflowKit.MacHelper","cdHash":"F3A1B2D858F28373BE6F52CF4C5E2DF19B65D","isPlatformBinary":1}}}

{"event":"ES_EVENT_TYPE_NOTIFY_EXEC","process":{"pid":27558,"path":"/bin/zsh","uid":501,"arguments":["/bin/zsh","-c","touch /tmp/testcut","-"],"ppid":27556,"ancestors":[27556],"signing info":{"csFlags":570510081,"signatureIdentifier":"com.apple.zsh","cdHash":"467AA8464C3C76FDB13FFE1FD356CE4BD75A44","isPlatformBinary":1}}}

{"event":"ES_EVENT_TYPE_NOTIFY_EXEC","process":{"pid":27558,"path":"/usr/bin/touch","uid":501,"arguments":["touch","/tmp/testcut"],"ppid":27556,"ancestors":[27556],"signing info":{"csFlags":570522385,"signatureIdentifier":"com.apple.touch","cdHash":"4547651A716192E0A34744DFECC015C8373B663","isPlatformBinary":1}}}

Based on the sequence of events, shortcuts will call the BackgroundShortcutRunner XPC service first. This service will further call the MacHelper XPC service, which will finally execute our command.

One interesting note is that MacHelper has Full Disk Access.

csaby@macos12 ~ % codesign -dv --entitlements :- /System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/MacHelper.xpc/Contents/MacOS/MacHelper
Executable=/System/Library/PrivateFrameworks/WorkflowKit.framework/XPCServices/MacHelper.xpc/Contents/MacOS/MacHelper
Identifier=com.apple.WorkflowKit.MacHelper
Format=bundle with Mach-O universal (x86_64 arm64e)
CodeDirectory v=20400 size=920 flags=0x0(none) hashes=18+7 location=embedded
Platform identifier=13
Signature size=4442
Signed Time=2021. Jun 2. 4:20:59
Info.plist entries=23
TeamIdentifier=not set
Sealed Resources version=2 rules=2 files=0
Internal requirements count=1 size=80
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>com.apple.dock.add-item</key>
		<true/>
		<key>com.apple.private.tcc.allow</key>
		<array>
			<string>kTCCServiceSystemPolicyAllFiles</string>
		</array>
		<key>com.apple.application-identifier</key>
		<string>com.apple.WorkflowKit.MacHelper</string>
	</dict>
</plist>

Yet, its shell child process doesn’t inherit its rights, so our script won’t run with FDA rights.

Conclusion Link to heading

I think there will be plenty of interesting items uncovered later regarding Shortcuts. This is just a first glimpse, and as we are still at the 1st Beta things might change.

I also recommend watching Meet Shortcuts for macOS - WWDC 2021 - Videos - Apple Developer.