Saturday, September 13, 2008

SSH ProxyCommand without netcat

The ProxyCommand is very useful when hosts are only indirectly accessible. With netcat it is relative strait forward:
ProxyCommand ssh {gw} netcat -w 1 {host} 22

Here {gw }and {host} are placeholders for the gateway and the host.

But it is also possible when netcat is not installed on the gateway:
ProxyCommand ssh {gw} 'exec 3<>/dev/tcp/{host}/22; cat <&3 & cat >&3;kill $!'

The /dev/tcp is a built-in feature of standard bash. The files don't exist. To check whether bash has this feature built-in use run cat < /dev/tcp/google.com/80 on the gateway. To make sure that bash is used, use:
ProxyCommand ssh {gw} "/bin/bash -c 'exec 3<>/dev/tcp/{host}/22; cat <&3 & cat >&3;kill $!'"

And it even works together with ControlMaster.

(Updated on Oct 22 to include kill to clean up background cat)
(Updated on Mar 3 2011 to make placeholders more clear and explain /dev/tcp)

14 comments:

Robert de Bock said...

That is one great trick, I'll try this one on different UNIX/Linux systems! Thanks for this hint!

Anonymous said...

Not bad the idea, but for me that leaves one idle cat around after teminating the connection.

Roland said...

Yes, it leaves one cat around. Didn't worry about it. Not sure how one could prevent that.

Anonymous said...

How about closing the file descriptor #3 after everything was done? Will this work? --
ProxyCommand ssh {gw} 'exec 3<>/dev/tcp/{host}/22;(cat <&3 & );cat >&3; exec 3>&-;'

Roland said...

Closeing the file descriptor doesn't exit the background cat for me. But your comment made me realized one can just kill the background cat after the connection is finished. Thanks. I updated it in the article.

placidrage said...

I am sorry to bump up an old post, I am trying your solution with a slightly different approach, but doesn't seem to work and it's mainly because I don't think I understand the proxy command line correctly.

What's the {gw} string? and is the {host} the same as the %h variable?

And on a second note /dev/tcp directory doesn't exist either on my local or remote machine during an ssh connection.

Is it possible to explain the expression with the exec:
exec 3<>/dev/tcp/{host}/22

My setup looks something like this:

Host proxy
Hostname proxy.server.com
User myuser
DynamicForward localhost:3128

Host *.server.com
ProxyCommand /usr/bin/nc -x localhost:3128 -Pmyseconduser %h %p

In a perfect world, I'd ssh to proxy, then ssh in another console to the dev.server.com, and use proxy to browse the same server on ports 80 and 3000

The problem is that proxy doesn't have netcat installed. so no nc love there.

Any help that allows me to rewrite this trick and adapt it to my context would be appreciated.

Roland Schulz said...

I updated the post to make it more clear. Yes you can use %h if that is the valid hostname. gw is the gateway you use as intermediary machine. And you need to make sure you use bash with /dev/tcp enabled.

Brooks said...

I came across this having the problem of no netcat on the gateway, but unfortunately it has no /dev/tcp either.

I did nonetheless eventually find a solution! See, there _was_ a copy of netcat on the final target, and I could use that with an extra ssh hop:

ProxyCommand ssh {gw} ssh {host} netcat -w1 {host} 22.

It does end up with an extraneous loopback from the target to itself, but otherwise seems to work.

Oliver said...

Hi,

Great article. I ran into some problems though you might to be able to help with.

I'm trying to multihop over 2 gateways to my target using your method (netcat isn't on any of the gateways) using the following config

Host gateway1
HostName gateway1.org
User john

Host gateway2
HostName gateway2.org
User jack
ProxyCommand ssh gateway1.org 'exec 3<>/dev/tcp/gateway2.org/22; cat <&3 & cat >&3;kill $!'

Host target
HostName target.org
User jill
ProxyCommand ssh gateway2 'exec 3<>/dev/tcp/target.org/22; cat <&3 & cat >&3;kill $!'

I have problems connecting to gateway2

$ ssh gateway2
jack@gateway1.org's password:

I type in the password and always get `Permission denied, please try again.`. After the third try I get ( Permission denied (publickey,password,keyboard-interactive). ).

The thing is that I have configured gateway1 to use passwordless authentication to jack@gateway2.org and it works if I ssh into gw1 and from there into gw2.

Do you have a clue what is going on?

Oliver said...

I was able to solve my problem by adding the key from localhost to gw1's and gw2's authenticated_keys file.

Now I got my multihop setting working I see some strange fingerprints popping up.

If I use the ProxyCommand to ssh into gw2 for example, the rsa fingerprint is different. The same goes if I ssh via ProxyCommand into target.

Does anybody know why that is the case? Should I worry?

Roland Schulz said...

SSH should check the host-key for each hop and for the final destination. Each one should match. I think you either get confused that the host-key is for the gateway/destination while it is really for the other, or it is really incorrect and you should be worried.

Oliver said...

Thanks for the reply. I got no answer on the OpenSSH mailing list. It's not even maintained for over half a year.

Your reply prompted me to recheck my findings. And my error was to "only" check for
ssh-keygen -lf /etc/ssh/*
but not for
ssh-keygen -lf /etc/ssh58/*
which was the active installation. Everything looks legit and I learned a lot about ssh.

Thanks!

Ottxor said...

I recently had the case, where the gateway ran a restricted shell, where only "ssh" and "exit" were allowed.

But with OpenSSH-5.4 and later one can use:

ProxyCommand ssh -W {host}:%p ${gw}

Matt Saunders said...

Thanks, this is an awesome trick!