It all started with an seemingly inocent ssh command :
ssh -o "ProxyCommand ssh -o 'ForwardAgent yes' B 'ssh-add && nc %h %p'" C
Understanding what that command actually did took me a whole day.
Primer on SSH protocol
SSH is protocol has 3 components :
- Transport layer (rfc 4253) : encrypts connection (typically TCP) and authenticates ssh server.
- After this step, the client can request to start a given service. Standard services ‘ssh-userauth’ and ‘ssh-connection’ are described below.
- Authentication protocol (rfc 4252) : authenticates ssh client on the remote machine.
- Transport layer (rfc 4254) : multiplexes authenticated user connection into channels to provide interactive shells, tunneling of TCP connections …
Simple tunneling
The following demonstrates how to tunnel both ways using SSH.
-T
do NOT create a pseudo terminal-N
do NOT run commands
### local_host ###
ssh -R 8888:localhost:8888 -L 8889:localhost:8889 -T -N remote_host &
nc -l localhost 8888 &
nc localhost 8889
### remote_host ###
nc -l localhost 8889 &
nc localhost 8888
Be careful it is a trap ! IPv6
nc -l 127.0.0.1
will listen only for IPv4 connectionsssh -R 8888:localhost:8888
may attempt to connect using IPv6
X11 server
The Xserver running on ssh client side must be invoked without
-nolisten
. Otherwise it will only accept connections on unix sockets.
This basically works using reverse tunneling. However, SSH must understand the X protocol to a certain degree.
- SSH will create a dummy Xauthority cookie on the server side.
- X protocol messages are forwarded to the client side, the cookie will be replaced by the real one.
- The real cookie will never go through the wire.
- SSH will set
DISPLAY
environment var on the remote shell.- X applications use environment var
DISPLAY
(hostname:display.screen
) to know which Xserver to connect to.
- X applications use environment var
ProxyCommand
Instead of using a TCP connection below SSH transport layer, the stdin/stdout of any program can be used. This is used to proxy ssh connections through a bastion host (or jump host) running on a different machine. 3 different ways to achieve the same technique :
ssh -o "ProxyCommand ssh -T bastion_host nc %h %p" remote_host
ssh -o "ProxyCommand ssh bastion_host -W %h:%p" remote_host
ssh -J bastion_host remote_host
(only on newer ssh program versions)
Security characteristics
- The proxy only replaces the TCP connection. SSH transport layer and user auth will be done on top of it.
- This means it will only see encrypted traffic flowing through it.
- No keys must reside on
bastion_host
since user auth will be done inlocal_host
.
Authentication with ssh-agent
SSH private keys should be encrypted with a passphrase. This way they are stored encrypted on disk.
ssh-agent
is a convinience program to keep the unencrypted keys in memory for a given duration.
The keys can later be used for several ssh sessions without user interaction.
The ssh client can contact the ssh-agent daemon via environment vars :
SSH_AUTH_SOCK
: unix socket file used to contact the agent.SSH_AGENT_PID
: PID of the current running agent.
ssh-agent
never transmits unencrypted keys to ssh clients. Instead it answers authentication challenges for them.
ForwardAgent
ssh-agent
traffic can be forwarded via reverse tunneling. This allows to keep all private keys on local_host
even when using several interactive ssh sessions to jump across machines.
This is NOT the same setup as for ProxyCommand.
Forward agent vulnerability
An attaquer with root access on remote_host_1
can use the unix socket to contact the local_host
agent.
This will allow him to impersonate any user connected to remote_host_1
using agent forwading.
Mixing ForwardAgent, ssh-add
and ProxyCommand
What does this command do ? ssh -o "ProxyCommand ssh -A remote_host_1 'ssh-add && nc %h %p'" remote_host_2
This can only work if keys are NOT passphrase protected. Otherwise ssh protocol wire data will contain garbage.
SOCKS proxying
SSH can act as a SOCKS proxy (-D
option). A SOCKS proxy acts as an OSI layer 5 gateway. It can forward any protocol above TCP (ex: HTTP).
Aside from the SOCKS headers that indicate where the connection should be forwarded to, the upper layers are opaque to the proxy. For example, a SOCKS proxy will only see encrypted HTTPS traffic flow by.
Example with HTTP
curl ifconfig.me
# output: 83.87.183.152
ssh -v -D 1080 -T -N remote_host
curl --socks5 localhost ifconfig.me
# output: 144.37.93.213
Note that the client application (here curl) MUST understand SOCKS. It needs to prepend the relevant SOCKS headers.