Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Message-ID: <Ya4pNtfatmG59Cbj@f195.suse.de>
Date: Mon, 6 Dec 2021 16:16:06 +0100
From: Matthias Gerstner <mgerstner@...e.de>
To: oss-security@...ts.openwall.com
Subject: tmate-ssh-server: Local Privilege Escalation Issues and DoS issues
 (CVE-2021-44512, CVE-2021-44513)

Hello,

this report is about code review results of the tmate terminal sharing
software [1]. Some local privilege escalation and remote denial-of-service
attack vectors have been identified. Skip to section 6) for the concrete
findings. The following sections give a broader overview of the tmate
security design.

[1]: https://tmate.io

1) What is tmate?
=================

Tmate allows a terminal session to be easily shared over the network using the
SSH protocol. Other people can attach themselves to the shared terminal
session either with full access or with read-only access (just viewing what
happens on the terminal).

Tmate consists of two forks of the tmux terminal multiplexer's [2] code base.
One fork is for the tmate client side [3] (the party that is sharing its
terminal over the network) and one fork is for the tmate server side [4] (the
central party that relays SSH connections). Both forks originate from
the year 2016 and no sync seems to have happened since then. The upstream
author states that he doesn't backport fixes any more due to lack of time.

The tmate client actively connects to the tmate server side using an outgoing
SSH connection. This SSH session will be kept alive and a tmux terminal
session is established with some additional instructions shown on the screen
on how to attach to the tmux session via the network.

When other people connect to the tmate server using a regular SSH client and a
secret token as username they will be relayed to the tmate client's tmux
session and can fully access or view it (read-only mode).

[2]: https://github.com/tmux/tmux
[3]: https://github.com/tmate-io/tmate.git
[4]: https://github.com/tmate-io/tmate-ssh-server.git

2) Review Scope
===============

This review was performed for tmate client version 2.4.0. On the server side
the current Git development status of tmate-ssh-server as of commit befd49f4
was used. This is because the last tmate-ssh-server release (version 2.3.0) is
already over two years old, and a larger number of unreleased changes is found
on the project's master branch by now. Most of the statements and findings in
this report should also be true for the 2.3.0 server version release, however.

This review was focused on the general security architecture of the tmate
protocol and the network faced interfaces.

3) General Cryptographic Security
=================================

The tried and tested openssh security model is changed a lot in tmate. In
regular SSH the two parties establish a secure connection (secure against
eavesdropping), then verify each other. Verification means that the client
verifies the fingerprint of the public SSH server host key to make sure it is
the correct party it is talking to and no man in the middle is around. Then
the server requires authentication of the client as the requested login user
e.g. via tunneled cleartext password, public key or PAM module authentication
mechanisms etc.

In tmate we have three parties:

- A: the party that is sharing its terminal using the tmate client.
- B: the central tmate server that manages multiple tmate sessions and is
     typically publicly accessible in some form.
- C: the party that is connecting to a shared terminal using a regular SSH
     client.

The typical communication flow goes like this:

1. The tmate client (A) connects to to the tmate server (B). It verifies the
   server's fingerprint. Server hostname, port and fingerprint are configured
   via $HOME/.tmate.conf ("tmate-server-host", "tmate-server-port",
   "tmate-server-rsa-fingerprint").
2. The tmate server (B) performs no verification at all, because any tmate
   client is allowed to create a new session on it. It generates a random
   token consisting of 25 alphanumeric characters. This token uniquely
   identifies the new session and is communicated back to the tmate client (A).
3. The tmate client (A) displays the random token and the valid SSH URLs to
   attach to the shared terminal session. It is a command line like follows for
   full or read-only access respectively:
   ```
   ssh    T6PAFr59tsrfEWCaUZg8APCAe@...e-team-mate-server.org
   ssh ro-4Eu99VBssTnw9Q3LuYQUzcLEy@...e-team-mate-server.org
   ```
4. The secret token / command line now needs to be communicated to people
   that want to attach to the terminal. This step is critical, because it
   depends on the person that wants to share the terminal and how it shares the
   token with others.
5. The user (C) wanting to attach to the shared session now needs to connect
   to the tmate server (B) using the correct secret token to get access to the
   shared terminal. (C) will verify the fingerprint of (B), but (B) will not
   authenticate (C) beyond the knowledge of the secret token. The tmate server
   (B) will now relay data between (A) and (C).

So in contrast to the classical SSH setup the two endpoints (A) and (C) are
never verifying each other in any way, except via the secret token of 25
characters. So the host fingerprint verification and the user authentication
is all condensed into this secret token that needs to be forwarded from (C) to
(A) by some means. Any unintended party that gets hold of the secret token can:

- gain full or read-only access to the session shared by (A)
- replace the session shared by (A) by a malicious session and thus trick (C)
  (instead of a random token also an explicit fixed token can be used that
  overwrites existing sessions)

Furthermore the tmate-server (B) is a third party in this setup that needs to
be fully trusted by both (A) and (C). If (B) is compromised then all security
is gone.

To summarize, the security model used in tmate makes it easy for people to
share their terminals over SSH, but this simplicity may come with a false
sense of security, because the mechanisms used in the background are rather
complex and the important step 4 depends fully on how the person sharing its
terminal is treating the secret token.

4) Default Setup of tmate
=========================

When looking at a default installation of the tmate client (A) then it is very
simple to share a terminal via the default upstream server "ssh.tmate.io".
This upstream server name and its fingerprint are hard coded in the client in
source file "options-table.c". This means trust for the default upstream
server party (B) is builtin, a decision that should be left up to the user in
my opinion.

Thus it is enough to just type "tmate" in a shell to immediately share
terminal access with the upstream server and possibly give full control to it,
should it be compromised in some form.

I discussed this with the upstream developer and he agreed to make this an
opt-in but he is still considering options to maintain backward-compatibility
for existing users.

5) Code Quality and Design
==========================

Getting an overview of the critical code is not all that easy. The SSH logic
(based on libssh) feels crammed into the tmux code base. The dividing line
between original tmux and changed tmate code was difficult to find for me. In
some spots `#ifdef TMATE` sections are found, some additional source files are
sprinkled in the source directory.

Especially on the tmate-ssh-server side the tmux internals are basically
openly connected to the Internet and this raises the question whether the tmux
developers considered untrusted input in these areas very much.

The code paths accessible to unauthenticated users (and for the tmate-ssh
server party (B) all connections are unauthenticated) are pretty broad and
hard to follow during code review.

The network protocol logic which is based on libmsgpackc binary data items
does not enforce maximum string lengths which means that a variety of DoS
attacks are possible against party B.

The one (probably crucial) security measure taken by the tmate server source
code to protect itself is the following:

- each new session is forked from the master process
- each forked session is placed into a "jail" that consists of some separate
  namespaces and a chroot jail that runs as user "nobody" by default

It is time consuming to make sure that there are no major security issues in
this area reachable over the network. Fuzzing might be an approach to check
this more quickly, I did not go further in this direction, though.

If code execution could be achieved in a forked session process on party B)
then "only" a way out of the jail needs to be found to reach the same security
scope as all the other tmate sessions running on the same host.

6) Individual Findings
======================

These are all issues on the tmate-ssh-server code base running on party B).

a) Local security issues in /tmp/tmate (CVE-2021-44512, CVE-2021-44513)
-----------------------------------------------------------------------

The tmate-ssh-server maintains a world-writable directory in
/tmp/tmate/sessions into which UNIX domain sockets named after the secret
tokens are placed:

```
$ ls -ld /tmp/tmate
drwx-----x 4 tmate users  80  Sep 1 11:04 /tmp/tmate

$ ls -ld /tmp/tmate/sessions
drwx----wx 2 tmate users 120  Sep 1 11:33 /tmp/tmate/sessions/

$ ls -l /tmp/tmate/sessions
srw-rw---- 1 user users    0  Sep 1 11:05 aVMAvcCWupR3DTK7JF2NfxLeS
lrwxrwxrwx 1 user users   25  Sep 1 11:33 ro-4Eu99VBssTnw9Q3LuYQUzcLEy \
                                      -> T6PAFr59tsrfEWCaUZg8APCAe
lrwxrwxrwx 1 user users   25  Sep 1 11:05 ro-XjB7FchbDAjmMmNnXf2wry3bV \
                                      -> aVMAvcCWupR3DTK7JF2NfxLeS
srw-rw---- 1 user users    0  Sep 1 11:33 T6PAFr59tsrfEWCaUZg8APCAe
```

So this is how different sessions are maintained. If a file is a symlink then
it is considered a read-only session, otherwise read-write.

This setup is subject to a race condition (CVE-2021-44513):

- the creation of these directories is unsafe using `mkdir()` and `chmod()`
  system calls in `main()` found in source file "tmate-main.c". The code
  potentially reuses existing directories that belong to other users. Only the
  following `chmod()`s would fail if the owner does not match. This is a race
  condition, however, that could be won by using symlinks. In this case a
  local attacker could gain full control over this directory structure and thus
  create additional malicious sessions or get control of existing, legit
  sessions.

This setup uses too broad permissions in /tmp/tmate and /tmp/tmate/sessions
(CVE-2021-44512):

- since /tmp/tmate/sessions is world-writable a local malicious user can
  create arbitrary new files in there. For example a UNIX domain socket that
  reaches an attacker controlled local process instead of an actual tmate
  server session.
- although /tmp/tmate/sessions only allows 'wx' for other users this still
  allows to execute a `readlink()` system call on existing symbolic links in
  the directory. Thus a local compromised user that has knowledge of the
  secret token for a read-only session can find out the token for the
  read-write session and get full access.


Both problems are addressed by upstream commit 1c020d1f [5]. I recommended to
the upstream author to perform the ownership check first and only then perform
the `chmod()` calls, but this change did not happen yet.

[5]: https://github.com/tmate-io/tmate-ssh-server/commit/1c020d1f5ca462f5b150b46a027aaa1bbe3c9596

b) Denial-of-Service Attack Vectors
-----------------------------------

- there is no limit to the number of sessions that can be created on the tmate
  ssh server. A simple `ssh someone@...tmate-server -s tmate -p $tmate_port`
  is enough to establish a new session, get a new session token in
  /tmp/tmate/sessions and a forked session process. This can be done a lot of
  times in parallel to consume resources on the host.
- by sending overlong `HEADER` cilent identification, or overly big window
  dimensions via the `SYNC_LAYOUT` message memory exhaustion can be triggered
  on the host. This will usually "only" kill the forked session process but
  could also negatively impact availibility of the service to other sessions.
  Practically all string parameters in the network protocol have no size
  limit, I only outlined two prominent cases.
- I managed to cause a NULL pointer dereference by sending a bad `SYNC_LAYOUT`
  message, but triggering it is subject to a race condition for some reason.

### Reproducers

Attached is a simple Python script that allows to trigger some of the DoS
issues:

```
# this will not lead to an immediate OOM but shows the principle of sending
# overly long headers
$ tmate_dos.py --send-long-header

# this should lead to an immediate OOM by specifying overly large pane
# parameters
$ tmate_dos.py --allocate-gigantic-pane

# this often leads to a NULL pointer dereference (not fully reliable for
# some reason)
$ tmate_dos.py --send-bad-pane-id
```

### Fixes

Some of these aspects have been addressed by upstream commits [6], [7], [8].

The upstream author expressed that things like a limit on the number of
sessions should be covered by using cgroup limits or similar operating system
features. No such setup is available upstream yet though.

[6]: https://github.com/tmate-io/tmate-ssh-server/commit/36f073b4ccf05da2fd51cc10a2debb443c592c50
[7]: https://github.com/tmate-io/tmate-ssh-server/commit/b41672b634af4ec8797449e78e4b731e24e26e16
[8]: https://github.com/tmate-io/tmate-ssh-server/commit/1f314123df2bb29cb07427ed8663a81c8d9034fd

c) Miscellaneous Bits
---------------------

- the tmate-server only seems to support two SSH host key types "rsa" and
  "ed25519" and thus restricts the available cryptographic algorithms (for
  example no ECDSA).
- the handling of random data via `random_stream_init()` and
  `random_stream_get()` is a bit peculiar. It caches up to 256 bytes of random
  data in the process that could in theory be shared with child processes and
  thus multiple processes could use the same random data. Luckily the only client
  code currently *always* calls `random_stream_init()` and thus effectively no
  cached random data is ever used.
- file descriptors are not opened with `O_CLOEXEC` flag by default but are only
  later explicitly closed via `close_fds_except()`. Since there is no
  `execve()` taking place this is necessary, but having `O_CLOEXEC` to avoid
  accidental future inheritance to unrelated programs would be sensible, too.

7) Timeline
===========

2021-09-03: I reported the findings to the upstream author offering a maximum
            of 90 days coordinated disclosure.

2021-10-17: The upstream author published the fixes that are also referenced
            in this report. I tried to establish a clear publication date for
            the issues but did not get a clear answer.

2021-11-29: I remembered the upstream author about the maximum 90 days
            non-disclosure time approaching. I failed to get any concrete
            information on release plans or finalization of some aspects of
            the fixes.

2021-11-30: I requested CVEs from Mitre for the most pressing issues found in
            the report.

Cheers

Matthias

-- 
Matthias Gerstner <matthias.gerstner@...e.de>
Security Engineer
https://www.suse.com/security
Phone: +49 911 740 53 290
GPG Key ID: 0x14C405C971923553

SUSE Software Solutions Germany GmbH
HRB 36809, AG Nürnberg
Geschäftsführer: Ivo Totev

View attachment "tmate_dos.py" of type "text/x-python" (2151 bytes)

Download attachment "signature.asc" of type "application/pgp-signature" (834 bytes)

Powered by blists - more mailing lists

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.