Exploring macOS Calendar Alerts: Part 1 – Attempting to execute code

tl;dr

This post is more about the journey of taking a thought, investigating it, and analyzing the possible abuses of a specific technology feature than about the end result or a particular vulnerability or exploit. In this post, I explore the potential abuse of some features within the macOS Calendar application.

Calendar, the default and bundled calendaring application on macOS, supports setting various alerts for calendar events and one of these alerts, “Open file”, will “open” a file at a given time. This can be used to launch apps or run binaries (i.e. execute code). As best as I can tell, this is working as intended, and Apple has put in safeguards to minimize the likelihood of abuse:

  • Calendar prevents you from importing an event with potentially harmful alerts (i.e. procedure or email alarms).
  • Subscribing to a calendar defaults to removing the alerts for that calendar, and a user must explicitly opt-out of this protection.
  • Programmatically adding an event/alert requires the “Calendars” privacy permission (or “Full Disk Access”).

So, while yes, Calendar alerts can be abused to execute code on a victim’s system, it would also require the attacker to be in a semi-privileged position. The attacker would either need to control a calendar the victim subscribes to and has explicitly chosen to allow alerts for, or have a foothold on the victim’s system with control over a process that has been granted specific permissions; I’ll go over both of these attack paths.

How It Started

It was a bit after 10:00 PM and I’d just gotten home from playing water polo. My energy was still up so I figured, “what better way to wind down than reading reasonable, well-informed, thought-provoking opinions on Twitter?”. One of the first ones I saw was this tweet by @cedowens:

Wait what?? The calendar on macOS can execute applescripts, signed and notarized mach-o binaries, and possibly other file types at a specified time (as a calendar event). Worked on Mojave and Catalina when I tried it 👀. Shoutout to the Atlassian security team for the idea 💡

It immediately caught my attention as I enjoy digging into and understanding systems’ oddities. I didn’t go to bed until after 1:00 AM that night, or the next night, ultimately giving the topic many hours of my time the next week.

This is a recounting of that adventure and what I discovered.

Investigating Alerts

After poking at “open file” alerts for a bit I came across this article on how to automate macOS using Calendar alerts, which discusses the topic from a non-malicious perspective. I also relied on Kanzai’s iCalendar Specification Excerpts and the actual iCalendar specifications, RFC 2445 and RFC 5545.

My first attempt at opening a file was a .sh Bash script. I created a new event, “Custom…” alert, “Open file”, changed the default of “Calendar” to “Other…”, and selected my payload.

When the alert triggered, Xcode launched and displayed the file. After playing around a bit, I determined “Open file” alerts behave exactly like their name implies, they open the specified file in the default application, as determined via Launch Services, for that filetype (equivalent to calling open <file> or double-clicking the file). After I set Terminal as the default application for .sh, Terminal launched, and displayed the following message:

"test.sh" can’t be opened because CalendarAgent is not allowed to open documents in Terminal.

I changed the opening application to iTerm, and the script executed without issue.

Alright, so we can execute “arbitrary” code (it has to exist on the filesystem and be set to “open” in such a way it executes) from a Calendar alert—how can we get this into someone else’s calendar?

Sending, Sharing, and Injecting

I focused on three primary methods of getting an event into a target victim’s calendar:

  • Sending an event
  • Sharing an event
  • Injecting an event

For each of these, I then considered various methods to perform the action, tried it in various scenarios, and crossed my fingers.

Sending an event

In my experience, there are two ways in which someone sends a calendar event:

  1. by directly emailing an ICS file (that a user clicks on to import into their default calendaring application), or
  2. adding the target as an attendee to an event (such as inviting them to a meeting).

An ICS file is a plain text file that follows the iCalendar specification (RFC 2445 or RFC 5545).

See below for an example ICS file that describes an event on 2020-01-01 from 1:00 PM to 2:00 PM UTC, which triggers an alarm that displays a message. Note, throughout this post I attempt to use “alarm” when referring to the general concept of an alarm/alert/notification as this is the term used in the specification and “alert” when referring specifically to an alarm within Calendar as that is the term used by Calendar; but, they are essentially synonymous in this discussion.

BEGIN:VCALENDAR
BEGIN:VEVENT
DTSTART:20200101T130000Z
DTEND:20200101T140000Z
SUMMARY:Example Event
DESCRIPTION:This is an example event.
BEGIN:VALARM
TRIGGER:PT0S
ACTION:DISPLAY
DESCRIPTION:This is an example display alarm.
END:VALARM
END:VEVENT
END:VCALENDAR

Don’t worry too much about the directives and keywords in the above as I’ll discuss the relevant ones as we go.

To find out what an “open file” alert looked like, I tried creating one in Calendar and then emailing that event (Calendar’s “Mail Event” feature, resulting in an ICS file attached to a new email message that I could then inspect in a text editor), but the alarm was not included with anything that indicated what file to open, and neither did sending an invite (by adding an attendee in Calendar). So I turned to one of my tried and true techniques: I created an event, with an open file alert, called “FINDME” and then used ripgrep to search my filesystem for it (based on prior experiences and typical macOS application data storage approaches, I scoped the search to ~/Library). I discovered an ICS file associated with my event at ~/Library/Calendars/<calendar-UUID>.calendar/Events/<event-UUID>.ics. This revealed the VALARM format I was looking for:

BEGIN:VALARM
X-WR-ALARMUID:<UUID>
UID:<UUID>
TRIGGER:PT0S
X-APPLE-BOOKMARK:<Base64 encoded content>
ACTION:PROCEDURE
END:VALARM

The Base64 encoded content was binary data we will discuss later.

I used this ICS file to create test ICS files with “open file” (now known to be “PROCEDURE”) alarms. Per RFC 5545, which supersedes RFC 2445, the “PROCEDURE” ACTION for VALARM is actually deprecated, but Calendar still supports it. Attempting to open such an ICS file resulted in a warning from Calendar:

Alert window, reading: Some of the events in this calendar have alerts that open files or applications. The files that could be opened may change or damage information on your computer. If you continue, these alerts will be removed.

Notably, neither of these options allow the user to accept the potentially dangerous alert.

If I set the VCALENDAR’s METHOD to “Request” (i.e. a meeting invite), the event is added without warning, and without the alert.

After all this work, I had learned a good bit about the iCalendar specification and some nuances of Calendar, but was unable to send an “open file” alert such that it would be added to a user’s calendar. Interestingly, a VALARM ACTION of “DISPLAY” or “AUDIO” with an X-APPLE-BOOKMARK property is allowed by Calendar without warning or complaint, but doesn’t trigger the bookmark (i.e. open the file). It does, however, appear to pre-populate the “open file” target when going to edit the event to have a “Custom” alert.

Sharing an event

I have four types of calendars in Calendar: On My Mac, Exchange, iCloud, and Other. Other included calendars I have subscribed to, such as a holiday calendar, and “Siri Found in Apps”. If I couldn’t send an event with a procedure alarm such that my target would import it to their calendar, maybe I can add one to an event I share with them!

Exchange

In Exchange, calendar sharing is called “delegation”, and so I played around with adding procedure alarms to a calendar delegated to me by another account. No luck. The alarm never propagated to the other calendar. This is likely due to Exchange not supporting basically any of the VALARM component of RFC 5545, as detailed on Microsoft’s Open Specifications site.

iCloud

iCloud calendar sharing gave the same results as Exchange: alerts were not included in the calendar syncing. When viewing the properties of a shared calendar, there is an option to ignore alerts for the calendar but no option to keep alerts or attachments, as is the case for calendar subscriptions.

Subscribing

Subscribing to a calendar boiled down to giving Calendar an HTTP URL that pointed to an ICS file. The default settings when subscribing remove alerts from all imported events, although the user can uncheck the appropriate box to keep the alerts.

Calendar subscription window showing the default settings remove alerts and attachments

I created a test ICS, hosted it, and subscribed to it. Since I had a pretty good idea what removing alerts would do, I unchecked the option. AND IT WORKED! If I control the ICS file a user subscribes to, I can include procedure alarms that will result in code executing on the user’s system!

I can see this being abused as part of a phishing attack (you can use webcal:// links to initiate the subscription process, but the user still needs to uncheck the “remove alerts” option) or by someone who creates, buys, or hijacks a popular online calendar that users are likely to want alerts for, such as a regularly-updated calendar for last minute deals or ticket sales.

Injecting an event

After getting the above working, I started considering other routes of abusing procedure alarms. What if an attacker already has a foothold on the network or the victim’s system?

Network attacker

One of my coworkers speculated that if I could compromise an Exchange administrator account, I could inject procedure alarms into target users’ calendars directly (or possibly via a group calendar). I ultimately did not test this possibility, but because of the above note regarding Exchange not supporting that part of the specification, I do not expect it to be possible.

I also considered hijacking a subscription. Calendar does do TLS certificate validation so such an attack would require a non-HTTPS subscription (which is allowed) or a valid certificate from a certificate authority trusted by the victim system.

Local attacker

Lastly, one of the original scenarios for abuse proposed by @cedowens was to use procedure alarms to achieve persistence on a victim’s system. I imagined a scenario where you had compromised a process or otherwise achieved remote access without a graphical user interface. How could I programmatically or via command line tools get a code-executing procedure alarm into the user’s calendar?

I first played around with AppleScript. It is easy to create an event and an alarm and add that alarm to the event object (there’s even an OpenFileAlarm object), as long as the process has the Calendars privacy permission (or Full Disk Access); however, while the event appeared in the Calendar, there was no “open file” alarm with it. I successfully created other alarm types, but Calendar was not accepting/responding to/acknowledging the OpenFileAlarm object.

I then tried programmatically subscribing to a calendar, but this only opens the subscription dialog similar to using a webcal:// URI. AppleScript can click buttons, so I tried that, but that required my process to have been granted the Accessibility permission.

Since AppleScript didn’t look like it was going to work out, I moved on to looking closer at how Calendar stored its data. I tried modifying the ICS files I’d found earlier in ~/Library/Calendars (which required Calendars or Full Disk Access permissions) but none of my edits were reflected in Calendar. I searched for my “FINDME” event again, this time allowing ripgrep to search binary files and discovered the SQLite database ~/Library/Calendars/Calendar Cache. Upon editing the database and relaunching Calendar I was rewarded with my changes.

I was interested in two tables in the database, ZCALENDARITEM and ZALARM, which contain the data for individual events and the alarms associated with them, respectively. In ZALARM, I was able to identify my “open file” alerts and see that the “file” I was opening was referenced via a binary blob stored in the ZBOOKMARK column. This was the same binary content that was Base64 encoded in the ICS file.

By directly modifying the database, I was able to create new events with procedure alarms that executed a target file at a set time. I also discovered that the database contained the default alarms for the calendars (each calendar has a default alarm for events and all day events). For fun, I changed one to a procedure alarm and all future events for that calendar now open the specified file unless an explicit alarm is set (setting a default alarm to an “open file” alarm was not something I was able to do in the UI).

The Payload

So, finally, what is determining what file to open (that binary blob in the database, Base64 encoded in the ICS)? It is a (macOS) Finder Bookmark or Alias, essentially Apple’s version of a shortcut or link to an object. This limits the target file to open to one that is in a known location and accessible by the system, such as on its local filesystem. In the case of the local attacker above looking to establish persistence, they could easily drop their file on the system. For a remote attacker using a calendar subscription to carry out an attack, barring a phishing attack to get their file on the system, it needs to exist somewhere accessible. As it turns out, the bookmark can point to remote systems over at least FTP and SMB.

In my testing, when using an FTP location, if the volume wasn’t already mounted, the file did not open and there was no prompt to connect or authenticate. For SMB, I did receive a prompt to connect and authenticate when the volume wasn’t mounted. This is still not ideal and requires user interaction but was as close as I got to abusing a procedure alarm remotely.

If you want to read up on Finder Bookmarks, there’s a number of blog posts that I relied on. Note, when playing with the bookmarks, I did not attempt to figure out how to create a bookmark from scratch or significantly modify one. Instead, I would create a test event with an alert that did what I wanted, find it in the database or the corresponding ICS file and repurpose the contents.

According to the specification, a VALARM can have an ATTACH property, but I was unable to get Calendar to “open” the attachment.

Conclusion

Twitter is a great place for ideas and topics to explore, but a horrible place to find yourself if you want to fall asleep. The iCalendar specifications and extensions allow for malicious abuse of various combinations of components and properties, but successful exploitation will be dependent on client implementations. I focused on alarms within Calendar (and a little bit in Exchange) on macOS but there are other clients and other operating systems that could behave differently.

I chased the possibility of using a procedure alarm as an attack vector within Calendar to my satisfaction that it is possible, but there are a number of protections in place that an attacker would have to overcome. As such, I decided not to fully dig into the bookmark format to determine how, if possible, to make a cross-system payload. Instead, a different aspect of Calendar and events caught my eye and gave more interesting results, which I’ll cover in a follow up post.