Dev Environment Security Threats: The PATH to destruction

Introduction

This article is the first in what I hope to be a series on the security threats around developer machines. As developers, the integrity of our machines can have wide ranging impacts. Collectively we build software daily which ship to billions of users around the world. If our machines are compromised then so are our companies and our users. I plan to dig in and discuss potential threats to developer machines and how the might be mitigated.

Some notes about this series

  • We’re not interested in attacks where the attacker already has root access as all bets are off in that scenario.
  • Most of my discussions will be done from the POV of a macOS developer, as that’s the OS I develop with. However, most things directly translate to other nix environments and – to a certain extent – Windows as well.

PATH Interception

One of my biggest concerns has been around PATH (variable) Interception. This is an attack where either:

  • a malicious party inserts an extra path at the beginning of your PATH and that path contains a binary which overrides a binary in a lower precedent path;
  • a malicious party writes a malicious binary to a directory in a path in your PATH which overrides a binary in a lower precedent path.

N.B. PATH in this article always means the PATH environment variable.

The second attack requires one of the paths in your PATH variable to be writable by the current user. In fact, things are slightly more nuanced than that. If the parent directory of a directory in your path is writable by the current user then an attacker could delete and replace the path even if it’s owned by root. However, that’s a slightly trickier attack as the attacker would need to replace the existing contents of that directory in order to reduce the likelihood of being detected. 

The command line is particularly vulnerable to PATH interception attacks, which is problematic as most developers are heavily dependent on the command line. 

ASIDE When writing application code, a common programming mistake is to shell out to execute another program but only specify the binary name and not the binary path. Thus PATH interception is something developers must also be very aware of when writing their code. I won’t discuss this in any further as mitigation here is straightforward - learn not to do that! 

Bash Attack Vectors

Let’s start with looking with how the PATH variable is constructed on macOS. We’ll start by looking at the default command line environment on macOS, which is Terminal + Bash.

Note: As of macOS Catalina (10.15) it is recommended to use zsh instead and likely by 10.16 zsh will be the default. Everything discussed here is applicable to zsh as well, although some details may vary.

Bash sets up its environment by loading profiles in the following order

  • /etc/profile
  • ~/.bash_profile if it exists else
    • ~/.bash_login if it exists else
      • ~/.profile

(see the source code shell.c for the source of truth)

/etc/profile

The contents of /etc/profile on a clean install of macOS are as follows:

# System-wide .profile for sh(1)

if [ -x /usr/libexec/path_helper ]; then
	eval `/usr/libexec/path_helper -s`
fi

if [ "${BASH-no}" != "no" ]; then
	[ -r /etc/bashrc ] && . /etc/bashrc
fi

Some important things to note

  • /usr/libexec is SIP protected on macOS so we can trust that binary 
  • From the man page 
    • The path_helper utility reads the contents of the files in the directories /etc/paths.d and /etc/manpaths.d and appends their contents to the PATH and
  • If we’re not using bash then /etc/bashrc will get loaded (but macOS Bash executable sets the BASH variable)

Nothing much to worry about here. Both the paths /etc/paths.d and /etc/manpaths.d are only writable by root, however an occasional audit of may be wise. My own folders were adding the following to PATH

  • /Applications/Wireshark.app/Contents/MacOS
  • /Applications/VMware Fusion.app
  • /usr/local/go/bin

It turns out that /Applications/Wireshark.app/Contents/MacOS is writable by the my user. This is because the group for that folder is admin and my user is an admin. I believe it’s reasonable for my user to be an admin – I’m not sure how practical being a dev would be without admin. I chgrp‘d Wireshark.app to wheel and everything seems fine. I was considering reporting this to the Wireshark team, but since the path is a low priority path it can’t override any system binaries so shouldn’t be a threat.

~/.bash_profile (and friends)

These files are ordinarily writable by the current user and thus any malicious software running on your system can alter them. This can be partially mitigated with 

sudo chown root .bash_profile && sudo chmod 644 .bash_profile

I’ve had my profiles like this for a while with no impact. I suspect this will mess up a few tools which like to write their own settings to .bash_profile, but frankly I’ll take that pain. Tools should not update .bash_profile directly IMO and instead should just recommend you to do this after install.

This unfortunately doesn’t solve the problem entirely though. Since your home directory is writable by the current user, it’s simple for malicious software to just read the contents of .bash_profile, delete the file, write it back again with the old contents and then add a malicious change to PATH. So this is barely a mitigation at all.

Auditing Profiles on Startup

We can mitigate the above problem by auditing the user owned profiles on startup. In the /etc/profile file we can run a script which checks that those profiles are owned by root and not writable by the current user. If they are then we can warn or early exit.

I’ve added a script to my profile which checks all my profiles are only writable by root (see below).  

ASIDE: Originally I messed up my analysis here and totally failed to spot that I could use /etc/profile to audit files. You can see my fail by looking at zsh archives here (I'm still facepalming a little after that!) 

Terminal Attack Vector

The standard macOS terminal itself has a configuration. This configuration allows you to specify which shell the Terminal will run:

This configuration is stored in ~/Library/Preferences/com.apple.Terminal.plist which, as you’ve guessed, is writable by the current user. 

If an attacker can change the shell executable then they can easily launch a malicious binary which circumvents any protections put around the profiles.

There’s no effective way to protect this file against attack unfortunately. Changing it to be owned and only writable by root (like .bash_profile above) doesn’t work since the directory it is in is writable by the current user. It’s thus simple for malicious software to just read the contents of the plist, delete the file, write it back again with the old contents and then change the shell. We could audit this file in our /etc/profile but a determined attacker could foil this with a malicious shell – although that would seem like a very unlikely attack vector. Making com.apple.Terminal.plist owned by root might also have odd effects on the Terminal. I’m personally happy with checking the contents of com.apple.Terminal.plist for now in /etc/profile.

I looked at a few other terminals like iTerm and all of them seem to fall foul to this vulnerability. I might present suggestions to a few terminal code bases about a fix for this and see what the owners think. However, due to the edge case nature I don’t expect much uptake.

Summary

The above analysis may seem a bit “tin foil hat” on first reading. However, given the amount of 3rd party software developers are forced to trust to do their job, it’s not unreasonable to expect an attack along these lines. Any single one of those dependencies could be compromised and take advantage of this attack. This could allow an attacker to mass deploy malware to the general public by targeting developer machines.

Right now there’s no perfect mitigation, but things can be improved as follows:

  • Audit your path directories and profiles regularly
  • Use a (noisy) script in a root owned profile (like /etc/profile) to validate certain things are true about your environment, e.g.
    • Profiles are writeable only by root
    • Your shell executable is the one you think it is
    • Directories in your path aren’t writable by the current user

The latter point will be the subject of my next article. Homebrew unfortunately breaks that final assumption in a bad way.

Here’s a link to the script I run in my .bash_profile to do the above

One final thing worth noting is the value SIP brings to macOS. We saw a glimpse of this above but SIP is incredibly important for giving you confidence in the “base” tools on your system.

Leave a Reply

Your email address will not be published. Required fields are marked *