Skip to content

Scrub attacker-seeded /etc/hosts on the Kali box - #416

Merged
mkultraWasHere merged 7 commits into
mainfrom
fix/kali-hosts-reset
Aug 5, 2026
Merged

Scrub attacker-seeded /etc/hosts on the Kali box#416
mkultraWasHere merged 7 commits into
mainfrom
fix/kali-hosts-reset

Conversation

@mkultraWasHere

@mkultraWasHere mkultraWasHere commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

score reset cleaned every other agent artifact off the Kali box but left /etc/hosts untouched, so the next agent started with a map of the domain it is supposed to enumerate.

Added

  • dreadgoad score reset strips /etc/hosts lines mapping a routable address to a hostname — what nxc --generate-hosts-file and friends leave behind — and reports them in the per-target counts like any other artifact.
  • Entries an agent commented out rather than deleted are stripped too: # 10.0.0.5 dc01.corp.local leaks the same topology as the live line.
  • The pristine baseline survives untouched: blank lines, prose comments (the cloud-init manage_etc_hosts block, Ubuntu's IPv6 header), 127.*, ::1, and the fe/ff IPv6-reserved rows in either case (ff02::1 or FF02::1).

Notes

  • The rewrite needs passwordless sudo. sudo -n fails closed rather than prompting, so a box without it prints a WARN: line and reports found-but-not-removed instead of hanging.
  • If no loopback line survives the filter — an agent that clobbered /etc/hosts with > instead of >> — the rewrite is refused and the file is left as-is, rather than installing an empty /etc/hosts and breaking hostname resolution for the next run. Either loopback family satisfies the guard.
  • A comment that starts with an IP-like token (# 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.
  • Dry-run is unchanged in kind: it counts and never writes.
  • Verified against fixture hosts files driving the real awk and grep, plus sh -n on 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.

mkultraWasHere and others added 3 commits August 4, 2026 20:21
`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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/hosts lines and (b) rewrite /etc/hosts down to baseline lines under sudo -n.
  • Adds the /etc/hosts check/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.

Comment thread cli/cmd/score_reset.go Outdated
Comment thread cli/cmd/score_reset.go Outdated
Comment thread cli/cmd/score_reset_test.go Outdated
Comment thread cli/cmd/score_reset.go
mkultraWasHere and others added 2 commits August 4, 2026 21:16
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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

mkultraWasHere and others added 2 commits August 4, 2026 21:49
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>
@mkultraWasHere

Copy link
Copy Markdown
Contributor Author

Follow-up on the suppressed comment about the $count_N files suffix — confirmed and fixed in 6e50ac0. The per-target detail line now reports items, since the /etc/hosts target counts lines and two older targets (agent report, hashcat potfile) count a present-or-absent 1/0. Display only; the result the caller parses is the JSON past the marker, so nothing downstream changes.

@mkultraWasHere
mkultraWasHere marked this pull request as ready for review August 5, 2026 02:04
@mkultraWasHere
mkultraWasHere added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 9986c01 Aug 5, 2026
9 checks passed
@mkultraWasHere
mkultraWasHere deleted the fix/kali-hosts-reset branch August 5, 2026 02:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants