how Homebrew invites users to get pwned
Popular macOS package manager Homebrew is a great way to easily install and manage 3rd party software. As their own tag line goes, “Homebrew installs the stuff you need that Apple didn’t.”
However, installing it recently on a new setup brought something odd to my attention. An oddness, it turns out, that is a gaping security flaw.
do yourself a favor and install to /usr/local. Some things may not build when installed elsewhere. One of the reasons Homebrew just works relative to the competition is because we recommend installing to /usr/local. Pick another prefix at your peril!
Peril indeed, for those that follow that advice. Homebrew’s installer is kind enough to tell you what is happening, but it seems neither the installer nor the developers have any idea just what this means:
As soon as I saw that, the words ‘sudo piggyback!’ sprang to mind. But wait, the brew docs say installing into
/usr/local is safe, look at the screenshot at the top of this post from their FAQ, which says
3. It’s safe
Apple has left this directory for us. Which means there is no /usr/local directory by default, so there is no need to worry about messing up existing tools.
Ah, wrong kind of safe. Here, we’re not concerned with ‘messing up’ existing tools, but spoofing system tools that live further down the path search hierarchy behind the user’s back. Also, what the brew docs fail to mention is that although Apple may have “left this directory for us”, they didn’t intend for you to change its ownership and make it writable by just anything in userland. Other 3rd party software plays correctly with
usr/local and doesn’t change its ownership permissions.
Why does brew do this? According to the docs, they want to avoid using
sudo because of the security flaws it contains (it’s true,
sudo does have security issues); unfortunately, the proposed solution is far worse and creates a far bigger security hole.
The brew docs seem to be unaware of the danger, however, only noting that:
If you need to run Homebrew in a multi-user environment, consider creating a separate user account especially for use of Homebrew.
But that just isn’t going to cut it. We’re not worried about other users, but processes running as our user that can now attempt to elevate their own privileges by stealing the admin user’s password.
How’s that possible?
To understand the crucial error being foisted upon Homebrew users here you need to understand a little about the program search path on macOS and other unix variant operating systems.
This is basically a list of directories that the shell environment uses to find programs. It’s a convenience so that no matter what directory you’re in in the shell, you can execute commands without having to specify the full path to them. This is why you can type, say,
uptime in any directory and the program will run instead of having to type
The program search path hierarchy is saved in a variable called
PATH. You can see its value by typing
echo $PATH at the command prompt:
A more reader friendly version is output by doing
The order is ‘first come first served’; in other words, when you type a command on the command line, the shell will look for it first in the first path in the list. If it doesn’t find it there, it will move to the next path in the list and so on. However, and here’s the crucial bit, it will stop at the first hit, and execute that command.
As you can see from the above,
/usr/local/bin occurs before the other directories, which means it gets searched first. So, if you had an executable in there called
uptime that didn’t do what the normal uptime does, but say, advanced your clock by 1hr, then when you (or anyone else on the system) typed
uptime in the command line, instead of getting the output of how long the system has been booted, you’d get your clock going forward an hour. The system doesn’t know which
uptime you (or any other user) intended if you don’t specify the full path; it just executes the first
uptime it finds in the program search path.
If you’re still thinking “so….???”, let me add two more little spicy notes into this melting pot:
i. Since Homebrew changes the permissions on
/usr/local/bin to the user (see the preceding screenshot), the user (or any process running as the user) is able to write files to it and give those files executable permissions.
sudo is a program that lives in
/usr/bin, the path that is after (Danger! Danger!)
/usr/local/bin. Now if you (or someone else, or some other program) were to place a program called
/usr/local/bin, then every time you typed
sudo it would be that program that would be executed, not the real one.
Hopefully the picture is becoming clearer now, and I apologise if I’ve laboured the point for those of you that saw it right away, but this is worth being clear about. This hypothetical
sudo program could easily capture your password before passing on your commands to the real
sudo and you’d be none the wiser (until, of course, the malicious actor behind it chose to use your password for their own amusement or benefit!).
Oh, did I say ‘hypothetical’? Well, here’s a short video of me actually doing it in my VM (yes, folks, I know you don’t need
sudo to execute
uptime, it’s just an example; the command could be anything, such as
sudo mkdir -p /Library/...):
Sure enough, I was able to use a simple script to steal the user’s password. In this case, an admin password, but it could and would have been the password of whoever is set as the owner of
/usr/local/bin as a result of Homebrew’s recommended installation. Even for non-admin users this is a worry as the login password of course allows full access to the user’s Login Keychain.
Eh, run that by me again / tl;dr.
Installing Homebrew as recommended means that from then on, any process or application you launch can write anything it wants into the first directory that gets searched for command line binaries, change its mode to
execute and give it the same name as a system binary. It will then run instead of the system binary whenever you type the program with the same name in the command line (unless you type the full path to it). The potential for exploitation is vast. Few people if any ever type the full path to workaday binaries like
sudo and many others. And as shown in my example, any of these could be hijacked to perform different operations thanks to the way Homebrew is installed. This can be done and cleaned up in such a way that you’d never know it had happened.
What can you do about it?
My advice is if you’re running Homebrew from
/usr/local/bin you should
i. Uninstall Homebrew; follow the instruction here under ‘How do I uninstall Homebrew?’ This will remove all your installed packages.
ii. Reset the permissions of
/usr/local/binback to ‘wheel’.
sudo chown root:wheel /usr/local/bin
iii. Reinstall Homebrew and choose a location within your home folder.
iv. You should probably change your login password just to be on the safe side.
Above all, stay safe folks!