
Ancient “su – hostile” vulnerability in Debian 8 and 9 - l2dy
https://j.ludost.net/blog/archives/2018/06/13/ancient_su_-_hostile_vulnerability_in_debian_8_and_9/
======
tedunangst
For those unaware, ioctl(TIOCSTI) allows injecting characters back into the
tty, where they will be read by the next process to read from the terminal. In
this case, that process is the root shell that execed su.

~~~
_wmd
I guess you're the right person to ask - why hasn't this just been ripped out
of the likes of OpenBSD?

edit: seems it already has! [https://marc.info/?l=openbsd-
cvs&m=149870941319610](https://marc.info/?l=openbsd-cvs&m=149870941319610)

------
_wmd
Another variant of using TIOCSTI with poor permissions. FWIW this exact same
bug impacted Docker and LXC at various points. In the case of lxc-attach, when
stdio is connected to a TTY, it creates a new pty within the container and
multiplexes between them to avoid the issue. I don't think there is a single
legitimate use for that ioctl.. it should just die already

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

~~~
Dylan16807
"Terminal I/O Control - Simulate Terminal Input" ah okay

------
bcaa7f3a8bbc
PaX/grsecurity has mitigation to this issue, at least for 10 years.

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.
    

Has one run a grsecurity kernel, the system would not be affected.

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

* [https://gist.github.com/thejh/e163071dfe4c96a9f9b589b7a2c24f...](https://gist.github.com/thejh/e163071dfe4c96a9f9b589b7a2c24fc6)
    
    
        tiocsti-restrict : make TIOCSTI ioctl require CAP_SYS_ADMIN

* [https://lwn.net/Articles/720740/](https://lwn.net/Articles/720740/)

------
im3w1l
I'm increasingly feeling that terminals and bash are just too complex and have
too many edge cases and footguns and that we'd be better of just starting over
with something were security was a focus from day zero.

~~~
lmm
Yep. Unix has zillions of ways for processes to interact with each other,
which makes for an enormous attack surface. The future is something like
unikernels on the server and something like Qubes running them on the desktop,
so that each "process" is properly isolated and can only communicate through
channels that are deliberately designed for it.

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.

~~~
MisterTea
They fixed a lot of stuff in plan 9 and it's a pleasure to tinker with.
Everything is partitioned in namespaces to isolate processes. Since the entire
system is file system based, you manipulate the process namespace which is
really just a file that lists the mounts and binds which build that namespace
file system. Binds and unions are a blessing and eliminate the headache of
environment variables. Every object is a file and everything is communicated
via the network transparent in-kernel file protocol, 9p. And because of that,
Plan 9 is fully distributed.

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.)

------
peterwwillis
This explains the problem a little clearer I think:
[https://bugzilla.redhat.com/show_bug.cgi?id=173008](https://bugzilla.redhat.com/show_bug.cgi?id=173008)

~~~
masklinn
So if I understand correctly, the issue is that when su-ing to _more
restricted_ privileges a hostile program (immediately executed via -c) can use
TIOCSTI to inject commands which will escape su and execute as the more
privileged user?

Is that also an issue using `su; ./hostile; exit`?

~~~
tedunangst
You mean su to root? The same mechanism still exists, though root already has
other powers.

~~~
masklinn
No, I mean su to a new shell and execute a command there rather than su -c.

~~~
jwilk
That would be weird if "su -c" was vulnerable but interactive "su" was not.
The former is much easier to fix.

In fact, "su" in Debian (which the subject of the submitted article), calls
setsid() when you use -c, which defeats TIOCSTI.

~~~
masklinn
> That would be weird if "su -c" was vulnerable but interactive "su" was not.

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.

~~~
jwilk
I see what you mean.

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. :-)

------
codedokode
As I remember, `login` program (that asks for your login and password on
terminal) does a "virtual hangup" to prevent such things.

~~~
cryptonector
Well, on *BSD the way this works is that when the session leader exits the
pty/tty will internally call vhangup(2), which in turn does all that revoke(2)
does on the tty FD plus it sends SIGHUP to any processes with that tty as the
controlling tty.

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.

~~~
caf
When a tty is hung up on Linux, the file operations of all open file
structures associated with it are replaced with hung_up_tty_fops, which means
all subsequent read(2) returns 0 (EOF), write(2) returns EIO and ioctl(2)
returns ENOTTY or EIO.

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()).

~~~
cryptonector
Oh, that must be pretty new (relative to the early aughts anyways). Sorry I
missed it.

------
erlkonig
Ah, this looks like the old ungetc() exploit, where (back in the 1980s at
utexas.edu) we'd leave a process connected to a terminal, wait for another
user to log in, then push characters to their shell from our program using
ungetc(). Essentially, each character pushed ends up looking like a fresh
input character to the other program. The basic issue is whether all open file
handles that shouldn't be there (our hack program, for example) got closed out
by the new login session. For something like login, the question is easy,
_ONLY_ itself should be connected early on. For su, it's much weirder, since
the user may have created background jobs before running su, and su and sudo
can't reasonably close all other handles on the original tty device.

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>
    

Won't for _everything_ (no /dev/tty), but it does block the example. You can
add a tty if you have one handy, too, by using redirection in the spawned
process in this general form, but I don't currently have the cluon for how to
create a /dev/pts/<num> from the shell level - if someone can construct the
full command, I'd like to see it :-)

    
    
        setsid sh -c 'exec command <> /dev/tty2 >&0 2>&1'

~~~
jwilk
> I don't currently have the cluon for how to create a /dev/pts/<num> from the
> shell level

As I recommended in [http://www.openwall.com/lists/oss-
security/2018/06/14/2](http://www.openwall.com/lists/oss-
security/2018/06/14/2) , use screen or tmux:

    
    
      screen su - <user>
    

script(1) is more lightweight than screen/tmux, but it can't be easily
persuaded to run arbitrary commands, such as "su". :-/

~~~
yorwba
How is persuading _script_ to run _su_ not easy? I just tried _script -c su
/dev/null_ and it worked as I expected. ( _/ dev/null_ is there to prevent
_script_ from logging the interaction to a file)

~~~
jwilk
D'oh, you're right. I don't know how I missed this option.

------
carroccio
TIOCSTI ioctl
[https://events.ccc.de/congress/2008/Fahrplan/events/2992.en....](https://events.ccc.de/congress/2008/Fahrplan/events/2992.en.html)

------
wilun
Posix TTY and more precisely stdin/stdout/stderr inheritance and internals of
FD have a completely insane design. There is the famous divide between file
descriptors and file descriptions. Hilarity can and will ensue in tons of
domains. I nearly shipped some code with bugs because of that mess (and could
only avoid those bugs by using threads; you can NOT switch your std fd to non-
blocking without absolutely unpredictable consequences), and obviously some
bugs of a given class can create security issues. Especially, and in a way,
obviously, when objects are shared across security boundaries.

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...)

~~~
caf
There _is_ a model, it's just not particularly well publicised: a file
descriptor is a capability.

That's it.

~~~
wilun
Is it efficient and sufficient though? And can and do we build real security
on top of it?

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.

------
exikyut
Nobody else has pointed this out (!): whatever platform is running at this URL
doesn't sanitize input.

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 &lt; &gt;) and all.

------
pjkundert
We used TIOCSTI to attack Unix terminal sessions left open to “write” — in
1985. I was wondering when/if this would show up again!

------
JdeBP
This is old news, that keeps being reported over and over. Part of the
problem, I suspect, is that people keep pointing to the wrong locus of the
problem. Even here, this is being characterized as a _Debian_ problem.

This is a _kernel_ mechanism.

* [https://nvd.nist.gov/vuln/detail/CVE-2013-6409](https://nvd.nist.gov/vuln/detail/CVE-2013-6409)

* [https://nvd.nist.gov/vuln/detail/CVE-2015-6565](https://nvd.nist.gov/vuln/detail/CVE-2015-6565)

* [https://nvd.nist.gov/vuln/detail/CVE-2016-2779](https://nvd.nist.gov/vuln/detail/CVE-2016-2779)

* [https://nvd.nist.gov/vuln/detail/CVE-2016-7545](https://nvd.nist.gov/vuln/detail/CVE-2016-7545)

* [https://nvd.nist.gov/vuln/detail/CVE-2017-5226](https://nvd.nist.gov/vuln/detail/CVE-2017-5226)

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.

* [http://undeadly.org/cgi?action=article&sid=20170701132619](http://undeadly.org/cgi?action=article&sid=20170701132619)

* [https://github.com/openbsd/src/commit/baecf150995d4609cd1479...](https://github.com/openbsd/src/commit/baecf150995d4609cd147948779361c3152f355d)

Others take a different view.

* [http://www.openwall.com/lists/oss-security/2017/06/03/9](http://www.openwall.com/lists/oss-security/2017/06/03/9)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/15/8](http://www.openwall.com/lists/kernel-hardening/2017/05/15/8)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/17/1](http://www.openwall.com/lists/kernel-hardening/2017/05/17/1)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/29/16](http://www.openwall.com/lists/kernel-hardening/2017/05/29/16)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/30/9](http://www.openwall.com/lists/kernel-hardening/2017/05/30/9)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/30/26](http://www.openwall.com/lists/kernel-hardening/2017/05/30/26)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/30/27](http://www.openwall.com/lists/kernel-hardening/2017/05/30/27)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/30/32](http://www.openwall.com/lists/kernel-hardening/2017/05/30/32)

* [http://www.openwall.com/lists/kernel-hardening/2017/05/31/16](http://www.openwall.com/lists/kernel-hardening/2017/05/31/16)

~~~
AbacusAvenger
Usually when something is reported as being a distribution bug, it's because
they have some patch specific to their packages that causes the issue.

Is that not the case here? Are other distrbutions affected right now?

~~~
JdeBP
I say it again: This is a _kernel_ mechanism.

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.

* [http://repo.or.cz/davej-history.git/commitdiff/9d09486414951...](http://repo.or.cz/davej-history.git/commitdiff/9d0948641495169728d4074f976fd655e30afedf)

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.

* [https://github.com/freebsd/freebsd/commit/74b0526bbe6326adb7...](https://github.com/freebsd/freebsd/commit/74b0526bbe6326adb72e26dabfe79ab1fe00ca4b)

OpenBSD, which no longer implements the kernel mechanism, _had_ had it since
the initial import from NetBSD in 1995.

* [https://github.com/openbsd/src/blob/df930be708d50e9715f173ca...](https://github.com/openbsd/src/blob/df930be708d50e9715f173caa26ffe1b7599b157/sys/kern/tty.c#L847)

Illumos has it, and has had since at least the OpenSolaris launch.

* [https://github.com/illumos/illumos-gate/blob/9a2c4685271c2f0...](https://github.com/illumos/illumos-gate/blob/9a2c4685271c2f0cb4b08f4cc1192387e67af3f9/usr/src/uts/common/io/tty_common.c#L263)

It was even in 4.3BSD.

------
bjt2n3904
Just tested this out, can confirm it works on Debian 7 as well. Genius little
trick! Not sure about practical exploitation, though.

------
blauditore
I'm not a shell pro; what is happening on the sleep line?

~~~
c0l0
$ (sleep 10; /tmp/a.out id) &

$ -> 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`.

~~~
blauditore
Ah thanks, I didn't understand the subshell forking part before.

------
sandworm101
Lol. Thanks. Work machine. Must have ctrl-tabbed without realizing.

~~~
mikestew
Pretty sure you're in the wrong thread, mate.

~~~
qu4z-2
I guess they ctrl-tabbed without realising it...

