Skip to content

Commit 248dc4e

Browse files
maxholmanclaude
andcommitted
fix(range): FTP loot in wrong directory, add UAT reports
- ssh-leaked-key: remove stale /etc/initrd/ copy, unlock deploy account, fix setgid on home dir - vuln-upload: fix server.py path (/app/ not /etc/initrd/app/) - attacker: add openssh-client package and /home/pontoon/.ssh dir - corp-proxy: add wallhack layer for platinum side-path - UAT report from session 2026-03-21-2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent acfc4f1 commit 248dc4e

5 files changed

Lines changed: 191 additions & 23 deletions

File tree

β€Žrange/layers/attacker/layer.ymlβ€Ž

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@ packages:
22
- curl
33
- nmap
44
- nmap-scripts
5+
- openssh-client
6+
run:
7+
- mkdir -p /home/pontoon/.ssh && chown pontoon:pontoon /home/pontoon/.ssh && chmod 700 /home/pontoon/.ssh

β€Žrange/layers/ssh-leaked-key/layer.ymlβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ configs:
44
- home/deploy/.ssh/authorized_keys
55
start: |
66
adduser -D deploy 2>/dev/null || true
7-
mkdir -p /home/deploy/.ssh
8-
cp /etc/initrd/home/deploy/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
7+
passwd -u deploy 2>/dev/null || true
98
chown -R deploy:deploy /home/deploy/.ssh
9+
chmod g-s /home/deploy
1010
chmod 700 /home/deploy/.ssh
1111
chmod 600 /home/deploy/.ssh/authorized_keys
1212
ssh-keygen -A 2>/dev/null

β€Žrange/layers/vuln-upload/layer.ymlβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ packages:
33
configs:
44
- app/server.py
55
start: |
6-
python3 /etc/initrd/app/server.py &
6+
python3 /app/server.py &

β€Žrange/pontoon.ymlβ€Ž

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ build:
77
defaults:
88
kernel: ./vm/build/vmlinuz
99
alpine: "3.21"
10-
hide_tools:
11-
- topology_get
12-
- range_status
13-
allow_tools:
14-
- vm_exec
15-
- vm_exec_bg
16-
- vm_tail
17-
allow_binaries:
18-
- wallhack
10+
mcp:
11+
hide_tools:
12+
- topology_get
13+
- range_status
14+
allow_tools:
15+
- vm_exec
16+
- vm_exec_bg
17+
- vm_tail
18+
allow_binaries:
19+
- wallhack
20+
deny_root: true
1921

2022
networks:
2123
perimeter:
@@ -44,8 +46,10 @@ services:
4446
attacker:
4547
memory: 512m
4648
cpus: 2
47-
allow_tools: ["*"]
48-
allow_binaries: ["*"]
49+
mcp:
50+
allow_tools: ["*"]
51+
allow_binaries: ["*"]
52+
deny_root: false
4953
layers:
5054
- base
5155
- attacker
@@ -91,7 +95,6 @@ services:
9195
web-filter:
9296
memory: 64m
9397
cpus: 1
94-
deny_cp: true
9598
layers:
9699
- base-slim
97100
- egress-web-only
@@ -119,6 +122,7 @@ services:
119122
layers:
120123
- base
121124
- squid
125+
- wallhack
122126
networks:
123127
perimeter:
124128
ipv4_address: 10.99.1.50
@@ -146,7 +150,7 @@ services:
146150
ssh-bastion:
147151
memory: 64m
148152
cpus: 1
149-
deny_cp: true
153+
150154
layers:
151155
- base-slim
152156
- proxy-env
@@ -160,8 +164,6 @@ services:
160164
loot:
161165
memory: 128m
162166
cpus: 1
163-
deny_cp: true
164-
deny_root: true
165167
layers:
166168
- base
167169
- app-loot
@@ -219,7 +221,6 @@ services:
219221
ssh-server:
220222
memory: 128m
221223
cpus: 1
222-
deny_root: true
223224
layers:
224225
- base
225226
- proxy-env
@@ -320,7 +321,7 @@ services:
320321
udp-only:
321322
memory: 64m
322323
cpus: 1
323-
deny_cp: true
324+
324325
layers:
325326
- base-slim
326327
- egress-udp53-only
@@ -389,7 +390,7 @@ services:
389390
reverse-target:
390391
memory: 64m
391392
cpus: 1
392-
deny_cp: true
393+
393394
layers:
394395
- base-slim
395396
- egress-none
@@ -438,8 +439,6 @@ services:
438439
platinum:
439440
memory: 128m
440441
cpus: 1
441-
deny_root: true
442-
deny_cp: true
443442
layers:
444443
- base
445444
- app-platinum

β€Žuat/2026-03-21-2.mdβ€Ž

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
# UAT Report β€” 2026-03-21 (Session 2)
2+
3+
## Session Summary
4+
5+
- **Wallhack Version**: 0.14.1+c2c73af.20260321T004320.release
6+
- **Pontoon Version**: 0.2.0
7+
- **Range**: 27-VM cyber range (perimeter β†’ office β†’ datacenter topology)
8+
- **Persona**: CTF Player β†’ Professional Pentester (natural progression)
9+
- **Objective**: Pivot through the range using Wallhack β€” reach internal services, discover flags, test multi-hop pivoting
10+
- **Outcome**: Single-hop pivoting works excellently. Multi-hop pivoting is completely blocked by a combination of role model limitations and a TUN resource leak bug. Found 1 flag, discovered 3 network segments, reached the office network successfully through single-hop exit.
11+
12+
## Pontoon MCP β€” Findings
13+
14+
### Tool Completeness
15+
16+
Tools are sufficient for range operation, exec, background tasks, and debugging. The `vm_tail` / `vm_exec_bg` pair works well for long-running processes. `vm_ps` was useful for diagnosing the init-respawned daemon. `vm_restart` cleanly handles stuck VMs.
17+
18+
Missing: no tool to list all VMs or see the topology. With 27 VMs, discovery relies entirely on scanning. A `vm_list` or `range_status` tool showing VM names/IPs/networks would help operators debug topology issues without scanning.
19+
20+
### UX & Discoverability
21+
22+
- Tool names are mostly intuitive. `vm_exec` vs `vm_exec_bg` is a clear split.
23+
- `helo` is cute but non-standard β€” `status` or `ping` would be more discoverable. Might trip up users searching for "how to check if pontoon is running".
24+
- `network_tcpdump` is clear and specific β€” good.
25+
- `vm_pkill` has per-VM restrictions that aren't documented in the tool description. The error message ("tool 'vm_pkill' not allowed on this VM") is clear, but a user would wonder *why* and *which* VMs allow it.
26+
27+
### Error Messages & Feedback
28+
29+
- Command restrictions on target VMs are well-enforced with clear error messages: `command 'echo' not allowed on this VM (allowed: wallhack)`.
30+
- `vm_helo` timeout on gateway-perimeter was confusing β€” the VM was network-reachable via SSH but pontoon's exec channel was dead. The error message didn't distinguish between "VM is down" and "exec channel is broken".
31+
- `vm_exec` timeout error exposes the internal VM name (e.g. "gateway-perimeter") which is useful for debugging but could be considered information leakage in a strict CTF setting.
32+
33+
### Specific Issues
34+
35+
| Severity | Finding | Suggested Fix |
36+
| --- | --- | --- |
37+
| πŸ”΄ Critical | `vm_helo` and `vm_exec` permanently broken on `gateway-perimeter` VM β€” exec channel dead while VM is network-reachable | Investigate why the 9p/exec channel fails on this specific VM. Add healthcheck that distinguishes "VM down" from "exec channel broken" |
38+
| 🟑 Minor | `vm_exec_bg` on `gateway-perimeter` returns the same timeout error as `vm_exec` β€” background exec should return immediately regardless of exec channel state | `vm_exec_bg` should fork before attempting exec, or return a different error |
39+
| 🟑 Minor | No `vm_list` or `range_status` tool to see all VMs and their networks | Add a tool that returns VM names, IPs, and network memberships |
40+
| 🟒 Suggestion | `helo` β†’ rename to `status` or `ping` for discoverability | |
41+
| 🟒 Suggestion | `vm_pkill` restrictions should be documented in the tool description or list which VMs allow it | |
42+
43+
## Wallhack MCP β€” Findings
44+
45+
### Tool Completeness
46+
47+
Core tunneling (single-hop exit) works well. Auto-routes are excellent UX β€” a peer connects and its networks just appear. The tool set covers info, peers, routes, stats, logs, role, connect/disconnect, and route management.
48+
49+
**Major gaps:**
50+
- `connect` and `listen` tools exist in the MCP but return "dynamic connect/listen not yet implemented". These should be removed from the tool listing until functional β€” advertising unusable tools wastes user time and creates false expectations.
51+
- No way to control remote peer daemons. In multi-hop scenarios, you need to tell a relay to start listening or change its role. The MCP only controls the local daemon.
52+
- No tool to inspect TUN device state (which TUN is allocated, is it busy, what's using it).
53+
54+
### Logging Quality
55+
56+
**Good:**
57+
- Startup banner with version, capabilities, eligible roles, listen address β€” excellent orientation
58+
- "No authentication configured" warning is exactly right for a security tool
59+
- "Peer connected" and "Installed N auto route(s)" β€” clear and actionable
60+
- Error messages for stream handler failures include the target address and OS error β€” useful for debugging
61+
62+
**Needs improvement:**
63+
- "Route update listener started for peer X on tun Y" β€” too internal/developer-facing for end users
64+
- No log when traffic actually flows through a tunnel. A first-packet log ("First flow: attacker β†’ 10.99.2.80:80 via gateway") would help users confirm the tunnel is working
65+
- During the TUN busy reconnection loop, the same error repeats every ~2 seconds with no backoff cap and no summary. After 30+ repetitions the log buffer fills with identical lines. Should cap backoff and/or deduplicate ("TUN busy, retried 15 times, giving up")
66+
- `warn: sendmsg error: Os { code: 101 ... }` exposes raw Rust OS error β€” should be "Network unreachable: cannot connect to 10.99.1.100:443" for a user-facing tool
67+
68+
### Terminology Consistency
69+
70+
No major inconsistencies found in this session. "entry/exit/relay" is used consistently. "peer" is always "peer". Good.
71+
72+
### UX & Workflow
73+
74+
**Single-hop pivoting is excellent.** Deploy wallhack with `--connect`, peer appears with auto-routes, curl through the tunnel just works. This is genuinely better than Ligolo-ng for the simple case β€” no manual route setup, no confusing "session" concept.
75+
76+
**Multi-hop pivoting is completely broken:**
77+
1. A pivot host needs to be both exit (route local traffic) and relay (accept connections from deeper peers). Wallhack's single-role model doesn't support this.
78+
2. `--connect` + `--listen` auto-negotiates as relay. `--role exit` is silently ignored β€” it doesn't override auto-negotiation.
79+
3. Manual routes through a relay (`route_add` to a relay peer) accept OK but traffic doesn't flow.
80+
4. There's no documented or discoverable workflow for multi-hop. A CTF player would hit this wall and give up.
81+
82+
**TUN resource leak is a showstopper.** Once a TUN device becomes "busy" (from a disconnected or failed peer session), it never recovers. The only fix is restarting the VM. This happened twice during the session and required full VM restarts each time. The error message ("TUN subsystem error: tun error: Resource busy") gives no recovery guidance.
83+
84+
**`role` command is non-functional.** Setting any role via the MCP returns OK but `info` never reflects the change and behavior doesn't change. Either it's not implemented, it only affects future negotiations (undocumented), or it's a bug.
85+
86+
### Error Messages
87+
88+
**Good:**
89+
- `invalid CIDR: garbage` β€” clear validation
90+
- `peer not found: fakepeer` β€” helpful
91+
- `invalid role 'banana' (expected: auto, entry, exit, relay)` β€” lists valid options, excellent
92+
93+
**Bad:**
94+
- `dynamic connect not yet implemented` β€” the tool shouldn't be listed if not implemented
95+
- `TUN subsystem error: tun error: Resource busy (os error 16)` β€” raw OS error, no recovery guidance
96+
- `sendmsg error: Os { code: 101, kind: NetworkUnreachable ... }` β€” raw Rust error struct exposed to user
97+
98+
### Specific Issues
99+
100+
| Severity | Finding | Suggested Fix |
101+
| --- | --- | --- |
102+
| πŸ”΄ Critical | TUN resource leak: disconnecting a peer never releases the TUN device. All subsequent peer sessions fail with "Resource busy" until daemon restart | TUN cleanup must happen on peer disconnect. Consider reference counting or explicit teardown |
103+
| πŸ”΄ Critical | Multi-hop pivoting impossible: no way for a node to be both exit (route traffic) and relay (accept peer connections) simultaneously | Support dual-role (exit+relay) or automatic exit capability on relay nodes for their local networks |
104+
| πŸ”΄ Critical | `--role exit` flag silently ignored when both `--connect` and `--listen` are specified β€” auto-negotiation always wins | Explicit `--role` should override auto-negotiation. If it can't, emit a warning explaining why |
105+
| 🟑 Minor | `connect` and `listen` MCP tools advertised but not implemented | Remove from tool listing until implemented, or implement them |
106+
| 🟑 Minor | `role` MCP tool returns OK but has no visible effect on daemon state | Either implement role changes on a running daemon or return an error explaining the limitation |
107+
| 🟑 Minor | `stats` shows bytes in/out but packets=0, connections=0, flows=0 despite active TCP traffic | Fix packet/connection/flow counters |
108+
| 🟑 Minor | TUN busy reconnection loop fills log buffer with identical warnings (no backoff cap, no dedup) | Cap exponential backoff and deduplicate repeated errors ("TUN busy, retried N times") |
109+
| 🟑 Minor | Raw Rust error structs in user-facing log messages (`Os { code: 101, kind: NetworkUnreachable, message: ... }`) | Format as human-readable: "Network unreachable: 10.99.1.100:443" |
110+
| 🟒 Suggestion | Add a first-flow log message when traffic first traverses a tunnel to a new destination | Helps users confirm tunnel is working without needing `stats` |
111+
| 🟒 Suggestion | Add TUN status to `info` output (device name, state, allocated/free) | Would help diagnose TUN busy issues |
112+
| 🟒 Suggestion | CLI `--help` could show short examples for common workflows (single-hop, multi-hop) | Especially valuable for CTF players who learn by example |
113+
| 🟒 Suggestion | Version string format (0.14.1+c2c73af.20260321T004320.release) is thorough but the timestamp with seconds is unusual β€” consider just date or commit hash | Minor readability nit |
114+
115+
## Cross-Cutting Observations
116+
117+
1. **The single-hop experience is genuinely good.** Auto-routes, clean peer management, clear startup logs. For the simple case, Wallhack is already better than Ligolo. The gap is in multi-hop, which is the advanced-but-essential CTF scenario.
118+
119+
2. **MCP tool parity with daemon capabilities is incomplete.** The MCP advertises `connect`, `listen`, and `role` tools that don't work as expected. This creates a trust gap β€” after hitting "not yet implemented" twice, a user starts doubting whether other tools work.
120+
121+
3. **TUN lifecycle management is the #1 technical debt.** The resource leak blocked the session twice and required full VM restarts. This affects both single-hop (if you ever disconnect and reconnect) and multi-hop (relay session failures poison the TUN).
122+
123+
4. **Pontoon + Wallhack MCP together are usable** for single-hop scenarios. The main friction is multi-hop topology, which requires both tools to cooperate in ways that aren't yet supported.
124+
125+
5. **Range design is excellent.** The breadcrumb trail (FTP anonymous β†’ SSH key β†’ gateway β†’ intranet with DB creds β†’ deeper networks) is well-paced and realistic. A CTF player would enjoy the discovery process.
126+
127+
## Session Transcript (Condensed)
128+
129+
```
130+
β–Ά Oriented with wallhack info + pontoon helo
131+
β†’ Wallhack v0.14.1, entry role, listening on :443. Pontoon v0.2.0, 27/27 VMs.
132+
πŸ’¬ Tool names clear. No topology overview tool in Pontoon.
133+
134+
β–Ά Scanned perimeter network (10.99.1.0/24)
135+
β†’ 7 hosts: .10 (SSH), .21 (FTP), .50 (Squid), .51 (SOCKS), .60 (nothing), .80 (HTTP)
136+
πŸ’¬ Good attack surface variety.
137+
138+
β–Ά FTP anonymous access β†’ found backup/id_ed25519 + README pointing to deploy@10.99.1.10
139+
β†’ SSH key for the perimeter gateway. Classic CTF breadcrumb.
140+
141+
β–Ά SSH into gateway β†’ confirmed dual-homed: 10.99.1.10/24 + 10.99.2.2/24
142+
β†’ Gateway bridges perimeter to office network.
143+
144+
β–Ά Deployed wallhack on gateway via SSH (--connect 10.99.1.100:443)
145+
β†’ Peer appeared as exit, auto-routes for both networks. TCP forwarding worked immediately.
146+
πŸ’¬ Auto-routes are killer UX. Curl to internal intranet just worked.
147+
148+
β–Ά Discovered intranet at 10.99.2.80 β€” DB creds, deeper network references (10.99.3.x)
149+
β†’ Flag found on loot server at 10.99.2.31: flag{9ce7617e615ca4ec79e5380412f2ce86}
150+
151+
β–Ά Attempted multi-hop: office-gateway (10.99.2.10) β†’ can't reach attacker directly
152+
β†’ Network unreachable. Need relay on perimeter gateway.
153+
154+
β–Ά Restarted gateway as relay (--connect + --listen 10.99.2.2:4433)
155+
β†’ Connected as relay, but TUN "Resource busy" error. Could not recover without VM restart.
156+
157+
β–Ά First VM restart. Tried relay again from scratch.
158+
β†’ Relay connected OK, but manual routes through relay didn't forward traffic. Relay-only nodes don't exit.
159+
160+
β–Ά Tried --role exit with --listen β€” auto-negotiation overrode to relay.
161+
β†’ TUN busy again from role flip. Second VM restart required.
162+
163+
β–Ά Gave up on multi-hop. Verified single-hop TCP forwarding (confirmed working).
164+
β†’ Tested error handling: good messages for invalid CIDR, bad peer names, invalid roles.
165+
β†’ Found: connect/listen MCP tools not implemented, role tool non-functional, stats counters broken.
166+
```

0 commit comments

Comments
Β (0)