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.