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).PGDATABASE01has access to an SMB server onHRSHARES(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:~$database_admin@pgdatabase01:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
4: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:50:56:9e:a1:85 brd ff:ff:ff:ff:ff:ff
inet 10.4.125.215/24 brd 10.4.125.255 scope global ens192
valid_lft forever preferred_lft forever
5: ens224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:50:56:9e:ab:53 brd ff:ff:ff:ff:ff:ff
inet 172.16.125.254/24 brd 172.16.125.255 scope global ens224
valid_lft forever preferred_lft forever
database_admin@pgdatabase01:~$ ip route
default via 10.4.125.254 dev ens192 proto static
10.4.125.0/24 dev ens192 proto kernel scope link src 10.4.125.215
172.16.125.0/24 dev ens224 proto kernel scope link src 172.16.125.254We found that PGDATABASE01 is on a 172.16.50.0/24 subnet. Without a port scanner, we used a Bash loop with nc to scan for hosts with port 445 open, discovering 172.16.50.217.
Instead of transferring files through multiple hosts (PGDATABASE01 to CONFLUENCE01 to Kali) , we can set up an SSH local port forwarding from CONFLUENCE01 to PGDATABASE01. A local port forward can be set up using OpenSSH's -L option, which takes two sockets in the format IPADDRESS:PORT separated with a colon as an argument:
The first socket is the listening socket that will be bound to the SSH client machine.
The second socket is where we want to forward the packets to.
The rest of the SSH command is as usual - pointed at the SSH server.
The
-Nflag prevents SSH from executing any remote commands, meaning we will only receive output related to our port forward.
From Kali, we can now list SMB shares.

Why Local Port Forwarding?
Service (SMB) isn’t directly reachable from Kali.
Service is reachable by
PGDATABASE01.Tunnel traffic through an SSH session on
CONFLUENCE01→PGDATABASE01→HRSHARES.
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:
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.
Why Dynamic Port Forwarding?
Dynamically access any host and port reachable by the SSH server without needing to forward each port individually.
Particularly useful in complex pivot scenarios where multiple services across subnets are in play.
For nmap via proxychains the -sT flag must be used; the default sudo scan (-sS) won't work.
-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.
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:
We compromise
CONFLUENCE01(192.168.125.63) using CVE-2022-26134.Our goal is to enumerate
PGDATABASE01's (10.4.125.215)5432port.A firewall blocks inbound connections.
The only port we can connect is
8090which forbids us from creating port forwards.However,
CONFLUENCE01has an SSH client and outbound SSH is allowed.We can SSH from
CONFLUENCE01to Kali and use remote port forwarding to tunnel database traffic.

Start an SSH server on Kali.
Create the remote port forwarding on CONFULENCE01.
This creates a tunnel where connections to 127.0.0.1:2345 on Kali are forwarded to PostgreSQL on PGDATABASE01 via the SSH connection from CONFLUENCE01.
Use psql on Kali to connect to 127.0.0.1:2345 as if we’re directly connected to the database — bypassing the firewall.

Why Remote Port Forwarding?
Dynamically access internal services from an external pivot point
Bypass inbound firewall rules that block direct connections
Maintain control by reversing the connection direction (outbound from compromised host)
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:
We have
CONFLUENCE01(192.168.125.63) but we can only talk to it on one port (8090).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.From
CONFLUENCE01, we SSH outward to our Kali machine.We create a SOCKS proxy on our Kali machine that listens on a port (let's say
9998).Any traffic sent to this SOCKS proxy, will fly back through the SSH tunnel to
CONFLUENCE01. ThenCONFLUENCE01will forward it to the final destination inside its network.
Start an SSH server on Kali.
sadasd
This creates a tunnel where connections to 127.0.0.1:9998 on Kali are forwarded to whichever destination we define on our command via the SSH connection from CONFLUENCE01.
Use psql on Kali to connect to 127.0.0.1:2345 as if we’re directly connected to the database — bypassing the firewall and nmap to scan MULTISERVER03.
Both remote port forwarding and remote dynamic port forwarding use the SSH -R option; the difference lies in the arguments supplied to -R:
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.
Last updated
Was this helpful?