Jamf recently released a blog post discussing the dangers of developers using the deprecated AuthorizationExecuteWithPrivileges API within their applications. Jamf also launched an effort to investigate the resulting behaviors of calling this API to determine if there were any ways to automate the detection of insecure usage. It should be noted that just because this deprecated API gets used does not mean that a vulnerability is created. Many applications still continue to use the AuthorizationExecuteWithPrivileges API (against Apple's recommendation) and do so in a manner that does not allow attackers to intercept and elevate permissions. To understand how to detect insecure usage, you must first understand what’s happening under the hood when this API gets called.
Glancing Under the Hood
Often times a developer will be tasked with deciding how to run an executable as root while the application is running as a basic user. Vulnerable use cases are commonly seen in installation and update workflows since these actions require writing or overwriting files owned by root. A common update for an application that does not require root privileges might look something like this:
In the above diagram the actions take place as follows:
- MyApplication runs
- MyApplication checks for an available update
- MyApplication downloads a file called “Updater”
- The Updater is then executed by MyApplication
It’s also important to note that on Mac, the User ID (UID) commonly associated with the first user on the system is UID 501, as where root uses a UID of 0. In the above use case, all actions take place as the basic user, so no vulnerabilities are at play.
A typical update process that uses the AuthorizationExecuteWithPrivileges API results in a flow that looks like the following:
- MyApplication runs as user 501 (basic user)
- MyApplication checks for an update
- MyApplication downloads an executable called “Updater”
- MyApplication calls the AuthorizationExecuteWithPrivileges API to execute “Updater” with root privileges. This results in the creation of a process called security_authtrampoline which asks the user to enter their login password.
- If the password is entered correctly the Updater is then executed as UID 0 (root)
The local privilege escalation is possible here between steps 4 and 5. If a malicious actor swaps out the “Updater” executable while the user is busy entering their password, the attacker’s “Updater” executable will be run instead of the one that the developer intended to run.
Let’s take one step back though. What is it that allows this “Updater” owned by the basic user to run as the root user? To answer that we can take a look at the permissions on the security_authtrampoline executable.
Here we see that security_authtrampoline is an executable owned by root. No surprises there. What is fairly unique, however, is that the owner permissions of this file are “rws”. The “s” stands for setuid which is a special permission on unix. This special permission allows an executable to run with the permissions of the owner no matter which user actually executed it. This is how it’s able to execute a child process with elevated permissions, but it will only do this if the user has entered the correct password.
You can test this out by using the AuthorizationExecuteWithPrivileges API while monitoring process execution. For this purpose, we’ve written a tiny program called “vulnerable_application” that attempts to escalate another program called “Updater” using the AuthorizationExecuteWithPrivileges API. The result is a call to security_authtrampoline that looks like this:
From a detection perspective, the most interesting items in the process event are the arguments that get passed to it.
Generic Detection of Vulnerable App Updates
As a local user you can find recent usage of the API by looking in the logs with the following command:
strings /private/var/log/DiagnosticMessages/* | grep -A 1 AuthorizationExecuteWithPrivileges!
Keep in mind that not all usage of this API is insecure so although you may call to the API, in reality it might be safe (although its usage is still not recommended).
What we specifically care about is when the first argument passed to security_authtrampoline is a file owned by a basic user. If this is the case it means an attacker that has compromised the user’s account may be able to overwrite it with his own binary. All of this is to say that detection logic should look like the following:
- Monitor for execution of security_authtrampoline
- Ensure that the executable passed to security_authtrampoline is owned by root
- Ensure that the executable passed to security_authtrampoline is a full path and not a relative one
- Ignore anything on a read-only drive (false positive reduction)
You’ll notice we added added in a third check to ensure the path is a full path and not a relative path. This wasn’t previously discussed, but often times relative paths can fall victim to path injection attacks so it’s a good check in addition to the other two.
If you’re a Jamf Protect customer, we have you covered and you can identify applications using the vulnerable update practice described above by enabling the InsecureElevatedExecution analytic in your Jamf Protect UI.
If you’re not already a customer, take advantage of this workflow with a Jamf Protect trial.