POSTS
Sudo Science
Somehow I made it this far without actually understanding how sudo works. For years, I’ve just typed sudo, typed my password, and revelled in my new, magical, root super powers. The other day and I finally looked into it – to be honest, the mechanism is not at all what I expected. After going through the basics, we’ll walk through creating our own version of sudo.
How Sudo Works
sudo is just a regular old program that essentially does 3 things:
- Ask for your password and compare it to
/etc/shadow1 - Check to see if your username or group is in
/etc/sudoersfile. execthe command you want to run asroot
root is a special user that is bestowed with super powers, among them, impersonating other users. Each of these three steps actually require root permissions to accomplish. The key is that sudo is a setuid binary – this means that it gets run as the user that owns it instead of the current user. This is what gives sudo its power. Because sudo is owned by root, the program is run as root.
✗ ls -l /usr/bin/sudo
-rwsr-xr-x 1 root root 136808 Jul 4 2017 /usr/bin/sudo
^
The magic bit here is "s" making it a setuid binaryLet’s Make a (Useless & Dangerous) Sudo
setuid is actually a bit subtle. To explore the subtleties, we’ll go through a few false starts on our path to implement sudo.
As a bash script
Let’s try writing a bash script that will make us root without asking for a password:
#!/bin/bash
exec $@$ sudo chown root.root badsudo.sh
# Give the owner (root) setuid permissionns (s)
$ sudo chmod u+s badsudo.shLet’s try doing something privileged:
$ russell@russell-linux:~/scratch$ ./badsudo.sh cat /etc/sudoers
cat: /etc/sudoers: Permission deniedWomp. Sadly, we are not yet root. The reason why this doesn’t work is an important distinction: The binary in this situation is /bin/bash, invoked by the shebang – we only marked the shell script as setuid. Since /bin/bash isn’t a setuid binary, our shell script didn’t run as root.
If you are a thrill seeker, you can mark /bin/bash as setuid and chown it to root. Please don’t do that.2
In Rust
So we need a compiled language to make this work…to Rust!
extern crate exec;
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let _res = exec::execvp(args[1], args[1..]);
}Let’s try it! If whoami prints root, we know we can run programs as root.
$ sudo chown root.root rustsudo
$ sudo chmod u+s rustsudo
$ ./rustsudo whoami
russellStill doesn’t work! Turns out Linux really doesn’t want you messing around with setuid binaries. They’re just too dangerous and powerful. On my OS, at least, my home directory is actually mounted nosuid:
$ mount | grep "/home/russell"
/home/.ecryptfs/russell/.Private on /home/russell type ecryptfs (rw,nosuid,nodev,...)I’m not aware of any specific reasons to mount your home directory as suid. A reddit commenter pointed out that it’s because my home directory is mounted on encryptfs. Because of CVE-2012-3409, encryptfs always mounts nosuid. In any case, it’s certainly not a bad idea.
Any external media is also mounted nosuid. If you didn’t, a user without sudo access could elevate their permissions by inserting a flashdrive with a setuid binary.
To work around nosuid restrictions, we need to put the binary in a directory not mounted with setuid. On my computer at least we can use /tmp. /tmp is mounted on /, the root filesystem which doesn’t have setuid restrictions.
In Rust, Take 2
Don’t try this at home (or if you do, make sure to clean up after yourself)
$ cp rustsudo /tmp
$ ls -l /tmp/rustsudo
-rwxr-xr-x 1 russell russell 5809968 Mar 16 11:16 rustsudo
$ sudo chown root.root /tmp/rustsudo
[sudo] password for russell:
➜ sudo chmod u+s /tmp/rustsudo
➜ /tmp/rustsudo whoami
root
➜ cat /etc/sudoers
cat: /etc/sudoers: Permission denied
➜ /tmp/rustsudo cat /etc/sudoers | head
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
......
Success!
Closing Thoughts
I was actually really surprised by the setuid mechanism that enables sudo to work. In hindsight it seems reasonable, but I had no idea there was a filesystem bit that marked a program to be run as its owner. It’s a really cool mechanism! If your program is running as root, it’s a good idea to execute the setuid stdlib function to downgrade your permissions to a non-root user as soon as possible to minimize the exploit surface.
Want to get emailed about new blog posts?
Do you want to hire me? I’m available for engagements from 1 week to a few months. Hire me!
/etc/shadow/is the file that actually contains the password hashes [return]- As a Reddit user pointed out, this doesn’t actually work. Bash will detect being marked as a
setuidbinary, and if it is, it will drop permissions. To actually do this, you could make a super-bash program that runsexecve(["bash"], ["bash", "-c", ..args]);. Marking thissuper-bashprogramsetuidwould do the trick. The key is that that the bash binary itself is not marked assetuid. [return]