Shebang, $PATH, sudo and exploiting misconfigurations

In this post, we will look at what shebangs and the $PATH variable are, and how sudo works. At the end, we will walk through examples of how they can be exploited by a local user to get root privileges, and how to effectively mitigate it.

First are a few basic explanations, but feel free to skip to the examples if you're already familiar with them.



Learning time

Shebang

If you read Linux scripts, you'll notice that they often start with

#!/path/to/executable

or

#!/usr/bin/env executable

For example, a bash script would start with

#!/bin/bash

while a python script would start with

#!/usr/bin/env python

This is called a shebang, and it tells the system where to look for an interpreter to run the script. That is, if we don't specify one on the command line.

The second one is especially interesting, as it isn't a hard-coded value. Instead, it searches for the executable in the user's $PATH variable.

$PATH

On Linux systems, the $PATH variable is a user variable that contains all the folders where an executable can be called by name, not needing a full path. For example, if the $PATH is /usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin and we just type foo on the command line, it will search inside these folders. The search stops after the first match, and the executable is chosen. Let's say we have foo under /usr/bin. It will look inside /usr/local/bin first, before stopping at /usr/bin. The remaining folders are skipped.

sudo

Sudo is a command used to run a command as a different user. By default, it elevates a standard user to get root privileges. Root is the superadmin. It can do everything, even delete your entire filesystem.

Curious? read here


The wildly infamous

sudo rm -rf /

won't work, as it's missing an optional argument to rm. That argument being —no-preserve-root 😉 It will still mess up your system though, so only use it on VMs.


So it's a matter of course that safeguards were set up to limit who can call sudo, and what commands they can use with it. “How?” you might ask, well enter the sudoers file.

It's a little text file located in /etc/sudoers that contains which users or groups can execute which commands with whose privileges on which host. There can also be other lines which changes its behaviour. Sounds a little confusing? Don't worry, let's illustrate this with an example to see it in action.

Let's say this is in the sudoers file:

Defaults        env_reset
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

root    ALL=(ALL:ALL) ALL

%sudo    ALL=(ALL:ALL) ALL

jane    ALL=(root) /usr/bin/cat /var/log/*
%web    ALL=(www-data) ALL

The first half specifies default actions the system will take when someone calls sudo:

These are good values present by default on multiple modern installations, as they keep everything sane. A user's environment variables could create conflicts or unintended behaviours. And the secure path ensures that all executables are safe ones, as those directories can only be edited by root. But some people find them too restrictive and change them... which is good for us.

The second half is the “who can do what as who” part. It follows a specific format: user/group host=(user:group) command With that, let's break it down line by line:

Now, I know what you might ask, “Why is the host always ALL?” I sure did ask that myself. Well, the answer is because often your sudoers file is only used on a single machine, so the host doesn't really matter. You could use localhost and it will still work. The only way for it to be meaningful is to have a centralized sudoers file, manage all users and groups' privileges on their respective machines in that one sudoers file, and then synchronize the sudoers file to all the machines.

By the way, never edit the sudoers file with a normal text editor, it can brick your whole system. Always use sudo visudo



Exploiting misconfigurations

Now for the fun part! Let's see how a misconfiguration can allow a local user to gain higher privileges – this is called privilege escalation – and how to prevent it.

Lax file permissions

This is the easiest and the most straightforward to exploit. Fortunately (or unfortunately), every admin worth their salt will know how to avoid this.

Imagine we have a script that requires an elevated status to be run, for example a system update.

jane    ALL=(root) /opt/scripts/update.sh

You check update.sh's permissions, and surprise! It has a permission of 777, which means everyone – owner, group and other – can read, write and execute the file. This may not be intentional, permissions can change when transferred from a remote location or from a removable drive.

Now we can just edit it to be

#!/bin/bash
/bin/bash

and comment out or delete the rest. Now, executing sudo /opt/scripts/update.sh will give us an interactive bash shell as root

So remember: Always check your script's permissions, especially if you transferred it from somewhere else. And if you find “advices” or “fixes” online that suggest that the solution to a problem is to chmod 777 something, keep in mind that it's a very bad idea, usually done by people who couldn't be bothered to look for the real cause of the permissions issue.

More infos on chmod octal representation here


There are three digits, representing in order the permissions for owner, the owner's group, and others. These are:

  • Read: 4
  • Write: 2
  • Execute: 1

While compound permissions are done by adding them. For example, read+write would be 4+2=6

In case of a folder, the execute permissions translates to the ability to traverse the folder.



Disabled or tampered secure_path...

As was stated earlier, this flag ensures that the $PATH used by sudo only contains default programs and those added by root. Some online tutorials suggest adding a custom folder to this. This could be done, but we have to make sure they have the correct permissions so no one except root can edit the folder's contents, or else it could be easily exploited like the previous example. Then there are other tutorials that are clearly more “how to ruin your computer's security” than “how to fix X”. Yes, they advocate explicitely disabling it! What could go wrong, right? Path hijacking, that's what. It only requires another misconfiguration and the whole security becomes a joke.

...with missing absolute path in sudoers

Writing the full path is tedious, but it is done for a reason. And that is to avoid command hijacking, also called path hijacking. Let's say we have this line:

john    ALL=(root) cat /var/log/*

This would mean that john can run cat as root, allowing him to read any files inside /var/log. Now, remember the $PATH variable? It's a user variable, meaning each user has its own $PATH. The same $PATH that wasn't reset by env_reset since it's part of the minimal environment, and wasn't overriden to a safe value since secure_path was disabled. That means we can run

export PATH=/tmp/hax:$PATH

and any executables inside /tmp/hax will be executed with priority over other folders in the $PATH. We just have to create /tmp/hax/cat with the content

#!/bin/bash
/bin/bash

make it executable with

chmod +x /tmp/hax/cat

et voila! We just hijacked the cat command!

Next time we run

sudo cat /var/log/aaa

or anything inside /var/log, it doesn't matter as it's dropped anyway, we get an interactive bash shell as root

So remember: Never disable secure_path. Always use absolute paths in the sudoers file. Even if secure_path is enabled, writing the full path is a good reflex to have.

... with a /usr/bin/env shebang

Just like with the sudoers file, commands in a script can be hijacked. That's why most reasonable people put absolute paths in their scripts. But I noticed that python scripts often have

#!/usr/bin/env python

as their shebang. You probably guessed it by now, we can hijack the python command to get root access, as long as secure_path is disabled.

It doesn't matter that the sudoers has an absolute path

doe    ALL=(root) /opt/scripts/awesome.py

or that its permissions are set correctly, for example 755.

All it takes are

#!/bin/bash
/bin/bash
sudo PATH=/tmp/hax:$PATH /opt/scripts/awesome.py

Now, I'm not saying this shebang in itself is bad, so calm down, python fans! I know full well that using virtualenv means that we have a multitude of python installations on our system so this shebang is indeed the right approach... for a user. For a sysadmin, you have to make sure to always use absolute paths to point to the correct python version. And never disable secure_path.



I realize these are ideal scenarios that likely wouldn't happen in real life, but you never know. For one, you wouldn't get access to the sudoers file, but the admin should tell you what commands you can run with sudo, so start from there... is what I'd like to say, but never do this outside labs or without explicit permission. Anyways, that's all for now. See you later!