Back to All Modules

SSH Port Forwarding (Advanced)

#Overview

SSH port forwarding tunnels individual ports through an SSH connection. This section covers advanced SSH forwarding beyond the basic -L and -R flags, including ProxyCommand, ProxyJump, and persistent tunneling with autossh.

#Local Forwarding (-L) Advanced

#Multiple Forwards in One Session

# Forward multiple ports through a single SSH connection
ssh -L 8080:172.16.0.10:80 -L 3306:172.16.0.20:3306 -L 6379:172.16.0.30:6379 user@pivot

# Bind to all interfaces (not just localhost)
ssh -L 0.0.0.0:8080:172.16.0.10:80 user@pivot

# Bind to specific interface
ssh -L 192.168.1.50:8080:172.16.0.10:80 user@pivot
BASH

#GatewayPorts

# By default, remote forwards only bind to localhost on the SSH server
# Enable gateway ports to allow remote hosts to connect to your forwarded port
ssh -R 0.0.0.0:8080:127.0.0.1:80 user@pivot

# On the SSH server, edit /etc/ssh/sshd_config:
GatewayPorts yes    # or clientspecified

# Restart SSH
sudo systemctl restart sshd
BASH

#Remote Forwarding (-R) Advanced

#Reverse SOCKS Proxy via SSH

# Create a reverse SOCKS proxy on the pivot host
ssh -R 1080 user@pivot

# On the pivot, any user can use the SOCKS proxy:
# curl -x socks5://127.0.0.1:1080 http://internal-service/
BASH

#Reverse Port Forward for Callbacks

# Forward pivot's port 8080 back to attacker's local port 80
ssh -R 8080:127.0.0.1:80 user@pivot

# Now on the pivot: http://127.0.0.1:8080 reaches attacker's port 80
# Useful for serving payloads or receiving reverse shells through the pivot
BASH

#SSH ProxyCommand

ProxyCommand lets you tunnel SSH connections through an intermediate host without requiring a direct route. It's the building block for multi-hop SSH chains.

# Connect to target through pivot, without ProxyJump
ssh -o ProxyCommand="ssh -W %h:%p user@pivot" user@target

# How it works:
# 1. SSH connects to pivot
# 2. Runs "ssh -W target:22" on pivot (netcat mode)
# 3. SSH session to target flows through that connection

# With netcat instead of SSH (when pivot doesn't have SSH client)
ssh -o ProxyCommand="ssh user@pivot nc %h %p" user@target

# With socat (more reliable than nc)
ssh -o ProxyCommand="ssh user@pivot socat - %h:%p" user@target
BASH

#ProxyCommand with SSH Config

# ~/.ssh/config
Host target
    HostName 172.16.0.50
    User admin
    ProxyCommand ssh -W %h:%p pivot

Host pivot
    HostName 10.10.10.10
    User ubuntu
    IdentityFile ~/.ssh/pivot_key

# Now simply:
ssh target
# Automatically tunnels through pivot
BASH

#SSH ProxyJump (-J)

ProxyJump is the modern, simplified replacement for ProxyCommand. Available since OpenSSH 7.3.

# Single jump
ssh -J user@pivot user@target

# Multiple jumps (chain through 2+ pivots)
ssh -J user@pivot1,user@pivot2 user@target

# With SSH config
Host target
    HostName 172.16.0.50
    ProxyJump pivot1,pivot2

# Now:
ssh target
BASH

#ProxyJump with Dynamic Forwarding

# Jump through pivot AND create SOCKS proxy
ssh -J user@pivot -D 1080 user@target

# Jump through pivot, forward a port from beyond target
ssh -J user@pivot -L 8080:192.168.1.10:80 user@target
BASH

#autossh (Persistent Tunnels)

autossh automatically restarts SSH sessions and port forwarding when they drop. Essential for long-running tunnels during multi-day engagements.

# Install
apt install autossh

# Basic persistent local forward
autossh -M 0 -f -N -L 8080:172.16.0.10:80 user@pivot

# -M 0: Disable monitoring port (use ServerAliveInterval instead)
# -f: Run in background
# -N: No remote command

# With keepalive (recommended)
autossh -M 0 -f -N \
  -o "ServerAliveInterval 30" \
  -o "ServerAliveCountMax 3" \
  -L 8080:172.16.0.10:80 \
  user@pivot

# Persistent reverse forward
autossh -M 0 -f -N \
  -o "ServerAliveInterval 30" \
  -R 8080:127.0.0.1:80 \
  user@pivot

# Persistent SOCKS proxy
autossh -M 0 -f -N \
  -o "ServerAliveInterval 30" \
  -D 1080 \
  user@pivot

# As systemd service (survives reboot)
cat > /etc/systemd/system/ssh-tunnel.service << EOF
[Unit]
Description=SSH Tunnel
After=network.target

[Service]
User=operator
ExecStart=/usr/bin/autossh -M 0 -N -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -L 8080:172.16.0.10:80 user@pivot
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl enable ssh-tunnel
sudo systemctl start ssh-tunnel
BASH

#Windows SSH Forwarding

#OpenSSH (Windows 10+)

# OpenSSH is built into Windows 10/11 and Server 2019+
# Local forward
ssh -L 8080:172.16.0.10:80 user@pivot

# Remote forward
ssh -R 8080:127.0.0.1:80 user@pivot

# Dynamic forward (SOCKS)
ssh -D 1080 user@pivot
POWERSHELL

#Plink (PuTTY)

# Local forward
plink.exe -L 8080:172.16.0.10:80 user@pivot

# Remote forward
plink.exe -R 8080:127.0.0.1:80 user@pivot

# With password (non-interactive)
plink.exe -pw password -R 8080:127.0.0.1:80 user@pivot

# Dynamic forward (SOCKS)
plink.exe -D 1080 user@pivot
CMD

#Common Pitfalls

  1. GatewayPorts disabled: By default, -R forwards only bind to 127.0.0.1 on the server. Other hosts can't reach the forwarded port. Enable GatewayPorts yes or clientspecified in sshd_config.
  2. Connection drops: SSH tunnels die when the SSH session drops. Use autossh for persistence.
  3. Port conflicts: If the local or remote port is already in use, SSH fails silently. Check with netstat -tlnp or ss -tlnp.
  4. Firewall blocks SSH: If port 22 outbound is blocked, try SSH over port 443 or use Chisel/Ligolo instead.
  5. ProxyJump version: ProxyJump requires OpenSSH 7.3+. Use ProxyCommand on older systems.

#OPSEC Considerations

  • SSH tunnels show up in netstat and ss output as established connections
  • GatewayPorts yes in sshd_config is a detectable configuration change
  • autossh processes are visible in process listings
  • SSH config files in ~/.ssh/config are artifacts that persist
  • Plink.exe is a well-known red team tool that may trigger AV

#Cross-References