Scrub attacker-seeded /etc/hosts on the Kali box - #416
Conversation
`score reset` left the Kali /etc/hosts untouched, so AD host->IP mappings an agent wrote (e.g. `nxc --generate-hosts-file`) persisted across runs and handed the next agent the domain topology it is meant to enumerate. Add an /etc/hosts target to the Kali cleanup that strips non-loopback lines while preserving the pristine baseline (blank, comment, 127.*, ::1, and fe/ff IPv6-reserved rows). The rewrite is root-guarded with `sudo -n` so it fails closed rather than prompting. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up hardening on b212f1d. The rewrite installed whatever the keep-filter produced, with no floor. An agent that clobbered /etc/hosts outright (`nxc --generate-hosts-file > /etc/hosts` rather than `>>`) leaves no baseline line to keep, so the scrub wrote an empty /etc/hosts and broke hostname resolution for the next run — the exact scenario it exists for. The copy is now refused unless a loopback line survived the filter. It also staged the filtered copy at a fixed /tmp/.dg_hosts and handed that path to root. /tmp is world-writable, so a predictable name lets a local user swap the contents between the unprivileged write and the `sudo -n cp` and choose the system's /etc/hosts. Use mktemp instead. The IPv6 prefix test was case-sensitive, so FE80::/FF02:: rows were counted as attacker entries and stripped out of the baseline. Match with tolower(). Every skip path now reports why. Previously a box without passwordless sudo showed found-but-not-removed with stderr discarded and no reason. The find and clean commands were hand-written complements of each other; both now derive from hostsBaselineFilter so they cannot drift. Tests run the real awk against fixture hosts files (pristine, nxc-seeded, uppercase v6, ULA and global v6, clobbered) rather than string-matching the generated script, and `sh -n` validates the emitted script in both modes. The uppercase and empty-rewrite cases both fail against the previous filter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The scrub keyed off "the line starts with #", so an entry an agent commented out rather than deleted survived it. `# 10.0.0.5 dc01.corp.local` hands the next agent the same topology as the live line. Match on the first token past any leading `#` instead, and count the line as an artifact only when that token parses as a routable address. Prose comments stay baseline, which is the reason not to simply drop every comment: that would also take the cloud-init manage_etc_hosts block and Ubuntu's IPv6 header, both of which a pristine image ships. The token scan makes the leading `#` optional so it strips indentation as well. Splitting an indented line without that yields an empty first field, which would read as prose and let the entry through — a hazard specific to this parse, since awk's own $1 ignores leading blanks. A comment that *starts* with an IP-like token (`# 1.2.3.4 release notes`) is now dropped as an artifact. That is undecidable from the line alone, and the failure only ever costs a comment, never a mapping. The awk program is multi-line as a result, so both variants are emitted once into shell variables instead of inlined at each of the three call sites. The generated script is what an operator reads back off a failing box, so it stays legible. Verified against fixture hosts files driving the real awk: commented, double-hash, indented, prose, commented loopback, CRLF, empty, and missing files, plus the full generated block against a cloud-init image. Exercised under BWK awk only; Kali defaults to mawk and every construct used is POSIX awk. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds /etc/hosts scrubbing to the Kali “score reset” flow to remove attacker-/agent-seeded host mappings that could leak target topology to subsequent runs, while preserving a pristine baseline set of entries.
Changes:
- Introduces an AWK-based baseline predicate and uses it to (a) count non-baseline
/etc/hostslines and (b) rewrite/etc/hostsdown to baseline lines undersudo -n. - Adds the
/etc/hostscheck/remediation as a new cleanup target reported alongside other artifact counts. - Adds unit tests that execute the real AWK programs against fixture hosts files and sanity-check the generated shell script.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| cli/cmd/score_reset.go | Adds AWK baseline logic and a guarded /etc/hosts rewrite to the generated Kali cleanup script. |
| cli/cmd/score_reset_test.go | Adds tests for the AWK filter behavior and for the generated cleanup script shape/shell validity. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses review feedback on #416. The guard that refuses to install a filtered /etc/hosts with no surviving baseline tested for `^127\.` alone. It asks whether baseline survived, and `::1` is baseline — a file whose only loopback is the v6 one still resolves localhost, so refusing there would leave the artifacts in place for the next run with nothing gained. Accept either family. Not reachable on the images this targets, which all ship `127.0.0.1 localhost`, but the guard should match its own stated intent. The `::1` arm is anchored to a delimiter so it cannot match a routable address that merely starts with those characters. The pattern moves to hostsLoopbackGuard so the script and the test cannot drift, and TestHostsLoopbackGuard now runs the real grep against real filter output instead of pinning the literal — v4-only and v6-only baselines are accepted, a clobbered file and a comments-only file are refused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (1)
cli/cmd/score_reset.go:346
- The cleanup script prints per-target counts as "$count_N files", but the newly added /etc/hosts target counts attacker-seeded lines/entries (via awk), not files. This will surface as confusing output like "attacker /etc/hosts entries ...: 2 files". Consider switching the generic suffix to "items" (or removing the unit entirely) so the output stays accurate for both file- and non-file-based targets.
// The hosts programs are multi-line; hoisting them out of the per-target
// commands keeps the emitted script readable. Neither program touches
// anything on its own, so both are safe to define in dry-run mode.
fmt.Fprintf(&sb, "\ndg_hosts_find='%s'\ndg_hosts_keep='%s'\n", hostsFindProgram, hostsKeepProgram)
Addresses review feedback on #416. The per-target detail line hardcoded "files", which the /etc/hosts target made visibly wrong: "attacker /etc/hosts entries (non-loopback): 2 files" counts lines, not files. Two older targets are already off in the same way — the agent report and hashcat potfile targets report a present-or-absent 1/0. Display only. The result the caller parses is the JSON past the marker, not this line. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment volume on this one cleanup target had grown out of proportion to the file — roughly forty lines against zero on each of the fourteen sibling targets — and much of it restated the code rather than explaining it. Cut the prose transcription of the awk predicate, the duplicated rationale for hoisting the programs into shell variables, and per-case test comments that repeated their own test names. Kept every reason a reader cannot recover from the code: cp rather than mv so the destination inode keeps its mode, mktemp rather than a fixed path in world-writable /tmp, `#*` rather than `#+` so indentation is stripped, tolower for uppercase v6, what the loopback guard is defending, and why sudo -n fails closed. Comments only; no code changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Follow-up on the suppressed comment about the |
score resetcleaned every other agent artifact off the Kali box but left/etc/hostsuntouched, so the next agent started with a map of the domain it is supposed to enumerate.Added
dreadgoad score resetstrips/etc/hostslines mapping a routable address to a hostname — whatnxc --generate-hosts-fileand friends leave behind — and reports them in the per-target counts like any other artifact.# 10.0.0.5 dc01.corp.localleaks the same topology as the live line.manage_etc_hostsblock, Ubuntu's IPv6 header),127.*,::1, and thefe/ffIPv6-reserved rows in either case (ff02::1orFF02::1).Notes
sudo -nfails closed rather than prompting, so a box without it prints aWARN:line and reports found-but-not-removed instead of hanging./etc/hostswith>instead of>>— the rewrite is refused and the file is left as-is, rather than installing an empty/etc/hostsand breaking hostname resolution for the next run. Either loopback family satisfies the guard.# 1.2.3.4 release notes) is treated as an artifact and dropped. That is undecidable from the line alone; the failure costs a comment, never a mapping.awkandgrep, plussh -non the generated script. Exercised under BWK awk only — Kali defaults to mawk, and every construct used is POSIX awk. Not yet run against a live Kali box over SSM or Bastion.