Today's exploration has taught me things, horrible things

This security model is fuckin' wacky
This commit is contained in:
R Tyler Croy 2021-07-17 14:47:40 -07:00
parent b9931a5f17
commit ce68e55d18
No known key found for this signature in database
GPG Key ID: E5C92681BEF6CEA2
1 changed files with 99 additions and 0 deletions

View File

@ -0,0 +1,99 @@
---
layout: post
title: How sudo gets root
tags:
- security
- linux
- unix
---
Today I fell into a rabbit hole around user and process
permissions and had to learn how `sudo` actually worked. For the program I was
working on I set out to figure out how to perform something akin to a "user
swap" when launching subprocesses. On its face it seems simple enough, my
program runs with user id `1000` and I wish to shunt child processes over to
run as another user id. `sudo` can do it, so why can't I? "For reasons" is the answer.
The alternative for this post could be "unix permissions are insane".
Regardless of whether spawning a child or doing something within the process
for modifying "who" is running a program is effectively the same. The program
runs with the user id (`uid`) of the caller, so in the case of your shell which
is running as you (often `uid` of `1000`) and spawned process will inherit the
same `uid` .
In a C program, I can however reassign my process' `uid` with
the `setuid(2)` system call:
> **`setuid()`** sets the effective user ID of the calling process. If the
> calling process is privileged (more precisely: if the process has the
> **CAP_SETUID** capability in its user namespace), the real UID and saved
> set-user-ID are also set.
(In Rust this function is helpfully exposed via [the nix crate](https://docs.rs/nix/0.22.0/nix/unistd/fn.setuid.html).)
My normal shell user is _not_ the `root` user and therefore in just about every
program I execute, they cannot use `setuid(2)` to change to **any other user
id**.
If processes spawned by my user cannot assume other user ids, how the heck does
`sudo` do it?
Based on the man page excerpt above, it's easy to assume that perhaps the
process has the `CAP_SETUID` capability? Per-file capabilities are a Linux-ism which exposed to user-land via libcap:
> Libcap implements the user-space interfaces to the POSIX 1003.1e capabilities
> available in Linux kernels. These capabilities are a partitioning of the all
> powerful root privilege into a set of distinct privileges.
These capabilities _can_ provide the desired `setuid` functionality, but they
can also be dangerous and lead to vulnerabilities. [This blog
post](https://steflan-security.com/linux-privilege-escalation-exploiting-capabilities/)
has a simple demonstration of using `CAP_SETUID` to gain root on a machine. We
can check whether the `sudo` binary has this capability with the `getcap`
program, which is not always in the distribution's default packages:
sudo getcap `which sudo`
Unfortunately it looks like `sudo` has no special file capabilities,
fortunately there is _one_ other way to for a program to run with `root`
privileges, but if you don't know where to look, good luck finding it!
The file system contains a little bit more information about an executable
referred to as the "setuid bit". Using `chmod` you take a binary that you own,
set the "setuid bit" and then whenever _any_ user runs the binary, it will run
as your user. Naturally if a binary is owned by root with the setuid bit set,
any caller of that binary will run the binary **as root**.
This is exactly what sudo does, which you can check just by exampling the file mode:
ls -lah `which sudo`
-rwsr-xr-x 1 root root 181K May 27 07:49 /usr/bin/sudo
Note the `rws` at the beginning of the mode string, this means read (`r`),
write (`w`), and setuid (`s`) are all set at the user level on the file.
Additionally the executable (`x`) bits allow all users within the `root` group,
and _everybody else_ to execute the file.
Sudo basically starts out as `root` and then will read its configuration file
and validate/drop permissions as necessary. Since the program is running with
the `root` level privileges, it can easily call `setuid(2)` type functions and
change to _any_ user on the system.
Should you wish to explore _exactly_ how sudo works, the [source code is on GitHub](https://github.com/sudo-project/sudo).
---
There is effectively no user hierarchy in Unix-inspired systems, there is
_root_ and then there is everybody else. Unfortunately for my hacking this
means I cannot have a user (`1000`) launch a process with a "lateral" user
permission such as uid `1001`. I would first need to escalate privileges in
some manner from `1000` to `root` and then drop back down again to `1001`.
If you ever see a program that "drops privileges" such as Docker, Nginx, or Systemd, just
remember that in there is no dropping privileges without first escalating to
`root` in a Unix-inspired system!