What Is SSH Tunneling
SSH tunneling is a method of transporting arbitrary networking data over an encrypted SSH connection. … It also provides a way to secure the data traffic of any given application using port forwarding, basically tunneling any TCP/IP port over SSH.
SSH tunneling is amazing but can be confusing to understand and configure, especially when dealing with a reverse SSH tunnel. Recall that our goal in all of this is to run commands that we type in an SSH client on a remote SSH server. When using an SSH tunnel, instead of typed commands being sent to the server, network traffic is sent packaged inside of SSH and then unpackaged and delivered by the SSH server.
Imagine that you’re in the following situation: You have remote access
to an SSH server on an internal network, but you want access to the web
server on the same network. You can’t access the web server directly, but
the server with SSH installed does have access and the SSH server doesn’t
have the tools you want to use installed on it.
One way to overcome this problem is to set up a forward SSH tunnel. Without getting into too much detail, running the command ssh -L
8008:web:80 thedarktech@sshserver will connect to the ssh server as the user thedarktech and set up port 8008 on your local system. Anything sent to port 8008 will be sent down the existing SSH tunnel to the SSH server and delivered to the web server
That’s pretty cool, but recall that not many Windows systems are
running an SSH server service. Not all is lost, though. We can configure
a reverse SSH tunnelling connection. In this case, we connect to our
own SSH server from the Windows client in the usual fashion. Through
that SSH connection, we also specify a remote port on the SSH server that
will be tunnelled to the local host and port.
local host and port can be used, for example, to expose port 3389 to access an internal system using remote desktop, or to another system that the Windows client can access (like the web server in our example).
The Paramiko demo files include a file called rforward.py that does exactly this. It works perfectly as is so I won’t just reprint that file, but I will point out a couple of important points and run through an example of how to use it. Open rforward.py, skip down to main(), and follow along.
def main(): options, server, remote = parse_options() password = None if options.readpass: password = getpass.getpass('Enter SSH password: ') client = paramiko.SSHClient() client.load_system_host_keys() client.set_missing_host_key_policy(paramiko.WarningPolicy()) verbose('Connecting to ssh host %s:%d ...' % (server, server)) try: client.connect(server, server, username=options.user, key_filename=options.keyfile, look_for_keys=options.look_for_keys, password=password) except Exception as e: print('*** Failed to connect to %s:%d: %r' % (server, server, e)) sys.exit(1) verbose('Now forwarding remote port %d to %s:%d ...' % (options.port, remote, remote))
The few lines at the top double-check to make sure all the necessary
arguments are passed to the script before setting up the Parmakio SSH cli-
ent connection (which should look very familiar).
try: reverse_forward_tunnel(options.port, remote, remote, client.get_transport()) except KeyboardInterrupt: print('C-c: Port forwarding stopped.') sys.exit(0)
The final section in main() calls the reverse_forward_tunnel function.
Let’s have a look at that function.
def reverse_forward_tunnel(server_port, remote_host, remote_port, transport): transport.request_port_forward('', server_port) while True: chan = transport.accept(1000) if chan is None: continue
In Paramiko, there are two main communication methods: transport, which is responsible for making and maintaining the encrypted connection, and channel, which acts like a sock for sending and receiving data over the encrypted transport session. Here we start to use Paramiko’s request_port_forward to forward TCP connections from a port on the SSH server and start up a new transport channel.
thr = threading.Thread(target=handler, args=(chan, remote_host,remote_port)) thr.setDaemon(True) thr.start()
Then, over the channel, we call the function handler.
But we’re not done yet.
def handler(chan, host, port): sock = socket.socket() try: sock.connect((host, port)) except Exception as e: verbose('Forwarding request to %s:%d failed: %r' % (host, port, e)) return verbose('Connected! Tunnel open %r -> %r -> %r' % (chan.origin_addr, chan.getpeername(), (host, port))) while True: r, w, x = select.select([sock, chan], , ) if sock in r: data = sock.recv(1024) if len(data) == 0: break chan.send(data) if chan in r: data = chan.recv(1024) if len(data) == 0: break sock.send(data) chan.close() sock.close() verbose('Tunnel closed from %r' % (chan.origin_addr,))
And finally, the data is sent and received.
Let’s give it a try.
Let’s Check Our Code Then
We will run rforward.py from our Windows system and configure it to be the middle man as we tunnel traffic from a web server to our Kali SSH server.
C:\tmp\demos>rforward.py 192.168.100.133 -p 8080 -r 192.168.100.128:80 --user thedarktech --password Enter SSH password: Connecting to ssh host 192.168.100.133:22 ... C:\Python27\lib\site-packages\paramiko\client.py:517: UserWarning: Unknown ssh-r sa host key for 192.168.100.133: cb28bb4e3ec68e2af4847a427f08aa8b (key.get_name(), hostname, hexlify(key.get_fingerprint()))) Now forwarding remote port 8080 to 192.168.100.128:80 ...
You can see that on the Windows machine, I made a connection to the SSH server at 192.168.100.133 and opened port 8080 on that server, which will forward traffic to 192.168.100.128 port 80. So now if I browse to on my Linux server, I connect to the web server at 192.168.100.128 through the SSH tunnel, as shown in Figure
If you flip back to the Windows machine, you can also see the connec-
tion being made in Paramiko:
Connected! Tunnel open (u'127.0.0.1', 54537) -> ('192.168.100.133', 22) -> ('192.168.100.128', 80)
SSH and SSH tunnelling are important to understand and use. Knowing when and how to SSH and SSH tunnel is an important skill for black hats, and Paramiko makes it possible to add SSH capabilities to your existing Python tools.
We’ve created some very simple yet very useful tools in this chapter. I encourage you to expand and modify as necessary. The main goal is to develop a firm grasp of using Python networking to create tools that you can use during penetration tests, post-exploitation, or while bug-hunting. Let’s move on to using raw sockets and performing network sniffing, and then we’ll combine the two to create a pure Python host discovery scanner.