Hacking Linux Exposed





previous article
next article
SSH Bouncing - How to get through firewalls easily, Part 2.
By Bri Hatch.

Summary: Often you'll have firewalls or other network equipment that doesn't allow direct SSH access to machines behind it. Using a bit of trickery, you can get through without seemingly jumping through any hoops.

Last time I showed you a trick to seamlessly SSH 'through' firewalls or other devices that don't allow direct SSH access to the machines behind them, by 'bouncing' off that device. It requires that you have SSH access to the intermediate host, and what happens is that you set up your ~/.ssh/config to use a ProxyCommand. This ProxyCommand makes an SSH connection to the middleman host and runs netcat as follows to get you connected to the real SSH server:

  nc -w 1 target_host 22

My goal was to be able to be at a command prompt and type 'ssh hostname' and have it magically reach the endpoint.

Many people pointed out that I could have first established an SSH connection to the intermediate host with an SSH forward set up, and then ssh to the local SSH forward port. While I've used that system many times before, I prefer the netcat solution I detailed last time. If you want to know why or how the portforward version works, see the bottom of this article.

So, let's optimise and secure our setup. Right now, you need to have shell access to the middle-man machine, and the ability to run any command you want. Plus, you either need to type the password for it, or you need to have key-based trust enabled.

What I'd prefer is that you can execute the netcat command and only the netcat command, nothing else. The easiest way to do that is to use key-based authentication, utilising the ability for the SSH server to force a command to run when that key is used. If you look back at my previous articles, you'll see how to create SSH PubKeys/Identities, including how to use the 'command=' option.

So let's take a look at my $HOME/.ssh/authorized_keys file on the firewall:

  $ grep ncssh $HOME/.ssh/authorized_keys
  command="/home/bri/bin/ncssh" ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAp8
  kJbrWjIF0PIpSVdm3aqGSMOgQ7Pm1X87iz2nV1uQV4hMt06= ncssh-proxy-key

You'll note the command="/home/bri/bin/ncssh" line precedes the public key itself - this forces the /home/bri/bin/ncssh program to run when this key is used for authentication, regardless what the user wanted to do. Assuming your key (on your desktop) doesn't have a passphrase, you'll be able to log into this machine without a password, and the ncssh program will run automatically.

Here's the ncssh program:

  # ncssh
  # Server-side program to allow clients to run ncssh-proxy
  # or simply 'ssh ... nc -w 1 ipaddr 22' and 'bounce' off
  # this host.
  # BUGS:
  #   Only allows IP addresses.  If that annoys you,
  #   change $IPADDR pattern below.
  #   Ditto for the destination port.
  # Copyright 2003,2004 Bri Hatch 
  # Released under the GPL
  # Likely paths for netcat - let's be paranoid and not
  # fall back on just any old thing we find.
  # Bonus points to anyone who knows why /usr/local/lib/pingers is in there.
  my @nc = qw( /usr/bin/nc /usr/local/bin/nc /usr/local/lib/pingers/nc );
  my $nc;
  for my $bin ( @nc ) {
          $nc = $bin if -x $bin
  my $IPADDR = '\d+\.\d+\.\d+\.\d+';
  my $SSHPORT = '22';
  # Change '22' to '\d+' if you want to allow any
  # destination port, not just port 22.
  # The original command (nc -w 1 ip.ad.dr.es 22) is stored by
  # the SSH server in the environment variable SSH_ORIGINAL_COMMAND.
  # If command is just the IP address to which it should connect
  if ( $orig =~ /^ \s* $IPADDR \s* $/x ) {
          $ipaddr = $orig;
  # If command is a 'nc -w ... host 22' style command
  } elsif ( $orig =~ /^ \S* (netcat|nc) \s .*? ($IPADDR) \s $SSHPORT \s* $/x ) {
          $ipaddr = $2;
  # Time to actually run netcat
  if ( $ipaddr ) {
          $nc or print STDERR "No netcat found\n" and exit 1;
          exec $nc, '-w', '1' , $ipaddr, 22;
          die "Unable to exec netcat '$nc': $? $!";
  print STDERR "You're not allowed to execute '$orig'\n";
  exit 1;

This program is very simple[1] -- it takes the original command (supplied by the ssh client) and determines where the IP address of the target is within it. By accepting commands of the form 'nc ... ip.ad.dr.es 22', it should work unchanged with any ProxyCommand you already have, meaning you can just drop it in and your intermediate host will be more paranoid instantly.[2]

Your $HOME/.ssh/config can still look exactly the same as before, as seen here:

  $ head $HOME/.ssh/config
  Host machine1
  Hostname machine1
  HostKeyAlias machine1
  Identity /path/to/ncssh-proxy-key
  ProxyCommand netcat-proxy-command firewall.my_network.com
  Host machine2
  Hostname machine2
  HostKeyAlias machine2
  Identity /path/to/ncssh-proxy-key
  ProxyCommand netcat-proxy-command firewall.my_network.com

(This configuration uses the netcat-proxy-command script shown last time.)

So, using this system, you have the following situation:

  • Client can type 'ssh machinename' and not worry about any of the underlying magic.
  • Local $HOME/.ssh/config file details the middleman and destination, listing the identity to use (if necessary), and causing the netcat-proxy-command to run.
  • Local netcat-proxy-command is responsible for SSHing to the middleman and running netcat.
  • Server's authorized_keys file forces the ncssh command to run, preventing anyone from actually accessing the server's shell account at all.
  • Server's ncssh program verifies supplied IP address and makes actual connection to the target.

All told, a paranoid and yet functional solution.

Next time we'll do one more enhancement - make it possible to have any user on the client machine get access to the Identity/PubKey that's necessary to bounce off the middleman. This will allow anyone to log into this machine, and without setting up any keys or configuration, log into the target machines transparently.

My response to the LocalForward Solution

It's possible to use an SSH LocalForward to tunnel through the firewall device. You run ssh twice, once to set up the tunnel, and once to connect to the server behind it:

    $ ssh -f intermediatehost -L 9999:destinationhost:22 sleep +1d
    $ ssh localhost -p 9999

This is a completely legitimate way to do it. The first command logs into intermediatehost, and sets up a LocalForward using the -L option. /usr/bin/ssh binds local port 9999, and when you connect to this port it gets tunnelled inside the SSH connection to destinationhost's port 22. The next ssh line connects to that port, and you're hitting the target server. Naturally you need HostKeyAlias commands to keep the keys happy, as is the case with my netcat solution.

What's the problem with using the forwarding method? Well, it requires you run the tunnel command first, or set up a daemon to keep it running all the time. You don't get the benefit of simplicity - running one command that contains all the setup that's necessary.

Want a free copy of Hacking Linux Exposed, Third Edition?

HLEv3 doesn't exist yet. We haven't written it. But it's time to get started. Do you have anything new you'd like to see covered in more detail, or removed entirely? Anything new and interesting you'd like to see?

If you have ideas you'd like to share, email me[3]. I'll send out a copy of the book, once it's down in dead-tree format, to one lucky person picked at random from the useful suggestions.


[1] Simple, and well commented, I hope.

[2] I had a few folks complain that I should not assume your SSH server runs on port 22, so please change the $SSHPORT value in the script as appropriate.

[3] Send me email directly at bri@hackinglinuxexposed.com, don't reply to this mailing list.

Bri Hatch is Chief Hacker at Onsight, Inc and author of Hacking Linux Exposed and Building Linux VPNs. He's simply too tired to write anything interesting down here right now. Bri can be reached at bri@hackinglinuxexposed.com.

Copyright Bri Hatch, 2004

This is the September 23, 2004 issue of the Linux Security: Tips, Tricks, and Hackery newsletter. If you wish to subscribe, visit http://lists.onsight.com/ or send email to Linux_Security-request@lists.onsight.com.

previous article
next article