edit: seems it already has! https://marc.info/?l=openbsd-cvs&m=149870941319610
tl;dr passing a TTY with stopped privileged jobs reading from it (like an interactive root shell) into an unprivileged location is deadly, as the unprivileged location can use TIOCSTI to load up the TTY's input buffer and then exit, causing the stopped jobs to read that input when they're resumed
From grsecurity's config for GRKERNSEC_HARDEN_TTY.
| There are very few legitimate uses for this functionality and it
| has made vulnerabilities in several 'su'-like programs possible in
| the past. Even without these vulnerabilities, it provides an
| attacker with an easy mechanism to move laterally among other
| processes within the same user's compromised session.
Some independent developers and KSPP people are also trying to submit this mitigation to the mainline kernel for many years, but so far none of the patch went into the kernel. Since grsecurity is now a private product, you may want to check them out and apply this mitigation to the mainline kernel.
[PATCH] drivers/tty: add protected_ttys sysctl
tiocsti-restrict : make TIOCSTI ioctl require CAP_SYS_ADMIN
We're going to have to rediscover how we do things like pipelines in a safe way, but the current unix design of small processes interacting via unstructured interfaces that mingle commands and data is just untenable.
For example: I can share the internet without a router or nat by exporting my internet facing ip stack and mount it on the machines needing net access. As far as the isp knows, a single machine is talking to it. I can do the same with file systems, file servers, network cards, sound cards, disks, usb devices, etc.
It's far from usable in production and 9p is a dog over high latency links. but the ideas it has are simple yet brilliant. Best distro to check out for newcomers is 9front (they're silly fellows but don't let that fool you. serious top notch hackers that lot.)
I doubt that.
I believe every project should be re-written every 4-5 years, it becomes less and less expensive to maintain it on the long run. Also keeps workers morale and promotion cycle healthy.
Unfortunately, not all companies have resources AND structure to put that resources in refactoring something that already works.
Don't get me wrong, I love internal projects because once you're behind the gate, they are the first things I probe and attack. Usually very easy targets.
Is that also an issue using `su; ./hostile; exit`?
( sleep 10 ; hostile ) &
In fact, "su" in Debian (which the subject of the submitted article), calls setsid() when you use -c, which defeats TIOCSTI.
Not necessarily, if TIOCSTI just pushes stuff to the term input buffer, this is going to get popped on the next prompt of su, so on an interactive su it's not going to be executed under escalated privileges but is instead going to be executed as the same user that executed the hostile program, while with a non-interactive su it's going to get popped on the next prompt of the su caller and thus get escalated privileges.
That's my understanding of it anyway, I could be completely off.
Yes, the exploit won't work if the payload is read by the attacker's shell, rather than the root shell. But it's easy to ensure that this won't happen. The laziest way is to kill the shell before issuing TIOCSTI ioctls. :-)
Linux for a long time had nothing like this. It has a vhangup(2), but looking at its implementation it doesn't seem to do what the BSD vhangup(2) does by calling revoke(2): replace all open file descriptors pointing to the tty with one that returns EIO for read(2)/write(2)/etc.
Linux does NOT have a revoke(2), or at least I can't find it. There was a patch for that back in 2006 and 2007. I don't know what happened to that.
EDIT: Some trivia as well. On BSD SIGHUP is generated by the tty. On Linux and Solaris/Illumos it's only generated by the session leader process on exit, and only if it wants to. This is how bash's disown builtin works: it just doesn't HUP the disown background jobs. The C shell (csh) historically never generated SIGHUP because it's a BSD shell. Back in the early aughts there was some controversy where csh users wanted OpenSSH's sshd to close a session when the pty session leader exits, as otherwise the session would stay open indefinitely. The OpenSSH developers feared this would lose data, and they were right. The source of this problem was that csh wasn't generating SIGHUP on Solaris and Linux, so background jobs stayed alive and held an open file reference to the pty, which meant that they kept sshd from seeing EOF on the ptm, so sshd would not terminate the session as long as those bg jobs stayed alive. This is all still the case today.
This is basically a tty-specific implementation of revoke(2).
Also, when the session leader exits on Linux, the kernel does send SIGHUP and SIGCONT to session leader's process group and the foreground process group (this is in tty_signal_session_leader()).
I think it's pretty shocking that there's not yet a revoke(2) for all these systems. If there was, then sshd could revoke(2) the pts when the session leader exits, then wait for EOF, and then close the session.
Also, please stop using the C shell. It's a shell that sucks. Granted, they all kinda suck, but the C shell more than all the rest. :)
EDIT: Oh, and to be clear, this is about the server-side of ssh. That you're using OS X on the client-side makes no difference.
Further su and sudo can't close all file descriptors of the "sub-session" as it exits, because that the "sub-session" is created by forking, so su/sudo aren't around at the end.
Creating a separate pseudo-terminal device to allow for draconian cleanup, and prevent even having both user IDs connected to the same tty device, seems like the best place to start.
Hmm, now I want to go update the user-group-setter program I use (which also can set auth user IDs on Solaris, etc) and try having it do ptty allocation for the subjob.
In the meantime, try this to get a session and run through the same demo steps:
setsid -w su - <user>
setsid sh -c 'exec command <> /dev/tty2 >&0 2>&1'
Yes, they are. The world has changed since the 1980s. This particular aspect changed in the middle 1990s.
There are plenty of tools for manipulating pseudo-terminals, but the correct solution here, that people have been pressing for for quite a number of years now and that some kernel people have already implemented, is to simply remove this ioctl() entirely. setsid() is incorrect for the reasons that Karel Zak alluded to in June 2017, and the work to patch the few users of TIOCSTI such as the C shell and mail has already been done by the OpenBSD people.
As I recommended in http://www.openwall.com/lists/oss-security/2018/06/14/2 , use screen or tmux:
screen su - <user>
Far is the time when Unix people were making fun of the lack of security in consumer Windows. Today, there is no comprehensive model on the most used "Unix" side, while modern Windows certainly have problems in the default way they are configured, but at least the security model exist with well defined boundaries (even if we can be sad that some seemingly security related features are not considered officially as security boundaries, at least we are not deluding ourselves into thinking that a spaghetti of objects without security descriptors can be shared and the result can be a secure system...)
This issue shows systems have been built for decades with blatant holes because it was not taken into account in even core os admin tools.
There is the other problem corresponding to the myth that everything is a fd. Which has never been true, and is even less and less as time passes.
Also, extensive extra security hooks and software using them are built, but not of top of this model.
Finally, sharing posix fd across security boundaries often causes problems because of all the features available for both sides, for which the security impact are not studied.
A model just stating that posix fd are capa is widely insufficient. So if this is the only one, even in the context in pure Posix we already know this is an extremely poor one.
Notice how the C #includes seem to be including emptiness. Well, <stdio.h> et al weren't stripped; they're still in the source code, un-converted < > (ie NOT converted to < >) and all.
This is a kernel mechanism.
Some kernel people take the view that TIOCSTI is of no Earthly use, and there are other ways to implement line editing, and just make using it an error.
Others take a different view.
Is that not the case here? Are other distrbutions affected right now?
Every operating system based upon Linux provides programs with this mechanism. This is not some ioctl() introduced by a Debian patch. This is a mechanism added to Linux by Linus Torvalds on 1993-12-23.
Pete French added it to FreeBSD against his better judgement on 2010-01-04. It might have been in an earlier implementation of the terminal line discipline, too.
OpenBSD, which no longer implements the kernel mechanism, had had it since the initial import from NetBSD in 1995.
Illumos has it, and has had since at least the OpenSolaris launch.
It was even in 4.3BSD.
I suspect most other distros use the one from util-linux.
$ -> the end of the prompt of a "normal" (i. e. non-root) user
() -> run everything inside in a forked subshell of the current shell
sleep 10 -> "block"/sleep for 10 seconds via the `sleep` executable in your $PATH
; -> after the left-hand side terminates, proceed with the next command on the right-hand side
/tmp/a.out id -> fork and exec the program located at /tmp/a.out with the literal byte sequence "id" on its argument vector
& -> run this command (the whole subshell that () requests) as a background job
When the user exits the shell that spawned the subshell, the whole process group will receive SIGHUP. The backgrounded subshell will still continue running, and after its `sleep` child process terminates, go on to run `/tmp/a.out`.
A background subshell is started that waits 10 seconds and then runs a program with the input "id". That program uses ioctl to send characters to the current TTY.
Since the user exited back to the root shell before the program executed, the `id` command is typed and executed on the root shell.
This is to make the process survive the subsequent logout.
Because this process inherited terminal fds from the parent, it can continue accessing the logged out terminal (now root).