Lyte's Blog

Bad code, bad humour and bad hair.

SSH Agent Forwarding Is a Bug

At the very best Agent Forwarding is a feature that I want to make sure no one ever uses again… why?

It’s dangerous – you’re placing a socket that will happily answer cryptographic challenges that it shouldn’t be in the hands of unknown parties.

It’s only marginally easier than the alternative.

So what is it?

Well as usual the man page is fairly helpful here:

1
2
3
4
5
6
7
8
9
 -A      Enables forwarding of the authentication agent connection.  This can also
         be specified on a per-host basis in a configuration file.

         Agent forwarding should be enabled with caution.  Users with the ability
         to bypass file permissions on the remote host (for the agent's
         UNIX-domain socket) can access the local agent through the forwarded con‐
         nection.  An attacker cannot obtain key material from the agent, however
         they can perform operations on the keys that enable them to authenticate
         using the identities loaded into the agent.

The normal reason why you would want to forward your agent is because you’re connecting to some server via some other server (usually because a direct connection isn’t available).

The diagram below shows a fairly standard scenario where a DB server can’t be accessed via the public internet, but can be indirectly accessed via the web server:

What a lot of people do at this point is forward their key with something like:

1
2
3
yourlaptop$ ssh -A you@webserver
webserver$ ssh you@dbserver
dbserver$

Which is relatively simple (and can be simplified more so by turning on authentication agent forwarding in ssh_config), but it’s terrible unless you completely trust the security of your web server (please don’t do that).

Lets look at why it’s so terrible:

  1. Anyone else with sufficient access to the web server can use your forwarded agent to authenticate to anything you have access to.
  2. Most web servers have known vulnerabilities that you probably haven’t patched for yet.
  3. 1 + 2 = terrible

To be clear I’m not saying that we’re forwarding actual bits of key material to an untrustworthy host, just that we’re forwarding a connection to an agent that is able to answer authentication challenges to another host you may have access to. In practice one way attackers might use this is if they can see the environment variables your SSH connection came in via (either they have gained access to your account or root) then they could interrogate the SSH_AUTH_SOCK environment variable, add that in to their own environment and use your authentication agent to initiate authenticated connections to other hosts while you are still connected to the untrustworthy host.

So what’s the alternative?

ProxyCommand + nc.

ProxyCommand is a (seemingly little known) directive you can give to SSH to say “hey before you connect to where I’ve told you to, fire up a socket to something arbitrary first”. “nc” (or netcat) is something to give you an arbitrary connection.

Here’s an example (continuing on from above):

1
2
yourlaptop$ ssh -o 'ProxyCommand ssh you@webserver nc %h %p' you@dbserver
dbserver$

What this does is connects to webserver, authenticates using your key as you and then runs “nc” replacing in the hostname and port for the dbserver as it does. This will return a socket (running over the encrypted SSH connection) that can connect directly to the dbserver. SSH will then connect to the socket as you and authenticate with your key, but note that the encryption between you and the dbserver is end-to-end so you don’t need to make your authentication agent available in any potentially untrustworthy environments.

Ok that’s great and all but it’s too much to type

Put it in your ~/.ssh/config file, for the example above something like this should work:

1
2
Host dbserver
ProxyCommand ssh you@webserver nc %h %p

Once that’s in place you can just run:

1
2
yourlaptop$ ssh you$dbserver
dbserver$

What about a range of hosts?

The only real thing to watch out for here is that if you match a range of hosts and the host you’re routing via is in that matched range, SSH will quite happily fork bomb your machine:

1
2
3
4
5
# Be very careful to disable ProxyCommand on the host you are routing via or you will fork bomb yourself.
Host gateway.difficult.to.get.to
ProxyCommand none
Host *.difficult.to.get.to
ProxyCommand ssh you@gateway.difficult.to.get.to nc %h %p

What if I have to jump via multiple hosts?

Well firstly, redesign your network.

If for some reason that’s not a realistic option, you can just stack hops together:

1
2
3
4
5
6
7
Host hop2
ProxyCommand ssh you@hop1 nc %h %p
Host hop3
ProxyCommand ssh you@hop2 nc %h %p
Host hop4
ProxyCommand ssh you@hop3 nc %h %p
...

But it’s so slow…

In some circumstances doing this can be slower (e.g. when you spend all your time connected to an intermediate server connecting to other servers). This is because you’re now firing up multiple SSH connections each time you make a new connection instead of just one.

One pretty common work around for this is:

1
2
3
ControlMaster auto
ControlPath /home/you/.ssh/master-%r@%h:%p
ControlPersist yes

Run “man ssh_config” and search for “ControlMaster” to read up in more detail about this.

The TL;DR of it is – ControlMaster creates multiplexing magic to convert multiple connections down to one (meaning much less build-up / tear-down lag).

Similarity to onion routing

Using ssh like this is very similar to onion routing in that you have end-to-end encryption (and authentication!) by only trusting each mid-point to run up a socket (courtesy of netcat) for you. However please don’t assume that because the encryption going on here is similar to TOR that you can use it to alleviate the privacy concerns that TOR does.

Update: clarified that the agent and not the key is being forwarded, also explained a little more depth what that means.

Comments