By Austin Ralls
At Carve, we help secure a variety of Unix-like systems, from cloud servers to IOT devices to on-prem installations. A core defense in depth practice is privilege separation through using specific user accounts for different services and administrative accounts.
Servers where everything is running as root have serious security issues. If a service running as root gets compromised, the attacker gets full control of the system. If a service running as a regular user gets compromised, the attacker only gets access to files related to that service… right?
In this blog post, we’ll talk about some ways sudo can be misconfigured and how you can better evaluate the security of your configuration. At the end, test yourself with our set of hands-on challenges.
The point of sudo is to allow low-privilege user accounts to act as other, often higher-privilege accounts. The configuration is located in
/etc/sudoers and is commonly configured to include files in
/etc/sudoers.d/ as well. The file can contain various directive, but the meat of the file is the user specifications. One that comes with Ubuntu 18.04 is:
%sudo ALL=(ALL:ALL) ALL
This allows any user in the group “sudo” to execute on any host, as any user and group, and to run any command. This is the configuration for trusted administrators to log in as their user but still be able to execute high-privilege administrative commands as root.
A more detailed review of sudo configuration is far too long for this post. The best source of documentation is the manual.
Sometimes a service will need high privileges to perform a specific task. One way to enable this is to give the service’s account a specific sudoers entry. For example:
webserver ALL=(root) NOPASSWD: /usr/bin/systemctl
This allows the user “webserver” to run the binary
systemctl with any arguments without entering a password. We’ve seen something like this used to allow users to restart services on a router through a web interface.
The point of running services as non-root users is so that if an attacker compromises the service in some way, the entire system isn’t compromised. If an attacker can escalate to a root shell from that non-root user, it’s the same as if the service was running as root.
systemctl example, if an attacker who achieves execution as the webserver user can run
systemctl as root, the attacker can run any command as root:
$ whoami webserver $ cat << EOF > /tmp/rootcommand.service [Unit] Description=Run arbibtrary command as root [Service] Type=oneshot ExecStart=id [Install] WantedBy=multi-user.target EOF $ sudo systemctl link /tmp/rootcommand.service Created symlink /etc/systemd/system/rootcommand.service → /tmp/rootcommand.service. $ sudo systemctl start rootcommand.service $ sudo systemctl status rootcommand.service ● rootcommand.service - Run arbibtrary command as root Loaded: loaded (/tmp/rootcommand.service; linked; vendor preset: disabled) Active: inactive (dead) Jan 01 01:01:01 hostname systemd: Starting Run arbibtrary command as root... Jan 01 01:01:01 hostname id: uid=0(root) gid=0(root) groups=0(root) Jan 01 01:01:01 hostname systemd: rootcommand.service: Succeeded. Jan 01 01:01:01 hostname systemd: Started Run arbibtrary command as root.
id is just a proof of concept; there are malicious commands an attacker could run. The point is that allowing
systemctl to be run as root allows any other command to also be run as root.
There are many other ways to weaken a sudo configuration. This post just covers the specification of binaries, not any other escalation method. Those may be covered in another post.
So, there are sudo configurations that allow privilege escalation to root and configurations that do not. How do you determine which are which? The best way is to try to escalate privileges yourself. Pretend to be an attacker and think how you could abuse the command you’re allowed to run. We have created a small wargame (set of challenges) for practicing escalation to root.
To simplify running the challenges, we packaged each level as a docker container. Run
docker run -it carvesystems/sudo_level1 in a terminal to try the first level. The goal is to get a root shell (run
bash -l as root to get a congratulatory message). Once you’ve done that, increment the number and go on to the next level. There are 10 levels in total. Once you’ve solved them all, or if you’re stuck and want a hint, drop us a line at firstname.lastname@example.org. We’ll send the first five emails with all ten solutions a Carve Systems shirt.
Writing secure sudo configurations is prone to error in unexpected ways. This is dangerous functionality that should be used as little as possible. The best method is to perform threat modeling at design time and architect the system in a way that doesn’t depend on complicated sudo configurations.
If you do need to grant restrictive sudo access, we recommend:
- Make it so that root is not required. For example, if you need access to a root-owned file, consider changing the owner or group to that of the user needing access.
- Create another program that does the required high privilege actions (and no other actions) and configure sudo to allow the low-privilege user to call just that program as root.
- Specify all arguments in the sudoers configuration.
- If applicable, run the service as root, perform actions that require high privilege, and then drop permissions (see nginx as an example).
Best practices can only get you so far. Penetration testing can help reveal further sudo misconfigurations, and threat modeling early on can prevent surprises later on.
Author: Austin Ralls