authors | state |
---|---|
Brian Joerger ([email protected]) |
implemented |
X11 forwarding makes it possible to open a graphical application on a remote
machine and but forward its graphical (X) data to the client's local display.
RFC4254 details the
protocol for supporting X11 forwarding in an SSH server/client.
X11 forwarding is used by some SSH users who are switching over to Teleport. Currently, we only support X11 forwarding between an OpenSSH client and server in proxy recording mode. As users move more of their fleet to Teleport, there is a growing demand for supporting X11 between any combination of OpenSSH server/client and Teleport server/client.
X11 forwarding is a sparsely documented protocol with many hidden intricacies. In implementing X11 forwarding for Teleport I leaned heavily upon OpenSSH implementation, which is (was) the only complete open source X11 forwarding implementation.
This RFD will explain some key concepts and terms for X11 forwarding, the general flow of the implementation, and the security details and decisions.
There are also two sections describing the main deviations made from the OpenSSH implementation:
Unix sockets instead of TCP sockets
Untrusted as default mode
An X Server is an application on a machine which manages graphical displays, input devices, etc. Linux systems ship with an XServer (XOrg), while other operating systems require a standalone XServer (ex. XQuartz for Mac, XMing for Windows).
An X Client is a application which interacts with an X server to send/receive graphical input and output. For Max and Linux, most terminals have an X Client built in. On Windows, you have to install a standalone X Client such as MobaXTerm.
When an X Client makes an X request, it parses the set $DISPLAY
to send an X request
to a specific display opened in the X Server. the first portion of this request is
an authorization packet used to authenticate and authorize the X Client.
The X Client gets the authorization packet from $XAUTHORITY
or ~/.Xauthority
.
If there is no matching authorization data in the file, or it doesn't match what
the X Server expected, then the X request will be denied.
The X Security extension was created about 10 years after the 11th version of X. The
extension introduced untrusted
tokens which could be used to grant fewer X privileges
to the user of the token. It also introduced the ability to set a timeout for the token.
In contrast, normal trusted
tokens give full unmitigated X privileges, allowing for action
such as screenshots, monitoring peripherals, and many other actions one would expect to
perform on local machines. In the case of X11 forwarding however, this means that the
client is providing the SSH server unmitigated access to its local X Server. If the server
becomes compromised or another user has access to your remote user, then the Client's
local XServer could be subject to attacks such as keystroke monitoring to capture a password.
Unfortunately, since the X Security extension was created as an afterthought, it is not built into the X protocol and has some serious performance drawbacks. The performance drop is sometimes in the ballpark of 10x. Additional, on release many X Clients didn't even support the X Security Extension properly and would crash immediately or at some point while running. For example, if an X Program reads the user's clipboard and doesn't handle failure, then the program would crash when used with the security extension since clipboard is a privileged action.
Despite these issues, untrusted
forwarding is a very important addition to the x11
forwarding protocol and should not be overlooked by users, even in the face of performance
and compatibility issues. Additionally, these issues appear to have been improved in the
last two decades, though it is hard to ascertain the extent and breadth of the improvement.
For a more elaborate explanation of the potential dangers of trusted (and untrusted) x11 forwarding, give this article a look.
- Client opens an SSH session and then sends an
x11-req
ssh request to the server. Thex11-req
includes an X authentication protocol and cookie, which will be detailed in the security sections below. - Server receives request and either approves or denies the request.
- If the server approves, it will open an XServer proxy listener to catch any XServer requests within the SSH session.
- Upon receiving an XServer request on the proxy listener, the server sends an
x11
channel request to the client. - If the client accepts, the client will start X11 forwarding between the x11
channel and the client's local display -
$DISPLAY
. - Server begins X11 forwarding between the x11 channel and XServer proxy connection.
- The XServer request is now connected to the client's local display. For example, if
xeyes
was run, then the program would be running on the server, but any graphical input/output would the client's local XServer.
In step 3 above, the SSH server opens an X Server Proxy. More specifically, the server starts listening on a unix socket so that it can accept requests to the corresponding display.
First, the server has to find an open display socket. A display's unix socket can be
determined by its display number - /tmp/.X11-unix/X<display_number>
. In order to avoid
overlapping with any local display sockets, which start from display number 0, we start
searching for an open display number between 10 and 1000. Both of these numbers can be
configured up to the largest display number supported by the X Protocol - the max
Int32 (2147483647).
During session shell creation, we can set $DISPLAY
and X authorization data for the
session. The $DISPLAY
is set to unix:<display_number>
, which will be translated
to the open socket during an X Client request. X Authorization data will be set in
the user's default xauthfile (~/.Xauthority
) by calling
xauth add <$DISPLAY> <x11-req.proto> <x11-req.cookie>
.
In the OpenSSH implementation, The X Server Proxies are opened as tcp sockets, from
localhost:6010
to localhost:7009
. This net listener created by the SSH session
child process on the server, and served by the parent process which holds the remote
connection.
Due to the re-execution model of Teleport SSH sessions, the parent and child processes
cannot share the listener easily like this. Instead, we've opted to use unix sockets
so that the listener can be opened and served by the parent process, while allowing
the child process to perform a chown
call to become the owner of the socket afterwards.
This decision also comes with a couple additional benefits:
- There are many more unix display sockets available than tcp display sockets (65535 vs 2147483647)
- For this reason Teleport also allows the max display supported to be configurable, while OpenSSH only allows 1000.
- If a server is running both SSHD and Teleport with X11 forwarding, the sockets will not overlap.
There are four points of contact which concern security within the X11 forwarding flow above:
- Client sends
x11-req
to server to allow X11 forwarding during the Session. - Session user makes an X Client request to the SSH Server's proxy X Server.
- Server requests to open an
x11
channel to client to forward X Client request. - Client forward X Client request to local X Server.
The initial X11 forwarding request made by the client will be denied by the server in the following cases:
- The Server does not have X11 forwarding enabled in its
teleport.yaml
. - The Server does not have an X Authority configured, specifically the
xauth
binary. - The Client's SSH certificate does not have the
permit-X11-forwarding
extension.
If the request is authorized, then the Server will read the X Authorization protocol and
cookie attached in the request and set $DISPLAY
and ~/.Xauthority
as explained above.
Once X11 forwarding is enabled for a session, the Session user can make an X Client
request and start X11 forwarding. As explained in the X Server Proxy section, the
SSH Session's X Client requests will leverage the set $DISPLAY
and the X Authorization
data set in ~/.Xauthority
to connect to the X Server Proxy.
If a user attempts to send an X Client request for the $DISPLAY
with a different xauth
file, it will be denied. However it's important to note that any remote user that does
have access to ~/.Xauthority
will be able to perform X11 forwarding as if they were
in the session. This is where understanding trusted
vs untrusted
forwarding is
very important, which have their own sections below.
When the SSH client receives an x11
channel request, it must authenticate the server
before providing access to the client's local X Server. To do this, it will use the
X authorization data it sent in the original x11-req
Note: The X authorization data sent in the x11-req
is fake X authorization data
created by the server.
First, the client sends fake X authorization data to the server in the x11-req
request.
As detailed in the SSH RFC, this data includes an X authorization protocol and a random
cookie of that protocol. We use the MIT-MAGIC-COOKIE-1
protocol, which calls for a random
128 bit hexadecimal encoded cookie. This is the simplest and most common protocol.
When the client receives an x11
SSH channel request from the server, it mimics and
X Server by scanning the connection for its X authorization packet, the first portion
of the request. If the X authorization packet includes the protocol and cookie sent by
the client, then the server will be trusted.
Note: The X authorization data used in this step is fake and does not actually provide access to the client's XServer. It's essential that an XServer's X authorization data is never shared outside of the device, or else the XServer can be compromised.
To complete X11 forwarding, the client must start forwarding between the x11
channel
and the local X Server. However, the X authorization data in the x11
channel request
was the fake data we created. So before forwarding the X request to the X Server, the
SSH client replaces the fake data with real X authorization data. This data will either
be trusted xauth data, or untrusted xauth data, depending on what the client requested.
Untrusted X authorization data must be generated through xauth
in order to be recognized
by the X Server. To do this, the client runs xauth generate <$DISPLAY> untrusted <timeout?>
.
Any X Client request using this untrusted token will be be subject to the X Security extension,
granting it fewer privileges than usual.
In trusted X11 forwarding, we can rely on the client's local environment. If we fill in the X authorization packet in the request with a random cookie, the X Server will just ignore it and connect as if it received the request locally.
New tsh ssh
flags and ssh options:
-X
/--x11-untrusted
: requestsuntrusted
X11 forwarding for the session.-Y
/--x11-trusted
: requeststrusted
X11 forwarding for the session.--x11-untrusted-timeout=<duration>
: can be used with untrusted X11 forwarding to set a timeout, after which the xauth cookie will expire and the server will reject any new requests.
Additionally, we will support the following OpenSSH flags for user's who want the same UX as OpenSSH:
-oForwardX11Trusted=<yes/no>
: sets the X11 forwarding trust mode.- When set to
yes
, X11 forwarding will always be intrusted
mode, whether through the-X
,-Y
, or-oForwardX11=yes
flag. - When set to
no
, normal behavior will be used, meaning-X
and-Y
will lead tountrusted
andtrusted
forwarding respectively.-oForwardX11=yes
will lead tountrusted
forwarding.
- When set to
-oForwardX11=yes
: requests X11 forwarding for the session.- When used,
oForwardX11Trusted
will default toyes
unless explicitly set.
- When used,
-oForwardX11Timeout=<int>
: same effect as--x11-timeout
.
In OpenSSH, the X11 forwarding flags and options have very unintuitive UX. Essentially, in
many OpenSSH distributions, both -X
and -Y
will start X11 forwarding in trusted
mode,
unless the client explicitly sets ForwardX11Trusted=no
in their ssh client config. In my
experience and research, this leads to many users becoming confused, not knowing if they are
using trusted
or untrusted
, and having no idea what the difference is.
The reason for this strange UX behavior is that the X Security Extension had quite a contentious release. On top of the security benefits being confusing and unclear to many users, using the extension initially led to many X programs to perform poorly or even crash, as mentioned in the previous section.
It seems that this UX decision is ultimately the result of trying to include an incompatible yet important security fix without having any effect on existing users of X11 forwarding. In other words the X Security Extension is an opt-in feature in OpenSSH X11 forwarding as a result of its issues.
Two decades later, I believe we have no reason to follow the same UX and should instead learn from the mistakes of OpenSSH. As a security centered product, the security implications of a user's actions should be as clear and unambiguous as possible.
From the get-go, tsh ssh -X
and tsh ssh -Y
will always lead to untrusted
or trusted
forwarding respectively. If a user runs into an X program which still doesn't support the X
Security Extension, I would prefer that they experience the crash, come to customer support
or other sources to discover the reason, and personally decide whether to switch to trusted
forwarding or keep their X Server secure.
This does leave us with the unfortunate fact that the UX of tsh
and ssh
will be different
and may lead to some confusion for customers who are making the switch from ssh
to tsh
.
However, the resulting UX of tsh
should be intuitive and clear enough to guide the user
through this switch, or the user has the options to use the supported SSH flags to maintain
the familiar UX.