Lab 01 — Pivot / Attack-Path Planner
Lens: Lateral movement and pivoting as reachability over a graph. WARMUP: Chapter 7 (pivoting & tunneling), Chapter 8 (segmentation). ATT&CK: Lateral Movement (TA0008) — Remote Services (T1021), Internal Proxy (T1090.001); Discovery — Remote System Discovery (T1018), Network Service Discovery (T1046).
The problem
One foothold is rarely the objective. After landing behind Meridian's field-office VPN, the operator must answer three questions, and so must the defender:
- Blast radius — from this foothold, what can I touch? (Which hosts are reachable at all.)
- The quietest route — what is the cheapest path to the crown jewel? Not the fewest hops, but the path with the least detection-risk + effort. A noisy SMB-admin hop over a monitored segment "costs" more than a quiet allow-listed SSH tunnel.
- The choke point — which single host, if cut, disconnects the foothold from the target? This is the defender's highest-leverage segmentation control.
A pivot — a SOCKS proxy through a multi-homed jump host, a port-forward, a chisel/ligolo tunnel — is nothing more than adding an edge to this reachability graph. Pivoting is graph traversal; the attacker runs a search to find the path, and the defender runs the same search to find the cut. That equivalence is the whole lab.
What you build (lab.py)
Hosts are nodes; a directed edge A -> B (an Edge(src, dst, cost, via)) means an attacker on A
can reach B because they are network-adjacent and the attacker holds the credential/route
(via) that enables the hop. cost models detection-risk + effort.
reachable(graph, start, exclude=None)— BFS the blast radius from a foothold (optionally treating one host as removed — the defender's "what if I cut this?" probe).reaches/reachable_targets— which crown jewels the foothold reaches.shortest_path(graph, start, target, exclude=None)— Dijkstra over per-edgecost; returns(path, total_cost)— the cheapest (quietest) pivot route, deterministically.choke_points(graph, start, target)— the hosts whose removal disconnectsstartfromtarget(articulation points / dominators via node-removal min-cut) — the segmentation cuts that matter.
Attack cases the tests cover
- From the VPN foothold, the operator reaches the jump host, file server, app server, ERP database, and the domain controller — but not a disconnected island segment.
shortest_pathpicks the cheapest, not the shortest-hop route to the ERP database (via the file server, cost 5) over the noisier app-server route (cost 9), even though both are three hops.- The only route to the DC runs through the app server, so
choke_points(..., "dc")returns bothjumphostandappserver; the ERP database has a second path, soappserveris not a choke for it — onlyjumphostis. - Removing the jump host isolates the foothold entirely (
reachable == {vpn}). - Unreachable target →
Nonepath / no choke points; output is fully deterministic.
Run
pip install -r requirements.txt
cd <this dir>
LAB_MODULE=solution pytest -q # reference passes (17 tests)
pytest -q # your implementation after the TODOs
# fallback interpreter:
# LAB_MODULE=solution /Users/s0x/src/10xdev/ai-enigneer/.venv/bin/python -m pytest -q
Hardening / detection
The choke point is the detection-and-control deliverable. Each one maps to a concrete defense:
- Segment the choke host (the jump host) into its own zone with deny-by-default ACLs — Lab 02 turns that into the firewall rule and proves the path breaks.
- Kill the enabling
via: areused-local-adminedge argues for LAPS (unique local-admin passwords); astored-db-crededge argues for a secrets vault and short-lived credentials. - Instrument the cheapest path: the quiet route is the one most likely to be taken, so put the netflow/Zeek sensor there. A pivot through a jump host shows up as an east-west netflow fan-out and (for a SOCKS proxy) many short connections multiplexed from one source — see WARMUP Ch. 7 & 10.
Extensions
- Add per-target weighting: rank choke points by how many crown jewels each one cuts.
- Compute true dominators (Lengauer–Tarjan) instead of node-removal, for large graphs.
- Model credential nodes explicitly so a shared password becomes a visible single point of failure.
- (Owned range only) Import a real BloodHound or network-graph export and run the same analysis.
- Multi-language build spec: reimplement
reachable+ Dijkstra in Rust (petgraph) or Go as a CLI that reads a JSON graph — a fast, dependency-light pivot planner for the toolkit.
Interview / resume
"Built a pivot / attack-path planner that models lateral movement as reachability over a host graph — computing the blast radius from a foothold (BFS), the quietest route to a target (Dijkstra over a per-edge detection-risk cost), and the choke points a defender cuts (node-removal min-cut) — demonstrating that the attacker's path-finder and the defender's segmentation-planner are one algorithm."
Limitations: directed graph with non-negative integer edge costs; choke points are found by single-node removal (an articulation/dominator approximation, not minimum multi-node cuts); synthetic graph only — no live network is touched.