When Gatekeeper looks the other way: Alerting on the new macOS vulnerability [April 2021]
Earlier this week Apple issued an update to macOS Big Sur bringing it up to version 11.3. This update included a security fix for a vulnerability within the macOS Gatekeeper security system, and given the ID of “CVE 2021-30657”. This vulnerability was disclosed to Apple by an expert macOS Security Researcher Cedric Owens (Twitter: @cedowens, GitHub: cedowens).
This vulnerability exploits a logic bug within the macOS policy subsystem (“syspolicyd”) that allows for "apps'' crafted in a specific manner to get executed and completely bypass the usual signing requirements imposed by the Gatekeeper system.
The Vulnerability, Explained
MacOS apps usually have a directory structure that looks like <Name>.app/Contents/MacOS/<Name>”. This last file is usually a Mach-O file that shares the same name as the app that is executed. Normally Gatekeeper checks to ensure that this Mach-O file has been signed and notarized if it contains the quarantine attribute "com.apple.quarantine", which is appended automatically to any files downloaded from sources such as web browsers or AirDrop.
Cedric took that knowledge and asked the question, "What if I replace the Mach-O file with a script? Since scripts are not checked by Gatekeeper." I think we all know how this test turned out at this point or you wouldn't be here reading this blog post.
Mitigation and Monitoring Options
So, how can we mitigate this vulnerability? The very best way would be to update to Big Sur 11.3 or install the Catalina Security Update 2021-002. If those are not feasible we can use Uptycs to monitor for similar behavior. We will be adding an alert for this behavior into an upcoming release, but in the meantime, you can add the following as a SQL-based Alert Rule:
SELECT upt_time AS time, upt_hostname AS hostname, pid AS processId, path, cmdline AS cmdLine, uid AS userId, ancestor_list AS ancestorList, sha256 FROM process_events WHERE parent = 1 AND cmdline LIKE '%/Contents/MacOS/%' AND ( path LIKE '/bin/%sh' OR path LIKE '/System/Library/Frameworks/%.framework/Versions/%/bin/%' ) AND upt_time >= :from AND upt_time < :to AND upt_added = TRUE ORDER BY upt_time DESC
Understanding This SQL-based Alert Rule
If reading SQL queries isn't your thing, let's break it down and understand what we're looking for here.
SELECT upt_time AS time, upt_hostname AS hostname, pid AS processId, path, cmdline AS cmdLine, uid AS userId, ancestor_list AS ancestorList, sha256 FROM process_events
This SELECT statement is just asking for specific columns from the process_events table and renames them with an AS statement to make reading easier.
- `upt_time` - the time that your Uptycs agent detected this event
- `pid` - the Process ID assigned by macOS to the process we're looking for
- `path` - the current path of the malicious process that is run
- `cmdline` - the actual command used to execute the Gatekeeper bypassing script
- `uid` - the User ID of the user who opened our malicious app
- `ancestor_list` - the process tree listing the parent and grandparent processes of our malicious process
- `sha256` - the SHA 256 hash of the process being run
Next we have the WHERE clause. This is the meat and potatoes of our SQL query and specifies the parameters we want to look for.
WHERE parent = 1 AND cmdline LIKE '%/Contents/MacOS/%' AND ( path LIKE '/bin/%sh' OR path LIKE '/System/Library/Frameworks/%.framework/Versions/%/bin/%' ) AND upt_time >= :from AND upt_time < :to AND upt_added = TRUE
- `parent = 1` - This is looking for processes with a parent with a process ID of 1, which on macOS is always the `launchd` process. We're looking for `launchd` because it is the process in charge of executing other processes, in this case our shell or python interpreter. Which brings us to our next line.
- `path LIKE '/%sh' OR path LIKE '/System/Library/Frameworks/%.framework/Versions/%/bin/%'` - This is looking for a script interpreter that would be used to run our malicious script. That's going to either be a shell interpreter like bash or zsh (thus the `/%sh` wildcard). Or it's going to be a programming language interpreter like python or ruby. I have specified the location of the built in language interpreters with some wildcards in there. These would be the most likely to be used, as they are built into macOS and thus guaranteed to be available on all systems.
- `upt_time >= :from` - this tells the Uptycs alerting system to only show results more recent than the last time this alert rule was run
- `upt_time < :to` - this tells the Uptycs alerting system to only show results that happened before this particular alert rule was scheduled to run
- `upt_added = TRUE` - this is to specify that any results found must be a new entry within the Uptycs database, and thus a new run of our malicious app
Our last line `ORDER BY upt_time DESC` is just to make sure that any results returned will show with the latest entries at the top of the table.
That's all there is to it. I believe the false-positive rate on this query should be low in most environments. I've tested it against things like Automator scripts and they don't trigger this alert because their parent process isn't launchd.
All credit for this vulnerability and relevant information goes to Cedric Owens.
Thanks to his fantastic blog post (https://cedowens.medium.com/macos-gatekeeper-bypass-2021-edition-5256a2955508) as well as the incredibly in-depth post by Patrick Wardle of Objective-See (https://objective-see.com/blog/blog_0x64.html).
Read both of those if you want all of the technical details on this exploit and how it works.