The Hitchhiker's Guide to Pivoting and Detecting It on the Range

The operator's walkthrough for Phase 03. The WARMUP explains the concepts (the network layers, DNS, the transports, recon, scanning, pivoting-as-graph, segmentation, egress filtering, and the telemetry that catches each). This guide shows you how to do it on your own authorized, isolated range: map the topology, establish a pivot through a multi-homed host, stand up a SOCKS proxy, observe the netflow and DNS the pivot generates, then add a **segmentation ACL

  • egress allow-list that breaks it**, and write the Zeek/Suricata detection. Then tear it down.

Authorization is the whole job. Everything here happens on the owned range from the README range diagram, under the Phase 00 Rules of Engagement, with stop conditions and a deconfliction contact named before anything runs. If you cannot name the owner, scope, stop conditions, and deconfliction contact for a step, you do not run it. Nothing here is a live target, a payload, or a weapon — and the labs themselves reason over synthetic metadata only. You build a pivot here to watch its telemetry, never to reach anything you do not own.


0. The loop you are about to run

   map topology  →  pick the multi-homed pivot  →  establish the pivot + SOCKS proxy
        │                                                      │
        ▼                                                      ▼
   plan it first (Lab 01: path + choke point)  ──────►  OBSERVE netflow + DNS the pivot makes
        │                                                      │
        ▼                                                      ▼
   add segmentation ACL + egress allow-list (Lab 02)  →  PROVE the path is broken (re-probe)
        │                                                      │
        ▼                                                      ▼
   write Zeek/Suricata detection  →  evidence packet  →  TEARDOWN (revert snapshots)

This is one pass of network-attack-and-defense over the same graph: plan the pivot, build it safely on owned hosts, watch what it emits, segment the choke point so it breaks, and leave behind the detection that would have caught it.


1. Stand up the authorized range

Use the isolated range from the README. The minimum for this phase is three small segments and a multi-homed pivot host that bridges two of them:

Range zoneHostsRole in this phase
operatora Linux box you ownyour attack box; runs the SOCKS client / proxychains
edge / DMZone multi-homed Linux host (two NICs)the pivot — reachable from operator, also on the internal segment
internalone or two hosts (a "file server", a "db")the segment only the pivot can reach
detectiona sensor box: Zeek + Suricata + a netflow collector (e.g. nfdump/fprobe)collects telemetry on the span port; where you write detections
 [operator]───(edge segment)───[ PIVOT (multi-homed) ]───(internal segment)───[fileserver] [db]
      │                                  │                                          │
      └──────────────── span/mirror to [detection: Zeek + Suricata + netflow] ──────┘

Hard preconditions (Phase 00):

  • The range is isolated — no bridge to home, corporate, or public networks. The "internet" zone is a fake/owned sink, never the real internet.
  • Snapshot every host before the run; you will revert after.
  • No real credentials, customer data, or personal data in any host, capture, or screenshot.
  • The deconfliction contact and stop conditions are written into the run sheet before step 2.
  • The only reachability you exercise is between owned range hosts.

Verify the sensor is collecting before you attack. Generate a benign marker and confirm it lands:

operator → make one ordinary connection to the pivot (e.g. a single SSH/HTTP request)
detection → confirm Zeek conn.log has that connection and the netflow collector recorded the flow

If the marker does not appear, fix collection first. A blind sensor produces a false "no detection."


2. Map the topology (and plan it with Lab 01 first)

Plan before you touch anything. Express the range as a Lab-01 graph and compute, on paper / in the planner, the reachable set from the operator box, the cheapest path to the db, and the choke point. For this range the choke point is obvious — the multi-homed pivot is the only edge into the internal segment — and that is exactly the point: the planner predicts what you are about to demonstrate.

# Lab-01 model of the range (synthetic — this is planning, not scanning):
g = Graph.of([
    Edge("operator", "pivot",      cost=1, via="ssh-foothold"),
    Edge("pivot",    "fileserver", cost=2, via="socks-proxy"),
    Edge("pivot",    "db",         cost=2, via="socks-proxy"),
])
reachable(g, "operator")            # {operator, pivot, fileserver, db}
shortest_path(g, "operator", "db")  # (('operator','pivot','db'), 3)
choke_points(g, "operator", "db")   # ('pivot',)  ← the host you will segment in step 5

Then confirm reachability on the range — within scope. From the operator box, an authorized reachability check (a narrow port probe to the owned pivot only) confirms the operator → pivot edge. You do not scan beyond scope; the internal segment is confirmed through the pivot in the next step, which is the whole demonstration.

Expected observation: the reachability check itself shows up in netflow as a small fan-out from the operator box. Note it — this is the scan-detection signal from WARMUP Chapter 5, in miniature.


3. Establish the pivot and a SOCKS proxy

On the owned pivot host, with a foothold you legitimately have (your own SSH key on your own box), stand up a dynamic SOCKS proxy so the operator box inherits the pivot's edges. The canonical, no-extra-tooling way is SSH dynamic forwarding from the operator box:

# On the operator box — dynamic (SOCKS) forward THROUGH the owned pivot:
ssh -D 1080 -N youruser@pivot        # 1080 is now a local SOCKS proxy routed via the pivot
# Now route a tool through it (authorized internal targets only):
proxychains4 curl http://fileserver/   # the connection originates from the PIVOT, not the operator box

What just happened, in graph terms: the operator box now has the pivot's outgoing edges (pivot → fileserver, pivot → db) — exactly the Lab-01 model. (chisel or ligolo-ng would build the same reachability over an HTTP/TUN channel when SSH is not available; the reachability is identical, which is why Lab 01 models the edge, not the tool.)

Stay in scope. Only ever proxy to owned internal hosts. The pivot's purpose here is to generate telemetry you can study, not to reach anything new.


4. Observe the telemetry the pivot generates

This is the heart of the exercise: a pivot is loud if you know the shape. Drive a little traffic through the SOCKS proxy (a few requests to the owned fileserver and db) and then go read what the sensor recorded.

In netflow / Zeek conn.log, expect:

  • The pivot becomes a connection hub — it now initiates connections to multiple internal peers it did not talk to before. East-west fan-out from one host. (WARMUP Ch. 6/7.)
  • A long-lived connection from the operator box to the pivot — the tunnel itself — unlike normal short request/response flows.
  • Multiplexing — many logical streams over the one tunnel; the byte/timing profile of that single connection looks unlike a normal application.
# conn.log (sketch): note the long duration on the tunnel and the new pivot→internal flows
ts       orig_h     resp_h      proto duration  bytes   note
...      operator   pivot       tcp   1380.4    big     ← the persistent SOCKS tunnel (long-lived)
...      pivot      fileserver  tcp   0.9       small   ← NEW east-west flow (pivot is now a hub)
...      pivot      db          tcp   1.2       small   ← NEW east-west flow

If you also exercise DNS through the pivot (resolve names via the pivot's resolver), watch dns.log: even a small amount of name resolution through a new path is visible, and if you (on the range, for teaching) encode data into query names, the entropy and unique-subdomain count spike — the DNS-tunnel signal from WARMUP Chapter 2.

Expected observations to record in the evidence packet: the long-lived tunnel flow, the new east-west fan-out from the pivot, and (if exercised) the DNS query-rate/entropy. These are the detections you are about to make fire.


5. Break it: segmentation ACL + egress allow-list

Now play defender. The choke point from step 2 is the pivot; the control is to segment so the operator/edge segment cannot use the pivot as a gateway, and to deny-by-default egress so a beacon has nowhere to go. Model the policy in Lab 02 first, then enforce it on the range.

Lab-02 model of the fix:

# The lateral path the pivot enabled, as flows:
path = [Flow("operator", "pivot", 22), Flow("pivot", "db", 5432)]

# Before: a loose ruleset permits the whole path (the attacker walks it):
loose = [Rule("any", "any", "any")]
blocks_path(path, loose)        # False  ← nothing is stopped

# After: deny-by-default + only the designed flows; the pivot→db hop is no longer allowed:
tight = [
    Rule("operator", "pivot", 22),         # operator may admin the pivot on ssh
    Rule("dmz",      "internet", 443),     # the pivot's only egress: https via the proxy
    # (no pivot→internal allow; no broad egress)
]
blocks_path(path, tight)        # True   ← the second hop (pivot→db) is denied
first_blocked_hop(path, tight)  # 1      ← exactly where the segmentation bites
over_permissive(loose)          # flags the any-any rule
egress_findings([Rule("dmz","internet","any")])  # recommends pinning egress to 443/80/53 via proxy

Enforce it on the range. Apply the equivalent ACL on the segment firewall / the pivot's host firewall: deny the pivot → internal segment, and deny-by-default egress except tcp/443,tcp/80, udp/53 to the (owned, logged) proxy/resolver. Then re-probe to prove effective state, not intended config:

operator → re-run `proxychains4 curl http://db/` through the pivot
expected → the pivot→db hop is now denied; the request fails; the denied flow is logged
operator → confirm the DESIGNED flow still works (regression: operator→pivot:22 still succeeds)

If the path still works, your rule is shadowed (an earlier any-any allow) — fix the ordering and re-probe. Intended config is not effective config (WARMUP Ch. 8).


6. Write the detection (Zeek / Suricata)

The control breaks this pivot; the detection catches the next one. Write detections on the shape you observed in step 4, not on a throwaway indicator.

Zeek (behavioral — the pivot's fan-out and the long-lived tunnel):

# notice on a host that becomes an east-west connection hub (pivot fan-out):
#   count distinct internal resp_h per orig_h per window; alert above a baseline threshold.
# notice on an unusually long-lived connection to/from an edge host (the tunnel itself):
#   alert on conn.log duration >> the host's normal connection duration.
# (Zeek's scan-detection + a small connection-fan-out script cover both.)

Suricata (signatures — odd-port egress / known tooling):

alert tcp $HOME_NET any -> $EXTERNAL_NET ![80,443,53] (msg:"Possible C2: egress on non-standard port";
    flow:to_server,established; threshold:type both, track by_src, count 5, seconds 60; sid:1000001;)

DNS (entropy/volume — if you exercised the DNS path): alert on per-host query rate to one zone, high mean label entropy, and high unique-subdomain count from dns.log (WARMUP Ch. 2/10).

Prove the detection fires. Re-run the pivot (before re-applying the block, or on a fresh snapshot) and confirm the Zeek notice / Suricata alert / DNS-entropy threshold triggers on the recorded telemetry. A detection you have not seen fire is a hypothesis, not a detection.


7. Evidence packet and teardown

Evidence packet (sanitized, owned-range only):

  • The Lab-01 pivot map: reachable set, cheapest path, choke point.
  • The step-4 telemetry: the conn.log lines (long-lived tunnel + east-west fan-out) and any dns.log entropy figures — observation separated from inference.
  • The Lab-02 policy: the over-permissive findings, the tightened ruleset, and the blocks_path / re-probe proof that the path is closed and the designed flow still works.
  • The Zeek/Suricata detection and a screenshot/log of it firing.
  • Hash every artifact at collection; record source, time, collector, timezone, and any transformation.

Teardown:

  • Revert every host to its pre-run snapshot.
  • Tear down the SOCKS tunnel and remove the temporary host-firewall rules (or keep the good ones as the durable control — that is the point of leaving a detection behind).
  • Confirm no range host can reach anything outside the isolated range.
  • Record teardown completion in the run sheet.

8. Common false claims (and the truthful version)

False claimThe truth
"The pivot was invisible — it was inside SSH/HTTPS."The content was encrypted; the shape (long-lived tunnel, east-west fan-out, multiplexing) is exactly what netflow/Zeek caught.
"A SYN scan is undetectable."It dodges application logs, not netflow; the fan-out is the signature.
"DNS tunneling is stealthy."It is one of the most detectable channels — the encoded names have obvious entropy and unique-subdomain counts.
"We have VLANs, so we're segmented."VLANs without inter-VLAN ACLs leave the graph connected; you must verify the hop is actually denied.
"We blocked the C2 IP, so we're safe."Blocking an IP costs the adversary minutes; only detecting the TTP (beacon shape, tunnel entropy, pivot fan-out) costs them a redesign.
"The rule exists, so the path is blocked."Intended config is not effective config — re-probe to prove the hop fails and the designed flow still works.
"The detection should work."A detection you have not watched fire is a hypothesis; re-run the pivot and confirm the alert triggers on the real telemetry.

This guide stays on the owned range and pairs every offensive step with its detection. The runnable labs (Lab 01, Lab 02) reason over synthetic metadata only — they plan and analyze the pivot and the segmentation; they never scan, tunnel through, or touch a host you do not own.