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:
- Defaults env_reset: will reset the environment to a minimum set (like $HOME and $PATH)
- Defaults secure_path=“...”: will forcefully override the $PATH variable before launching the command
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:
- root ALL=(ALL:ALL) ALL: user root is allowed to run all commands as all users of all groups on all hosts
- %sudo ALL=(ALL:ALL) ALL: members of the sudo group can run all commands as all users of all groups on all hosts
- jane ALL=(root) /usr/bin/cat /var/log/*: user jane can run
/usr/bin/cat
on all files inside /var/log. This would mean that she is allowed to read all log files - %web ALL=(www-data) ALL: members of the web group can run all commands as www-data. Of course, it's limited to commands that www-data itself can use. This typically means that they can manage webservers like apache and nginx
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
- creating our hijacked python executable with the usual bash invoker (be sure to name it python)
#!/bin/bash
/bin/bash
- using it to “run” the script
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!