When Gatekeeper Turns a Blind Eye: Alerting on the Latest MacOS Vulnerability

Blog Author
Ben Montour

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.

 

Conclusion

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 as well as the incredibly in-depth post by Patrick Wardle of Objective-See ().

 

Read both of those if you want all of the technical details on this exploit and how it works.

Q1 ATT&CK Eval Webinar -Sales Banner - On Demand