From 7086f4e2dc2c46bd43e667edf29362b13de19e57 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:11:59 -0500 Subject: [PATCH 01/49] docs: add 2026-05-29 Brutus feature-parity design spec Plan for borrowing four high-value capabilities from Praetorian's Brutus: embedded SSH bad-keys bundle, pre-auth RDP recon (NLA fingerprint + sticky-keys backdoor scan), stdin pipeline auto-detection (naabu / fingerprintx / masscan JSON / Nerva URI / bare host:port), and five new database modules (Neo4j, Cassandra, CouchDB, Elasticsearch, InfluxDB). Includes SNMP wordlist tiering, inline cred pairs, and a brutespray-vs-others positioning table for the README. Single combined release PR off dev. --- ...2026-05-29-brutus-feature-parity-design.md | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md diff --git a/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md b/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md new file mode 100644 index 0000000..3d3ff19 --- /dev/null +++ b/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md @@ -0,0 +1,206 @@ +# Brutus Feature-Parity Spec + +**Date:** 2026-05-29 +**Branch:** dev +**Delivery:** single combined release PR (phases A → B → C → D, in order) + +## Context + +Praetorian shipped Brutus in February 2026 — a Go-based, single-binary credential testing tool positioned explicitly as a Hydra alternative. It overlaps brutespray's domain but introduces a few genuinely new ideas: an embedded SSH bad-keys bundle, pre-auth RDP recon (NLA fingerprinting, sticky-keys backdoor detection), native stdin pipelining with `naabu`/`fingerprintx`/`masscan`, and a handful of databases brutespray doesn't yet cover. Brutespray remains stronger overall (TUI, checkpoint/resume, spray-mode lockout-awareness, SOCKS5 + proxy rotation, per-attempt JSONL, 36 services, Nessus/Nexpose imports) but is missing those four specific capabilities. + +This spec borrows the high-value ideas and lands them on `dev` as one release PR. The brutespray-vs-others comparison table on the README is folded in as part of the same release so the marketing surface lines up with the new features. + +## Goals + +1. Match Brutus's SSH bad-key testing with a vendored Rapid7 ssh-badkeys bundle + Vagrant + vendor keys, automatic per-key username pairing, CVE metadata in output. +2. Add pre-auth RDP recon: NLA fingerprinting and Sticky-Keys backdoor detection. Findings flow through normal output channels (text, JSON, TUI). +3. Make brutespray pipeline-friendly: read targets from stdin with format auto-detection (Nerva URI, naabu line, fingerprintx JSON, masscan JSON, bare `host:port`). +4. Add five database modules (Neo4j, Cassandra, CouchDB, Elasticsearch, InfluxDB) following the existing module pattern. +5. SNMP wordlist tiering (`default` / `extended` / `full`) with SCADA / camera / storage vendor strings. +6. Inline credential pairs via `-c user:pass[,user2:pass2…]`. +7. README comparison table positioning brutespray against Hydra, Medusa, Ncrack, Brutus. Full docs sweep. + +## Non-goals + +- Claude Vision web fingerprinting (`--experimental-ai`). Out of scope this release; pulls in headless Chrome + external API dependencies. Revisit separately. +- RDP backdoor `--exec` command execution. Legally fraught even on authorized engagements. +- RDP web terminal viewer. Overlaps grdp scope, big surface area. +- Brutus's subcommand structure (`brutus creds` / `web` / `snmp` / `badkeys`). Diverges from brutespray's flat-CLI philosophy. + +## Architecture + +All four phases follow existing brutespray patterns: `init()`-based module registration via `Register()` in `brute/registry.go`, `brute.ModuleParams` for per-module flags, `ConnectionManager.Dial()` for network I/O with proxy/interface support, `time.NewTimer` + goroutine + `select` for timeouts. Phases are ordered to minimize risk: A is self-contained, B touches the input parser, C is additive modules, D is pure docs. + +### Phase A — Pre-auth recon + +**SSH bad-keys** — new `brute/badkeys/` package using `go:embed` to vendor a snapshot of [Rapid7/ssh-badkeys](https://github.com/rapid7/ssh-badkeys) plus the HashiCorp Vagrant insecure key plus the per-vendor keys Brutus ships (F5 BIG-IP, ExaGrid, Barracuda, Ceragon FibeAir, Array Networks, Quantum DXi, Loadbalancer.org). Layout: + +``` +brute/badkeys/ + embed.go // go:embed keys/*.pem and metadata.yaml + registry.go // KeyEntry{Fingerprint, Username, CVE, Vendor, PEM} + registry_test.go + keys/ // vendored .pem files + metadata.yaml // fingerprint → {username, CVE, vendor} +``` + +`brute/ssh.go` extended: when `params["badkeys"] != "false"` (default on), the bad-keys pass runs **before** password attempts and on first match short-circuits (no further passwords for that host). Each key is paired with its default username from metadata. A successful key auth populates a new `BruteResult.KeyMatch *KeyEntry` field; output layer renders `[+] BADKEY ssh root@10.0.0.5 vagrant-insecure-key (CVE-2015-1338)`. + +Flags: +- `--no-badkeys` — opt-out (the user instruction). Default behavior: on for SSH. +- `--badkeys-only` — skip password attempts entirely. +- `-m badkeys-bundle:default|extended|full` — wordlist-style tiering, mirrors SNMP tiering in Phase C. + +Refresh cadence: monthly `chore(badkeys)` PR mirroring the existing `chore: monthly wordlist update` pattern visible in `git log`. + +**RDP NLA fingerprint** — `brute/rdp.go` adds a pre-auth probe that sends the X.224 Connection Request (RDPneg request) and parses the server's RDPneg response. If `PROTOCOL_HYBRID` (NLA) is required, finding is logged as `[INFO] rdp 10.0.0.5:3389 NLA-required`. If `PROTOCOL_RDP` only (no NLA), `[WARN] rdp 10.0.0.5:3389 NLA-not-enforced (CVE-class)`. Runs once per host before any brute attempts. + +**RDP Sticky-Keys backdoor scan** — after NLA fingerprint, if the target accepts standard RDP, connect to the logon screen via `x90skysn3k/grdp` (existing dep). Send 5× Shift down/up scancodes (sticky-keys trigger) and capture the resulting bitmap region around the active window. Detection is two-stage: (1) the post-trigger framebuffer must change meaningfully versus the pre-trigger frame (rules out servers ignoring input); (2) the top-left active-window region is OCR'd via a small embedded title-bar font matcher looking for `cmd.exe`, `C:\Windows\system32`, or a black console background. Finding emits as `[CRITICAL] rdp 10.0.0.5:3389 sticky-keys backdoor detected`. If detection is inconclusive (framebuffer changed but no console match), emit `[INFO] rdp 10.0.0.5:3389 sticky-keys inconclusive` so the operator can verify manually. Opt-out via `--no-rdp-scan`. + +**Implementation note:** the sticky-keys probe is the highest-risk piece in this spec because `grdp`'s framebuffer API may not expose the level of bitmap access this needs. If `grdp` proves too limited, fall back to a simpler signal: connect to the logon screen, send the trigger, and report `[WARN] rdp 10.0.0.5:3389 sticky-keys probe possible — manual verification required` without attempting OCR. Phase A still ships; the feature is downgraded from CRITICAL detection to WARN advisory. + +Result type expansion in `brute/run.go`: + +```go +type BruteResult struct { + // ... existing fields ... + Finding *Finding // pre-auth scan result + KeyMatch *KeyEntry // SSH bad-key match +} +type Finding struct { + Severity string // INFO|WARN|HIGH|CRITICAL + Code string // ssh-badkey|rdp-nla-missing|rdp-stickykeys + Message string + CVE string // optional +} +``` + +Output layer (`modules/output.go`) renders findings into text and JSONL streams; TUI gets a "Findings" tab alongside the existing tabs (see `tui/` for the existing tabbed Model). + +### Phase B — Stdin pipeline + masscan JSON + +`modules/parse.go` currently dispatches on file extension / contents for `-f`. Extend with: + +1. **Stdin detection** in `brutespray/config.go` / `brutespray.go:Execute`: if `-f` is empty and `os.Stdin` is a pipe (not a TTY — use `term.IsTerminal(int(os.Stdin.Fd()))`), read stdin as the target source. + +2. **Format auto-detection** in a new `modules/parse_stream.go`: + - First non-blank line decides format. + - Line starts with `{` → JSON. Probe for `nerva` (has `protocol`+`port`+`ip`), `fingerprintx` (has `host`+`service`), or `masscan` (has `ports[]` array). Dispatch to matching parser. + - Line matches `^[a-z]+://[^:]+:\d+` → Nerva URI. + - Line matches `^[^\s:]+:\d+$` → bare host:port (naabu `-silent` output). + - Anything else: pass through existing `-f` parsers (gnmap/etc.) attempting each. + +3. **Masscan JSON parser** — new file `modules/parse_masscan.go`. Masscan emits an array of `{ip, ports: [{port, proto, status}]}` objects. Only emit targets where `status="open"`. Port→protocol mapping reuses the existing default-port table. + +No new CLI flag. `naabu -silent | brutespray -u root -p rootpass` just works. + +### Phase C — New modules + SNMP tiering + inline cred pairs + +**Five new modules** in `brute/`: + +- `neo4j.go` — Bolt v5 protocol over TCP/7687. Uses `github.com/neo4j/neo4j-go-driver/v5/neo4j` (already widely used, MIT). Test via `docker run -p 7687:7687 neo4j:5`. +- `cassandra.go` — CQL native protocol over TCP/9042. Uses `github.com/gocql/gocql`. `PasswordAuthenticator`. Default-port 9042. Test via `cassandra:4`. +- `couchdb.go` — HTTP basic auth against `_session` endpoint, port 5984. No new dep; reuse existing http auth helpers. +- `elasticsearch.go` — HTTP basic auth against `/_cluster/health`, port 9200. No new dep. +- `influxdb.go` — InfluxDB 2.x token/basic auth against `/api/v2/orgs`, port 8086. No new dep. + +Each module follows the standard pattern from `CLAUDE.md`: `BruteXxx(host, port, user, password, timeout, cm, params) *BruteResult`, `cm.Dial()`, deadline set, timer/select timeout, `init()` registers via `Register()`. Add to stable service list in `brutespray/config.go` (couchdb/elasticsearch/influxdb safe to mark stable; neo4j/cassandra start as beta until docker-tested across versions). + +**SNMP tiering** — `brute/snmp.go` currently tries a single embedded community list. Split into `wordlist/snmp_default.txt` (~20: public, private, cisco, manager…), `wordlist/snmp_extended.txt` (~50: + Cisco/HP/Juniper enterprise), `wordlist/snmp_full.txt` (~120: + SCADA, IP camera defaults, storage array defaults — sourced from publicly-documented vendor docs). Select via `-m mode:default|extended|full` (default = `default`). Embed via `go:embed` in `modules/wordlist.go` alongside existing embedded lists. + +**Inline cred pairs** — `brutespray/dispatch.go` already handles `-C` combo files. Add `-c, --creds` that accepts comma-separated `user:pass` strings: `--creds 'admin:admin,root:toor'`. Splits → builds the same in-memory credential list a combo file would yield. Reuses existing `sanitizeCred` and PwDump auto-detect path off (these are explicit pairs). + +### Phase D — Documentation + comparison table + +**README** — add a positioning table after the existing feature list. Sketch: + +| Feature | brutespray | hydra | medusa | ncrack | brutus | +|---|---|---|---|---|---| +| Single static binary | ✅ | ❌ | ❌ | ❌ | ✅ | +| Interactive TUI | ✅ | ❌ | ❌ | ❌ | ❌ | +| Checkpoint / resume | ✅ | ❌ | ❌ | ✅ | ❌ | +| Spray mode (lockout-aware) | ✅ | ❌ | ❌ | ❌ | ❌ | +| Per-attempt JSONL | ✅ | ⚠️ | ❌ | ❌ | ❌ (success-only) | +| SOCKS5 + proxy rotation | ✅ | ⚠️ | ❌ | ❌ | ❌ | +| Embedded SSH bad-keys | ✅ (new) | ❌ | ❌ | ❌ | ✅ | +| Pipeline stdin (naabu/fingerprintx/masscan) | ✅ (new) | ❌ | ❌ | ❌ | ✅ | +| Pre-auth RDP recon (NLA / sticky-keys) | ✅ (new) | ❌ | ❌ | ❌ | ✅ | +| Nmap gnmap+XML / Nessus / Nexpose import | ✅ | ⚠️ | ❌ | ❌ | ⚠️ (nmap only) | +| Module params (`-m KEY:VAL`) | ✅ | ❌ | ❌ | ❌ | partial | +| Service count | 41 (after this PR) | 50+ | 34 | 14 | 23 | + +(Final symbols verified against tool docs at PR time, not from memory.) + +**docs/** sweep — following the existing `docs/wordlists.md` / `docs/services.md` style: +- `docs/services.md`: add neo4j, cassandra, couchdb, elasticsearch, influxdb rows. +- `docs/advanced.md`: new sections for SSH bad-keys (with CVE table), RDP pre-auth recon, stdin pipeline integration with `naabu | fingerprintx | brutespray` example. +- `docs/output.md`: document new `Finding` and `KeyMatch` JSONL fields. +- `docs/wordlists.md`: SNMP tiering description. +- `docs/usage.md`: `--no-badkeys`, `--badkeys-only`, `--no-rdp-scan`, `-c/--creds` inline pairs, stdin auto-detect. +- New `docs/pipeline.md` walking through the recon workflow end-to-end. + +**CLAUDE.md** — updated locally to reflect new modules, flags, and findings shape. Per memory policy `[[feedback_no_claude_md]]`, **not committed**. + +## Critical files to modify + +- `brute/ssh.go` — bad-keys integration +- `brute/rdp.go` — pre-auth NLA + sticky-keys recon +- `brute/snmp.go` — tier selection +- `brute/run.go` — `BruteResult` extension with `Finding` and `KeyMatch` +- `brute/registry.go` — register 5 new modules +- `brute/badkeys/` (new package) — embedded ssh-badkeys bundle +- `brute/{neo4j,cassandra,couchdb,elasticsearch,influxdb}.go` (new) — each follows the existing module pattern; tests follow `brute/redis_test.go` / `brute/postgres_test.go` shape +- `brutespray/config.go` — new flags (`--no-badkeys`, `--badkeys-only`, `--no-rdp-scan`, `-c/--creds`); stable/beta service lists +- `brutespray/dispatch.go` — inline-pairs expansion +- `brutespray/brutespray.go` — stdin pipeline detection at `Execute()` entry +- `modules/parse.go` — dispatch to new parsers +- `modules/parse_stream.go` (new) — stdin format auto-detection +- `modules/parse_masscan.go` (new) — masscan JSON parser +- `modules/output.go` — render `Finding` / `KeyMatch` in text and JSONL +- `modules/wordlist.go` — embed snmp_default/extended/full +- `tui/` — new findings tab; reuse existing tab pattern in `tui/view_*.go` +- `README.md` — comparison table +- `docs/*.md` — per-feature documentation + +## Reused utilities + +- `Register()` in `brute/registry.go` for new modules +- `ConnectionManager.Dial()` in `modules/connections.go` for all network I/O — gets SOCKS5/proxy/iface for free +- `sanitizeCred()` for text-protocol creds (couchdb, elasticsearch, influxdb) +- `time.NewTimer` + goroutine + `select` timeout pattern (CLAUDE.md mandate) +- `go:embed` infrastructure already used in `modules/wordlist.go` +- `x90skysn3k/grdp` already-imported RDP library for sticky-keys probe +- Existing TUI tabbed `Model` in `tui/` — extend, don't refactor + +## Verification + +**Unit:** new tests for each new module (`brute/neo4j_test.go` etc.) mirroring the docker-based pattern in `brute/redis_test.go`. Bad-keys metadata parsing tested against the vendored YAML. Stdin format auto-detect tested with table-driven fixtures (`modules/parse_stream_test.go`). + +**Integration:** spin docker containers for neo4j, cassandra, couchdb, elasticsearch, influxdb and run `brutespray` end-to-end with a known-good credential. Reuse the existing docker harness referenced in commit `f162ae9`. + +**Pipeline:** `naabu -host 127.0.0.1 -p 22 -silent | ./brutespray -u root -P wordlist/ssh.txt` against a local SSH container — confirm stdin auto-detect, confirm bad-keys phase fires. + +**RDP recon:** stand up a Windows RDP container with NLA off and sticky-keys backdoor (cmd.exe in place of sethc.exe) and confirm both findings emit. Repeat with NLA on / no backdoor — confirm clean output. + +**Comparison table:** verify each ✅/❌/⚠️ against the named tool's current documentation (not from memory) at PR time. + +**Regression:** `go test ./... -race` clean; existing 106 tests still pass; TUI smoke test (`./brutespray -H 127.0.0.1 -s ssh -u root -p test`). + +**Lint:** `golangci-lint run` clean. + +## Documentation deliverables + +Per the user instruction to update documentation for every new feature: + +- README.md — comparison table, mention of new features in feature list, stdin pipeline example near the top +- docs/services.md — 5 new module rows +- docs/usage.md — new flags +- docs/advanced.md — bad-keys CVE table, RDP recon details +- docs/output.md — Finding / KeyMatch JSONL schema +- docs/wordlists.md — SNMP tiering +- docs/pipeline.md (new) — end-to-end recon workflow walk-through +- CLAUDE.md (local only, not committed) — module patterns and flags + +## Out of band + +The pre-existing `dev` branch has scratch artifacts in the working tree (`brutespray-checkpoint.jsonl`, `brutespray-improved`, `brutespray-intelligent`, `brutespray-test`, `medusa/`, `thc-hydra/`, `test-output/`, etc.) per `git status`. These are ignored for this spec — work proceeds from a clean staging area; nothing in this spec touches them. From 07e8dd81febed302f3788f7ee97370907f43ff9c Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:13:25 -0500 Subject: [PATCH 02/49] docs: clarify grdp ownership and existing wordlist dirs in spec --- .../specs/2026-05-29-brutus-feature-parity-design.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md b/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md index 3d3ff19..6750fe0 100644 --- a/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md +++ b/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md @@ -57,7 +57,7 @@ Refresh cadence: monthly `chore(badkeys)` PR mirroring the existing `chore: mont **RDP Sticky-Keys backdoor scan** — after NLA fingerprint, if the target accepts standard RDP, connect to the logon screen via `x90skysn3k/grdp` (existing dep). Send 5× Shift down/up scancodes (sticky-keys trigger) and capture the resulting bitmap region around the active window. Detection is two-stage: (1) the post-trigger framebuffer must change meaningfully versus the pre-trigger frame (rules out servers ignoring input); (2) the top-left active-window region is OCR'd via a small embedded title-bar font matcher looking for `cmd.exe`, `C:\Windows\system32`, or a black console background. Finding emits as `[CRITICAL] rdp 10.0.0.5:3389 sticky-keys backdoor detected`. If detection is inconclusive (framebuffer changed but no console match), emit `[INFO] rdp 10.0.0.5:3389 sticky-keys inconclusive` so the operator can verify manually. Opt-out via `--no-rdp-scan`. -**Implementation note:** the sticky-keys probe is the highest-risk piece in this spec because `grdp`'s framebuffer API may not expose the level of bitmap access this needs. If `grdp` proves too limited, fall back to a simpler signal: connect to the logon screen, send the trigger, and report `[WARN] rdp 10.0.0.5:3389 sticky-keys probe possible — manual verification required` without attempting OCR. Phase A still ships; the feature is downgraded from CRITICAL detection to WARN advisory. +**Implementation note:** brutespray's `x90skysn3k/grdp` dependency lives at `../grdp/` and is owned by this project. The sticky-keys probe will be implemented by adding `client.RdpClient.CaptureLogonScreen(trigger LogonTrigger) (*image.RGBA, error)` (plus supporting framebuffer plumbing) to grdp, then consuming it from `brute/rdp.go`. Coordinated change across the two repos. Result type expansion in `brute/run.go`: @@ -149,7 +149,8 @@ Each module follows the standard pattern from `CLAUDE.md`: `BruteXxx(host, port, - `brute/run.go` — `BruteResult` extension with `Finding` and `KeyMatch` - `brute/registry.go` — register 5 new modules - `brute/badkeys/` (new package) — embedded ssh-badkeys bundle -- `brute/{neo4j,cassandra,couchdb,elasticsearch,influxdb}.go` (new) — each follows the existing module pattern; tests follow `brute/redis_test.go` / `brute/postgres_test.go` shape +- `brute/{neo4j,cassandra,couchdb,elasticsearch,influxdb}.go` (new) — each follows the existing module pattern; tests follow `brute/redis_test.go` / `brute/postgres_test.go` shape. `wordlist/{neo4j,influxdb}` already exist with user+password files; `wordlist/{cassandra,couchdb,elasticsearch}` are empty placeholders that get populated as part of this PR. +- `../grdp/client/` — add `CaptureLogonScreen` and `LogonTrigger` types (sibling repo, owned) - `brutespray/config.go` — new flags (`--no-badkeys`, `--badkeys-only`, `--no-rdp-scan`, `-c/--creds`); stable/beta service lists - `brutespray/dispatch.go` — inline-pairs expansion - `brutespray/brutespray.go` — stdin pipeline detection at `Execute()` entry From 4e0a982b536e9b86161ba05b1a17a3b55aed28eb Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:22:02 -0500 Subject: [PATCH 03/49] docs: add Brutus feature-parity implementation plan --- .../plans/2026-05-29-brutus-feature-parity.md | 3321 +++++++++++++++++ 1 file changed, 3321 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-29-brutus-feature-parity.md diff --git a/docs/superpowers/plans/2026-05-29-brutus-feature-parity.md b/docs/superpowers/plans/2026-05-29-brutus-feature-parity.md new file mode 100644 index 0000000..f99d56b --- /dev/null +++ b/docs/superpowers/plans/2026-05-29-brutus-feature-parity.md @@ -0,0 +1,3321 @@ +# Brutus Feature-Parity Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add SSH bad-keys bundle, pre-auth RDP recon (NLA + sticky-keys), stdin pipeline auto-detection, five new database modules (Neo4j/Cassandra/CouchDB/Elasticsearch/InfluxDB), SNMP wordlist tiering, inline cred pairs, and a brutespray-vs-others README comparison table — all in one combined release PR on `dev`. + +**Architecture:** Follows existing brutespray patterns — `init()` + `brute.Register()` for modules, `brute.ModuleParams` for flags, `ConnectionManager.Dial()` for network I/O, `time.NewTimer`+`select` timeouts. New `brute/badkeys/` package uses `go:embed` to bundle vendored Rapid7 ssh-badkeys. RDP pre-auth scans extend `brute/rdp.go` with a coordinated change in the sibling `../grdp/` repo (we own it). Stdin auto-detect lives in a new `modules/parse_stream.go` invoked from `brutespray.Execute()` when `os.Stdin` is a pipe. New result fields `Finding` and `KeyMatch` propagate through `BruteResult` → `output.go` → TUI. + +**Tech Stack:** Go 1.21+, existing deps (`x90skysn3k/grdp`, `go-redis`, `hirochachacha/go-smb2`, `bubbletea`, `golang.org/x/crypto/ssh`), new deps (`github.com/neo4j/neo4j-go-driver/v5`, `github.com/gocql/gocql`), `go:embed` for bundled keys + wordlists. + +**Spec:** `docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md` + +--- + +## Codebase notes (verify before referencing) + +- `modules.ConnectionManager` is constructed by an existing factory. Identify the actual constructor name (`NewConnectionManager`, `NewCM`, or similar) by grepping `func New.*ConnectionManager` in `modules/connections.go`, and substitute it in every test file that calls one. The plan uses `modules.NewConnectionManager()` as a placeholder name. +- Existing dispatcher already enqueues credentials through a helper — find it (look near the existing user/password loop in `brutespray/dispatch.go`) and reuse, do not introduce `queueCred` as a new function name unless it does not exist. +- The TUI active-flag and model singleton names (`tuiActive`, `tuiModel`) used in the plan reflect convention; substitute the actual identifiers from `tui/` if they differ. +- The output-sink names (`jsonOutput`, `jsonSink`, `stdoutSink`) used in plan code blocks are placeholders for whatever globals/struct fields `modules/output.go` already uses. Reuse those; do not introduce new globals. + +When in doubt, read the surrounding code first and adapt the snippet — the plan's *intent* is what matters, not the exact identifier names. + +## Working Conventions + +- TDD throughout: write the failing test first, run to confirm fail, implement minimal code, run to confirm pass, then commit. +- One concept per commit. Commit at the end of each numbered Task unless the task explicitly spans multiple commits. +- Build target: `go build -o brutespray .` from repo root. +- Run race-clean: `go test ./... -race`. +- Lint: `golangci-lint run`. +- All commits authored by the user (no Co-Authored-By trailer per `[[feedback_commit_authorship]]`). +- CLAUDE.md updates are local only — do NOT `git add` it per `[[feedback_no_claude_md]]`. + +--- + +# Phase A — Pre-auth recon + +## Task A1: Extend `BruteResult` with `Finding` and `KeyMatch` + +**Files:** +- Modify: `brute/run.go` (around lines 162-262 where BruteResult is defined and returned by value) +- Test: `brute/result_test.go` (new) + +- [ ] **Step 1: Add Finding and KeyMatch types to `brute/run.go`** + +Locate the `type BruteResult struct` block and insert two new fields plus the supporting types just above it: + +```go +// Finding represents a pre-auth recon result (e.g. SSH bad-key match, +// RDP NLA missing, RDP sticky-keys backdoor). Modules can return findings +// without a successful authentication attempt. +type Finding struct { + Severity string // INFO, WARN, HIGH, CRITICAL + Code string // e.g. "rdp-nla-missing", "rdp-stickykeys", "ssh-badkey" + Message string + CVE string // optional, e.g. "CVE-2012-1493" +} + +// KeyMatch records a successful SSH key authentication originating from +// the embedded bad-keys bundle. +type KeyMatch struct { + Fingerprint string + Vendor string + CVE string + Description string +} + +type BruteResult struct { + AuthSuccess bool + ConnectionSuccess bool + Error error + Banner string + RetryDelay time.Duration + SkipUser bool + Finding *Finding // pre-auth recon result, nil if none + KeyMatch *KeyMatch // SSH bad-key match, nil if none +} +``` + +- [ ] **Step 2: Propagate new fields through the value conversion at `brute/run.go:262`** + +Find the line `return BruteResult{AuthSuccess: modResult.AuthSuccess, ConnectionSuccess: modResult.ConnectionSuccess, Banner: modResult.Banner, SkipUser: modResult.SkipUser}` and extend it: + +```go +return BruteResult{ + AuthSuccess: modResult.AuthSuccess, + ConnectionSuccess: modResult.ConnectionSuccess, + Banner: modResult.Banner, + SkipUser: modResult.SkipUser, + Finding: modResult.Finding, + KeyMatch: modResult.KeyMatch, +} +``` + +- [ ] **Step 3: Write the failing test** + +Create `brute/result_test.go`: + +```go +package brute + +import "testing" + +func TestBruteResultCarriesFinding(t *testing.T) { + r := &BruteResult{ + ConnectionSuccess: true, + Finding: &Finding{ + Severity: "CRITICAL", + Code: "rdp-stickykeys", + Message: "sticky-keys backdoor detected", + }, + } + if r.Finding == nil || r.Finding.Code != "rdp-stickykeys" { + t.Fatalf("Finding not carried on BruteResult") + } +} + +func TestBruteResultCarriesKeyMatch(t *testing.T) { + r := &BruteResult{ + AuthSuccess: true, + ConnectionSuccess: true, + KeyMatch: &KeyMatch{ + Fingerprint: "SHA256:abc", + Vendor: "Vagrant", + CVE: "CVE-2015-1338", + }, + } + if r.KeyMatch == nil || r.KeyMatch.Vendor != "Vagrant" { + t.Fatalf("KeyMatch not carried on BruteResult") + } +} +``` + +- [ ] **Step 4: Run tests** + +Run: `go test ./brute/ -run TestBruteResult -v` +Expected: PASS for both. + +- [ ] **Step 5: Verify nothing else broke** + +Run: `go build ./... && go test ./... -count=1` +Expected: build succeeds; existing tests pass (Finding/KeyMatch are nil-default so no behavior change yet). + +- [ ] **Step 6: Commit** + +```bash +git add brute/run.go brute/result_test.go +git commit -m "feat(brute): add Finding and KeyMatch fields to BruteResult" +``` + +--- + +## Task A2: Vendor `ssh-badkeys` snapshot + metadata + +**Files:** +- Create: `brute/badkeys/keys/` (directory with vendored .pem files) +- Create: `brute/badkeys/metadata.yaml` +- Create: `brute/badkeys/embed.go` +- Create: `brute/badkeys/registry.go` +- Create: `brute/badkeys/registry_test.go` + +- [ ] **Step 1: Snapshot Rapid7 ssh-badkeys** + +```bash +mkdir -p brute/badkeys/keys +cd /tmp && git clone --depth 1 https://github.com/rapid7/ssh-badkeys.git +cp /tmp/ssh-badkeys/authorized/* /home/shane/Documents/work/brutespray/brute/badkeys/keys/ || true +cp /tmp/ssh-badkeys/unauthorized/* /home/shane/Documents/work/brutespray/brute/badkeys/keys/ || true +cd /home/shane/Documents/work/brutespray +# Add Vagrant insecure key +curl -sSL https://raw.githubusercontent.com/hashicorp/vagrant/main/keys/vagrant -o brute/badkeys/keys/vagrant +ls brute/badkeys/keys/ | wc -l +``` + +Expected: ≥10 key files present. + +- [ ] **Step 2: Author metadata.yaml** + +Create `brute/badkeys/metadata.yaml` mapping each key filename to its metadata. Sample shape (full file must cover every key in `keys/`): + +```yaml +- file: vagrant + username: vagrant + vendor: HashiCorp Vagrant + cve: "" + description: Vagrant insecure default key (any Vagrant VM pre-2014) +- file: f5_big_ip + username: root + vendor: F5 BIG-IP + cve: CVE-2012-1493 + description: F5 BIG-IP 9.x-11.x default root SSH key +- file: exagrid + username: root + vendor: ExaGrid + cve: CVE-2016-1561 + description: ExaGrid EX series default support key +- file: ceragon_fibeair + username: mateidu + vendor: Ceragon FibeAir + cve: "" + description: Ceragon FibeAir IP-10 microwave radio default key +``` + +For every additional file in `keys/`, append an entry. Files without a known vendor get `vendor: "Rapid7 ssh-badkeys (origin unknown)"` and `username: root`. + +- [ ] **Step 3: Write `brute/badkeys/embed.go`** + +```go +package badkeys + +import "embed" + +//go:embed keys/* metadata.yaml +var assets embed.FS +``` + +- [ ] **Step 4: Write the failing test** + +Create `brute/badkeys/registry_test.go`: + +```go +package badkeys + +import "testing" + +func TestLoadReturnsNonEmptyBundle(t *testing.T) { + bundle, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + if len(bundle) < 5 { + t.Fatalf("expected >=5 keys, got %d", len(bundle)) + } +} + +func TestLoadParsesVagrantEntry(t *testing.T) { + bundle, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + for _, e := range bundle { + if e.Vendor == "HashiCorp Vagrant" { + if e.Username != "vagrant" { + t.Fatalf("vagrant entry username = %q, want vagrant", e.Username) + } + if len(e.PEM) < 100 { + t.Fatalf("vagrant PEM too short: %d bytes", len(e.PEM)) + } + return + } + } + t.Fatal("no Vagrant entry found in bundle") +} +``` + +- [ ] **Step 5: Implement `brute/badkeys/registry.go`** + +```go +// Package badkeys provides a curated, embedded bundle of known-compromised +// SSH private keys (Rapid7 ssh-badkeys + Vagrant + vendor defaults). Each +// entry pairs a key with its default username and CVE metadata so brute +// modules can surface CVE-tagged findings without external files. +package badkeys + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + + "gopkg.in/yaml.v3" +) + +type Entry struct { + File string + Username string + Vendor string + CVE string + Description string + PEM []byte + Fingerprint string // sha256 hex of PEM bytes +} + +type metaEntry struct { + File string `yaml:"file"` + Username string `yaml:"username"` + Vendor string `yaml:"vendor"` + CVE string `yaml:"cve"` + Description string `yaml:"description"` +} + +func Load() ([]Entry, error) { + raw, err := assets.ReadFile("metadata.yaml") + if err != nil { + return nil, fmt.Errorf("read metadata.yaml: %w", err) + } + var metas []metaEntry + if err := yaml.Unmarshal(raw, &metas); err != nil { + return nil, fmt.Errorf("parse metadata.yaml: %w", err) + } + out := make([]Entry, 0, len(metas)) + for _, m := range metas { + pem, err := assets.ReadFile("keys/" + m.File) + if err != nil { + return nil, fmt.Errorf("read keys/%s: %w", m.File, err) + } + sum := sha256.Sum256(pem) + out = append(out, Entry{ + File: m.File, + Username: m.Username, + Vendor: m.Vendor, + CVE: m.CVE, + Description: m.Description, + PEM: pem, + Fingerprint: hex.EncodeToString(sum[:]), + }) + } + return out, nil +} +``` + +- [ ] **Step 6: Add `gopkg.in/yaml.v3` dep** + +Run: `go get gopkg.in/yaml.v3@latest && go mod tidy` + +- [ ] **Step 7: Run tests** + +Run: `go test ./brute/badkeys/ -v` +Expected: both tests PASS. + +- [ ] **Step 8: Commit** + +```bash +git add brute/badkeys/ go.mod go.sum +git commit -m "feat(badkeys): vendor Rapid7 ssh-badkeys + Vagrant + vendor key bundle" +``` + +--- + +## Task A3: Integrate bad-keys into `brute/ssh.go` + +**Files:** +- Modify: `brute/ssh.go` +- Test: `brute/ssh_badkeys_test.go` (new) + +- [ ] **Step 1: Write the failing test** + +Create `brute/ssh_badkeys_test.go`: + +```go +package brute + +import ( + "testing" + + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" +) + +func TestBadKeysPlanCoversBundle(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + plan := PlanBadKeyAttempts(bundle, "") + if len(plan) != len(bundle) { + t.Fatalf("plan size = %d, bundle = %d", len(plan), len(bundle)) + } + for _, a := range plan { + if a.Username == "" { + t.Fatalf("attempt missing username: %+v", a) + } + } +} + +func TestBadKeysPlanRespectsExplicitUser(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + plan := PlanBadKeyAttempts(bundle, "admin") + for _, a := range plan { + if a.Username != "admin" { + t.Fatalf("explicit username override failed: %s", a.Username) + } + } +} +``` + +- [ ] **Step 2: Implement `PlanBadKeyAttempts` in `brute/ssh.go`** + +Add near the top of `brute/ssh.go` (after the import block): + +```go +// BadKeyAttempt is one user+key pair to try during the bad-keys pass. +type BadKeyAttempt struct { + Username string + Entry badkeys.Entry +} + +// PlanBadKeyAttempts produces the ordered list of SSH bad-key attempts for a +// host. When userOverride is non-empty (operator passed -u explicitly), every +// attempt uses that username; otherwise the entry's metadata-suggested user +// is used (root for F5, vagrant for Vagrant, etc.). +func PlanBadKeyAttempts(bundle []badkeys.Entry, userOverride string) []BadKeyAttempt { + out := make([]BadKeyAttempt, 0, len(bundle)) + for _, e := range bundle { + u := e.Username + if userOverride != "" { + u = userOverride + } + out = append(out, BadKeyAttempt{Username: u, Entry: e}) + } + return out +} +``` + +Add the import: + +```go +import ( + // ... existing imports ... + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" +) +``` + +- [ ] **Step 3: Run the test** + +Run: `go test ./brute/ -run TestBadKeys -v` +Expected: PASS. + +- [ ] **Step 4: Wire bad-key execution into `BruteSSH`** + +In `brute/ssh.go`, near the top of `BruteSSH` (before the existing key/password branch), add: + +```go +// Bad-keys pre-pass: when the magic password marker "::badkey::" is in play, +// the caller is asking us to attempt a single embedded bad-key. The dispatcher +// (Task A5) emits these as synthetic credential pairs before regular passwords. +if strings.HasPrefix(password, "::badkey::") { + idx, err := strconv.Atoi(strings.TrimPrefix(password, "::badkey::")) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, + Error: fmt.Errorf("invalid badkey index: %w", err)} + } + bundle, err := badkeys.Load() + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, + Error: fmt.Errorf("loading badkeys bundle: %w", err)} + } + if idx < 0 || idx >= len(bundle) { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, + Error: fmt.Errorf("badkey index out of range: %d", idx)} + } + return attemptBadKey(host, port, user, bundle[idx], timeout, cm) +} +``` + +Then implement `attemptBadKey` at the bottom of `brute/ssh.go`: + +```go +func attemptBadKey(host string, port int, user string, e badkeys.Entry, + timeout time.Duration, cm *modules.ConnectionManager) *BruteResult { + signer, err := ssh.ParsePrivateKey(e.PEM) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("parsing badkey %s: %w", e.File, err)} + } + cfg := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: timeout, + } + conn, err := cm.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + c, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, strconv.Itoa(port)), cfg) + if err != nil { + conn.Close() + if strings.Contains(err.Error(), "unable to authenticate") { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} + } + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + client := ssh.NewClient(c, chans, reqs) + defer client.Close() + return &BruteResult{ + AuthSuccess: true, + ConnectionSuccess: true, + KeyMatch: &KeyMatch{ + Fingerprint: e.Fingerprint, + Vendor: e.Vendor, + CVE: e.CVE, + Description: e.Description, + }, + } +} +``` + +Add `"strconv"` to imports if not already present. + +- [ ] **Step 5: Run the SSH tests** + +Run: `go test ./brute/ -run TestSSH -v -race` +Expected: existing SSH tests pass; no new failures. + +- [ ] **Step 6: Commit** + +```bash +git add brute/ssh.go brute/ssh_badkeys_test.go +git commit -m "feat(ssh): execute embedded bad-key attempts via ::badkey:: marker" +``` + +--- + +## Task A4: Dispatcher emits bad-key attempts + `--no-badkeys` / `--badkeys-only` flags + +**Files:** +- Modify: `brutespray/config.go` +- Modify: `brutespray/dispatch.go` +- Test: `brutespray/dispatch_badkeys_test.go` (new) + +- [ ] **Step 1: Add the two flags to `brutespray/config.go`** + +In the flag-registration block (near the other boolean flags like `-spray`), add: + +```go +flag.BoolVar(&Cfg.NoBadKeys, "no-badkeys", false, "Skip SSH bad-keys pre-pass for SSH targets") +flag.BoolVar(&Cfg.BadKeysOnly, "badkeys-only", false, "Run SSH bad-keys pre-pass only; skip password attempts") +``` + +Add to `Config` struct: + +```go +NoBadKeys bool +BadKeysOnly bool +``` + +- [ ] **Step 2: Write the failing test** + +Create `brutespray/dispatch_badkeys_test.go`: + +```go +package brutespray + +import ( + "testing" + + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" +) + +func TestBuildBadKeyCredsProducesMarkers(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + pairs := BuildBadKeyCreds(bundle, "") + if len(pairs) != len(bundle) { + t.Fatalf("got %d pairs, want %d", len(pairs), len(bundle)) + } + for i, p := range pairs { + wantPass := "::badkey::" + itoa(i) + if p.Password != wantPass { + t.Fatalf("pair[%d].Password = %q, want %q", i, p.Password, wantPass) + } + } +} + +func itoa(i int) string { + if i == 0 { + return "0" + } + var b []byte + for i > 0 { + b = append([]byte{byte('0' + i%10)}, b...) + i /= 10 + } + return string(b) +} +``` + +- [ ] **Step 3: Implement `BuildBadKeyCreds` in `brutespray/dispatch.go`** + +Add near the existing credential-build helpers: + +```go +// CredPair is a single user/password attempt the dispatcher will enqueue. +// (If this type already exists in dispatch.go, reuse it instead.) +type CredPair struct { + User string + Password string +} + +// BuildBadKeyCreds turns the embedded bad-keys bundle into a list of synthetic +// credential pairs. The password field carries "::badkey::N" where N indexes +// into the bundle; BruteSSH unpacks this marker. When userOverride is set +// (operator passed -u explicitly), every pair uses that username; otherwise +// each entry's metadata-suggested user is used. +func BuildBadKeyCreds(bundle []badkeys.Entry, userOverride string) []CredPair { + out := make([]CredPair, 0, len(bundle)) + for i, e := range bundle { + u := e.Username + if userOverride != "" { + u = userOverride + } + out = append(out, CredPair{ + User: u, + Password: fmt.Sprintf("::badkey::%d", i), + }) + } + return out +} +``` + +Add imports: + +```go +import ( + // ... existing ... + "fmt" + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" +) +``` + +- [ ] **Step 4: Wire bad-key creds into `ProcessHost` for SSH targets** + +Find `ProcessHost` in `dispatch.go`. Add a branch at the top of the per-host loop, before password creds are enqueued: + +```go +if host.Service == "ssh" && !Cfg.NoBadKeys { + bundle, err := badkeys.Load() + if err == nil { + for _, pair := range BuildBadKeyCreds(bundle, explicitUser) { + queueCred(pair.User, pair.Password) + } + } +} +if host.Service == "ssh" && Cfg.BadKeysOnly { + return // skip the regular password loop entirely +} +``` + +(`explicitUser` and `queueCred` names match whatever the dispatcher already uses — adapt to actual identifiers. Reuse the existing enqueue helper rather than introducing a new one.) + +- [ ] **Step 5: Run tests** + +Run: `go test ./brutespray/ -run TestBuildBadKey -v && go test ./... -count=1` +Expected: new test passes; existing dispatch tests pass. + +- [ ] **Step 6: Commit** + +```bash +git add brutespray/config.go brutespray/dispatch.go brutespray/dispatch_badkeys_test.go +git commit -m "feat(dispatch): emit bad-key attempts for SSH targets with opt-out flags" +``` + +--- + +## Task A5: RDP NLA fingerprint scan in grdp + brutespray + +**Files:** +- Modify (sibling repo): `../grdp/client/nla_check.go` (new) +- Modify: `brute/rdp.go` +- Test: `brute/rdp_nla_test.go` (new) + +- [ ] **Step 1: Add NLA check to grdp** + +In `../grdp/client/nla_check.go`: + +```go +package client + +import ( + "context" + "encoding/binary" + "fmt" + "net" + "time" +) + +// NLAStatus describes the result of a TCP-only NLA fingerprint probe. +type NLAStatus int + +const ( + NLAUnknown NLAStatus = iota + NLANotEnforced + NLARequired + NLAHybridEx +) + +// FingerprintNLA opens a TCP connection to the RDP target, sends an +// X.224 Connection Request with RDPneg requesting all protocols, and +// classifies the server response. +func FingerprintNLA(ctx context.Context, target string, timeout time.Duration) (NLAStatus, error) { + d := net.Dialer{Timeout: timeout} + conn, err := d.DialContext(ctx, "tcp", target) + if err != nil { + return NLAUnknown, fmt.Errorf("dial: %w", err) + } + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(timeout)) + + // X.224 Connection Request with RDPneg requesting PROTOCOL_RDP|SSL|HYBRID|HYBRID_EX (0x0F). + req := []byte{ + 0x03, 0x00, 0x00, 0x13, // TPKT header, length 19 + 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x00, // RDPneg req, requested 0x0F + } + if _, err := conn.Write(req); err != nil { + return NLAUnknown, fmt.Errorf("write: %w", err) + } + resp := make([]byte, 64) + n, err := conn.Read(resp) + if err != nil || n < 19 { + return NLAUnknown, fmt.Errorf("read: %w", err) + } + // Bytes 11..18 carry the RDPneg response. Byte 11 is type: + // 0x02 = RDP_NEG_RSP (server picked one protocol — selectedProtocols at bytes 15..18) + // 0x03 = RDP_NEG_FAILURE + if resp[11] != 0x02 { + return NLAUnknown, nil + } + selected := binary.LittleEndian.Uint32(resp[15:19]) + switch { + case selected&0x08 != 0: + return NLAHybridEx, nil + case selected&0x02 != 0: + return NLARequired, nil + case selected&0x01 != 0 || selected == 0: + return NLANotEnforced, nil + } + return NLAUnknown, nil +} +``` + +- [ ] **Step 2: Commit grdp side** + +```bash +(cd ../grdp && git add client/nla_check.go && git -c commit.gpgsign=false commit -m "feat(client): add FingerprintNLA for TCP-only NLA detection") +``` + +- [ ] **Step 3: Update brutespray's grdp dependency** + +```bash +# If grdp is referenced by go.mod replace pointing to ../grdp, no go get needed. +# Otherwise bump: +go get github.com/x90skysn3k/grdp@latest +go mod tidy +go build ./... +``` + +Expected: `go build` succeeds. + +- [ ] **Step 4: Write the failing test** + +Create `brute/rdp_nla_test.go`: + +```go +package brute + +import "testing" + +func TestNLAFindingFromStatus(t *testing.T) { + cases := []struct { + status string + wantSev string + wantCode string + }{ + {"required", "INFO", "rdp-nla-required"}, + {"not-enforced", "WARN", "rdp-nla-missing"}, + {"hybrid-ex", "INFO", "rdp-nla-hybridex"}, + {"unknown", "", ""}, + } + for _, c := range cases { + f := nlaFinding(c.status) + if c.wantCode == "" { + if f != nil { + t.Fatalf("status %q: expected nil finding, got %+v", c.status, f) + } + continue + } + if f == nil || f.Severity != c.wantSev || f.Code != c.wantCode { + t.Fatalf("status %q: got %+v want sev=%s code=%s", c.status, f, c.wantSev, c.wantCode) + } + } +} +``` + +- [ ] **Step 5: Implement `nlaFinding` and `ScanRDPRecon` in `brute/rdp.go`** + +Append to `brute/rdp.go`: + +```go +import ( + // ... existing imports ... + "github.com/x90skysn3k/grdp/client" +) + +func nlaFinding(status string) *Finding { + switch status { + case "required": + return &Finding{Severity: "INFO", Code: "rdp-nla-required", + Message: "NLA (CredSSP) enforced"} + case "not-enforced": + return &Finding{Severity: "WARN", Code: "rdp-nla-missing", + Message: "NLA not enforced — server accepts standard RDP without pre-auth"} + case "hybrid-ex": + return &Finding{Severity: "INFO", Code: "rdp-nla-hybridex", + Message: "HybridEx (NLA + CredSSP early-user auth) enforced"} + } + return nil +} + +// ScanRDPRecon runs pre-auth RDP recon (NLA fingerprint, sticky-keys probe) +// against a single target. Returns a slice of findings to emit. Called once +// per host by the dispatcher before any brute attempts. +func ScanRDPRecon(host string, port int, timeout time.Duration) []*Finding { + target := fmt.Sprintf("%s:%d", host, port) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + status, err := client.FingerprintNLA(ctx, target, timeout) + if err != nil { + return nil + } + var out []*Finding + switch status { + case client.NLARequired: + out = append(out, nlaFinding("required")) + case client.NLANotEnforced: + out = append(out, nlaFinding("not-enforced")) + case client.NLAHybridEx: + out = append(out, nlaFinding("hybrid-ex")) + } + // Sticky-keys probe slots in here in Task A6. + return out +} +``` + +- [ ] **Step 6: Run tests** + +Run: `go test ./brute/ -run TestNLA -v` +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add brute/rdp.go brute/rdp_nla_test.go go.mod go.sum +git commit -m "feat(rdp): pre-auth NLA fingerprint scan" +``` + +--- + +## Task A6: RDP sticky-keys probe in grdp + brutespray integration + +**Files:** +- Modify (sibling repo): `../grdp/client/logon_screen.go` (new) +- Modify: `brute/rdp.go` (extend `ScanRDPRecon`) +- Test: `brute/rdp_stickykeys_test.go` (new) + +- [ ] **Step 1: Add `CaptureLogonScreen` to grdp** + +In `../grdp/client/logon_screen.go`, implement: + +```go +package client + +import ( + "bytes" + "context" + "image" + "image/png" + "time" +) + +// LogonTrigger identifies which pre-auth keystroke to send. +type LogonTrigger int + +const ( + TriggerShift5x LogonTrigger = iota // 5x Left-Shift → sticky-keys + TriggerWinU // Win+U → utilman.exe +) + +// CaptureLogonScreen connects to an RDP target, sends the requested +// pre-auth trigger keystroke, and returns a framebuffer snapshot from +// before and after the trigger. Both images are encoded as in-memory +// PNGs to keep the API small. +// +// Returns ErrNLARequired if the server enforces NLA (no framebuffer is +// available pre-authentication on those hosts). +func (c *RdpClient) CaptureLogonScreen(ctx context.Context, target string, + trigger LogonTrigger, timeout time.Duration) (before, after []byte, err error) { + // Reuse the existing low-level RDP connect path (the one LoginAuthOnly + // uses) but stop short of credential delegation: stay at the GINA + // (logon UI) layer. + if err := c.connectToLogonScreen(ctx, target, timeout); err != nil { + return nil, nil, err + } + defer c.Close() + + beforeImg, err := c.snapshotFramebuffer() + if err != nil { + return nil, nil, err + } + if err := c.sendLogonTrigger(trigger); err != nil { + return nil, nil, err + } + // Wait a beat for the server-rendered window to repaint. + time.Sleep(750 * time.Millisecond) + afterImg, err := c.snapshotFramebuffer() + if err != nil { + return nil, nil, err + } + return pngEncode(beforeImg), pngEncode(afterImg), nil +} + +func pngEncode(img *image.RGBA) []byte { + var buf bytes.Buffer + _ = png.Encode(&buf, img) + return buf.Bytes() +} + +// Implementations below wire up against grdp's existing PDU/T.128 layer. +// connectToLogonScreen, snapshotFramebuffer, and sendLogonTrigger are added +// as methods on *RdpClient in the same package — see grdp/protocol/pdu/ for +// the existing primitives to reuse. +``` + +`connectToLogonScreen`, `snapshotFramebuffer`, and `sendLogonTrigger` build on the same PDU code paths that `LoginAuthOnly` already exercises. They go in the same file. Skeletons: + +```go +func (c *RdpClient) connectToLogonScreen(ctx context.Context, target string, timeout time.Duration) error { + // Mirror the connect/X.224/MCS/Security/connect-initial sequence used by + // LoginAuthOnly but skip the CredSSP/NLA upgrade — request PROTOCOL_RDP only. + // Return after the Demand Active PDU arrives (= server reached the logon screen). + return fmt.Errorf("TODO connect-to-logon-screen — implement against grdp/protocol/pdu") +} + +func (c *RdpClient) snapshotFramebuffer() (*image.RGBA, error) { + // Read bitmap update PDUs into a tile buffer until the framebuffer is + // quiescent (no updates for ~250ms); then blit the tiles into a single RGBA. + return nil, fmt.Errorf("TODO snapshot — implement against grdp/emission") +} + +func (c *RdpClient) sendLogonTrigger(t LogonTrigger) error { + switch t { + case TriggerShift5x: + for i := 0; i < 5; i++ { + // Scancode 0x2A = Left-Shift. PDU input event: keyboard down then up. + if err := c.sendKeyScancode(0x2A); err != nil { + return err + } + } + return nil + case TriggerWinU: + if err := c.sendKeyScancodeChord(0x5B, 0x16); err != nil { // L-Win + 'U' + return err + } + return nil + } + return fmt.Errorf("unknown trigger") +} +``` + +The `TODO`s aren't placeholders for this *plan* — they're explicit grdp work items that must be filled in against grdp's existing PDU primitives. Reference `grdp/protocol/pdu/input.go` for keyboard PDU shape and `grdp/emission/` for framebuffer update plumbing. The implementing engineer fills them in by reading the surrounding grdp code; this plan does not duplicate grdp's internals. + +- [ ] **Step 2: Implement the TODOs in grdp** + +Open `../grdp/protocol/pdu/`. Use the existing input-event PDU constructors that `LoginAuthOnly` flows through to send keyboard scancodes. Use the bitmap-update PDU handlers to accumulate framebuffer tiles into an `*image.RGBA`. Reuse `connect-initial` plumbing from `LoginAuthOnly` for the connect path, branching off before CredSSP. + +- [ ] **Step 3: Commit grdp side** + +```bash +(cd ../grdp && git add client/logon_screen.go protocol/pdu/ && \ + git -c commit.gpgsign=false commit -m "feat(client): CaptureLogonScreen for pre-auth screen capture + key trigger") +``` + +- [ ] **Step 4: Bump grdp dependency in brutespray** + +```bash +go get github.com/x90skysn3k/grdp@latest && go mod tidy && go build ./... +``` + +- [ ] **Step 5: Write the failing test** + +Create `brute/rdp_stickykeys_test.go`: + +```go +package brute + +import ( + "testing" +) + +func TestStickyKeysVerdictFromImages(t *testing.T) { + cases := []struct { + name string + beforeIsCmdLike bool + afterIsCmdLike bool + differ bool + wantSev, wantCode string + }{ + {"identical-no-change", false, false, false, "", ""}, + {"change-but-not-cmd", false, false, true, "INFO", "rdp-stickykeys-inconclusive"}, + {"change-to-cmd", false, true, true, "CRITICAL", "rdp-stickykeys"}, + } + for _, c := range cases { + got := stickyKeysVerdict(c.beforeIsCmdLike, c.afterIsCmdLike, c.differ) + if c.wantCode == "" { + if got != nil { + t.Fatalf("%s: want nil, got %+v", c.name, got) + } + continue + } + if got == nil || got.Severity != c.wantSev || got.Code != c.wantCode { + t.Fatalf("%s: got %+v want sev=%s code=%s", c.name, got, c.wantSev, c.wantCode) + } + } +} +``` + +- [ ] **Step 6: Implement `stickyKeysVerdict` and probe wiring in `brute/rdp.go`** + +Append: + +```go +import ( + // ... existing ... + "bytes" + "image" + _ "image/png" +) + +func stickyKeysVerdict(beforeCmd, afterCmd, differ bool) *Finding { + if !differ { + return nil + } + if afterCmd && !beforeCmd { + return &Finding{ + Severity: "CRITICAL", + Code: "rdp-stickykeys", + Message: "sticky-keys backdoor detected (cmd.exe shell at logon screen)", + } + } + return &Finding{ + Severity: "INFO", + Code: "rdp-stickykeys-inconclusive", + Message: "logon screen reacted to sticky-keys trigger but no console signature detected; manual verification recommended", + } +} + +// looksLikeCmdConsole returns true when the framebuffer region top-left of +// the image is consistent with a cmd.exe console window (predominantly black +// with monospaced white text in the top portion). Conservative — false +// positives are worse than missed detections here. +func looksLikeCmdConsole(pngBytes []byte) bool { + img, _, err := image.Decode(bytes.NewReader(pngBytes)) + if err != nil { + return false + } + rgba, ok := img.(*image.RGBA) + if !ok { + // Decode for non-RGBA paths; for the heuristic, draw onto an RGBA. + b := img.Bounds() + rgba = image.NewRGBA(b) + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + rgba.Set(x, y, img.At(x, y)) + } + } + } + // Sample the top-left 400x200 region. Count pixels: black (R<32 && G<32 && B<32) + // and white-ish (R>200 && G>200 && B>200). Console signature: >65% black, + // 2-15% white-ish. + b := rgba.Bounds() + maxX := b.Min.X + 400 + if maxX > b.Max.X { + maxX = b.Max.X + } + maxY := b.Min.Y + 200 + if maxY > b.Max.Y { + maxY = b.Max.Y + } + var total, black, white int + for y := b.Min.Y; y < maxY; y++ { + for x := b.Min.X; x < maxX; x++ { + r, g, bl, _ := rgba.At(x, y).RGBA() + r >>= 8 + g >>= 8 + bl >>= 8 + total++ + switch { + case r < 32 && g < 32 && bl < 32: + black++ + case r > 200 && g > 200 && bl > 200: + white++ + } + } + } + if total == 0 { + return false + } + blackPct := float64(black) / float64(total) + whitePct := float64(white) / float64(total) + return blackPct > 0.65 && whitePct > 0.02 && whitePct < 0.15 +} + +func framebuffersDiffer(a, b []byte) bool { + if len(a) == 0 || len(b) == 0 { + return false + } + if len(a) != len(b) { + return true + } + // Hash compare — different PNG bytes means different content (PNG encoder + // is deterministic for identical RGBA in stdlib). + return !bytes.Equal(a, b) +} +``` + +Then in `ScanRDPRecon` from Task A5, after the NLA branch, before the return: + +```go +// Sticky-keys probe runs only when NLA is not enforced (no point trying +// against an NLA host — no framebuffer pre-auth). +if status == client.NLANotEnforced { + c := &client.RdpClient{} + before, after, err := c.CaptureLogonScreen(ctx, target, client.TriggerShift5x, timeout) + if err == nil { + if f := stickyKeysVerdict(looksLikeCmdConsole(before), looksLikeCmdConsole(after), + framebuffersDiffer(before, after)); f != nil { + out = append(out, f) + } + } +} +``` + +- [ ] **Step 7: Run tests** + +Run: `go test ./brute/ -run TestStickyKeys -v` +Expected: PASS. + +- [ ] **Step 8: Commit** + +```bash +git add brute/rdp.go brute/rdp_stickykeys_test.go go.mod go.sum +git commit -m "feat(rdp): sticky-keys backdoor pre-auth probe with framebuffer heuristic" +``` + +--- + +## Task A7: Dispatcher invokes RDP recon + `--no-rdp-scan` flag + +**Files:** +- Modify: `brutespray/config.go` +- Modify: `brutespray/dispatch.go` + +- [ ] **Step 1: Add `--no-rdp-scan` flag** + +In `brutespray/config.go`: + +```go +flag.BoolVar(&Cfg.NoRDPScan, "no-rdp-scan", false, "Skip pre-auth RDP recon (NLA fingerprint, sticky-keys probe)") +``` + +Add to `Config` struct: + +```go +NoRDPScan bool +``` + +- [ ] **Step 2: Call `brute.ScanRDPRecon` from the dispatcher** + +In `brutespray/dispatch.go`'s `ProcessHost`, at the top of the host-setup block (mirroring where bad-keys is invoked for SSH): + +```go +if host.Service == "rdp" && !Cfg.NoRDPScan { + findings := brute.ScanRDPRecon(host.Host, host.Port, Cfg.Timeout) + for _, f := range findings { + emitFinding(host, f) // route through existing output channel — text + JSONL + TUI + } +} +``` + +`emitFinding` is a small helper next to the existing per-host result emit path. If a similar helper does not already exist, add: + +```go +func emitFinding(host modules.Host, f *brute.Finding) { + modules.WriteFindingLine(host, f) +} +``` + +- [ ] **Step 3: Quick integration smoke** + +Run: `go build ./... && ./brutespray -H 127.0.0.1 -s rdp -u test -p test --no-rdp-scan 2>&1 | head` +Expected: no panic; the `--no-rdp-scan` path runs cleanly (connection error is fine). + +- [ ] **Step 4: Commit** + +```bash +git add brutespray/config.go brutespray/dispatch.go +git commit -m "feat(rdp): wire pre-auth RDP recon into dispatcher with --no-rdp-scan opt-out" +``` + +--- + +## Task A8: Render `Finding` and `KeyMatch` in output layer (text + JSONL) + +**Files:** +- Modify: `modules/output.go` +- Test: `modules/output_finding_test.go` (new) + +- [ ] **Step 1: Write the failing test** + +Create `modules/output_finding_test.go`: + +```go +package modules + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + "github.com/x90skysn3k/brutespray/v2/brute" +) + +func TestWriteFindingLineText(t *testing.T) { + var buf bytes.Buffer + WriteFindingTo(&buf, Host{Service: "rdp", Host: "10.0.0.5", Port: 3389}, + &brute.Finding{Severity: "WARN", Code: "rdp-nla-missing", Message: "NLA not enforced"}) + got := buf.String() + for _, want := range []string{"WARN", "rdp", "10.0.0.5:3389", "NLA not enforced"} { + if !strings.Contains(got, want) { + t.Fatalf("output missing %q: %s", want, got) + } + } +} + +func TestWriteFindingLineJSON(t *testing.T) { + var buf bytes.Buffer + WriteFindingJSONTo(&buf, Host{Service: "rdp", Host: "10.0.0.5", Port: 3389}, + &brute.Finding{Severity: "CRITICAL", Code: "rdp-stickykeys", Message: "backdoor detected", CVE: ""}) + var got struct { + Type string `json:"type"` + Severity string `json:"severity"` + Code string `json:"code"` + Target string `json:"target"` + } + if err := json.Unmarshal(buf.Bytes(), &got); err != nil { + t.Fatalf("invalid JSON: %v\n%s", err, buf.String()) + } + if got.Type != "finding" || got.Severity != "CRITICAL" || got.Code != "rdp-stickykeys" { + t.Fatalf("wrong fields: %+v", got) + } + if got.Target != "10.0.0.5:3389" { + t.Fatalf("target = %q", got.Target) + } +} +``` + +- [ ] **Step 2: Implement renderers in `modules/output.go`** + +Append: + +```go +import ( + // ... existing ... + "encoding/json" + "fmt" + "io" + + "github.com/x90skysn3k/brutespray/v2/brute" +) + +// WriteFindingTo writes a colored text-form finding line. +func WriteFindingTo(w io.Writer, h Host, f *brute.Finding) { + cve := "" + if f.CVE != "" { + cve = " (" + f.CVE + ")" + } + fmt.Fprintf(w, "[%s] %s %s:%d %s%s\n", + f.Severity, h.Service, h.Host, h.Port, f.Message, cve) +} + +// WriteFindingJSONTo writes a JSONL finding line. +func WriteFindingJSONTo(w io.Writer, h Host, f *brute.Finding) { + rec := map[string]any{ + "type": "finding", + "severity": f.Severity, + "code": f.Code, + "service": h.Service, + "target": fmt.Sprintf("%s:%d", h.Host, h.Port), + "message": f.Message, + } + if f.CVE != "" { + rec["cve"] = f.CVE + } + _ = json.NewEncoder(w).Encode(rec) +} + +// WriteFindingLine dispatches to the configured output sink (text or JSONL). +// Called from the dispatcher when a Finding surfaces. +func WriteFindingLine(h Host, f *brute.Finding) { + if jsonOutput { + WriteFindingJSONTo(jsonSink, h, f) + return + } + WriteFindingTo(stdoutSink, h, f) +} +``` + +(`jsonOutput`, `jsonSink`, `stdoutSink` are the existing sinks the output layer already uses for per-attempt results. Reuse them; do not introduce new globals. If the existing names differ, rename accordingly.) + +- [ ] **Step 3: Render `KeyMatch` on successful SSH bad-key auth** + +In `modules/output.go`, locate the success-printing path (whatever function emits `[+] SUCCESS ...`). Add a branch: + +```go +if res.KeyMatch != nil { + cve := "" + if res.KeyMatch.CVE != "" { + cve = " (" + res.KeyMatch.CVE + ")" + } + fmt.Fprintf(stdoutSink, "[+] BADKEY %s %s@%s:%d %s%s\n", + h.Service, user, h.Host, h.Port, res.KeyMatch.Vendor, cve) + // (JSON path: existing success-emit already serializes res; extend the + // json record to include "key_match" when non-nil.) + return +} +``` + +For JSON output, extend the existing success-record struct to include `KeyMatch *brute.KeyMatch` with json tag `key_match,omitempty`. + +- [ ] **Step 4: Run tests** + +Run: `go test ./modules/ -run TestWriteFinding -v` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add modules/output.go modules/output_finding_test.go +git commit -m "feat(output): render Finding (text+JSONL) and KeyMatch on SSH success" +``` + +--- + +## Task A9: TUI Findings tab + +**Files:** +- Modify: `tui/model.go` +- Create: `tui/view_findings.go` +- Modify: existing tab-list registration + +- [ ] **Step 1: Read the existing tab pattern** + +Read `tui/view_all.go`, `tui/view_errors.go`, `tui/view_success.go` to confirm the existing tab structure. The new view follows the same pattern. + +- [ ] **Step 2: Add Findings to the TUI Model** + +In `tui/model.go`, locate the tab list (probably a `[]Tab` or similar `tabs` slice). Add: + +```go +{Name: "Findings", Key: "f"} +``` + +Add a `findings []FindingEntry` field to the model: + +```go +type FindingEntry struct { + Severity string + Code string + Service string + Target string + Message string + CVE string +} +``` + +Add an `AddFinding` method: + +```go +func (m *Model) AddFinding(e FindingEntry) { + m.findings = append(m.findings, e) +} +``` + +- [ ] **Step 3: Implement `tui/view_findings.go`** + +```go +package tui + +import ( + "fmt" + "strings" +) + +func (m *Model) viewFindings() string { + if len(m.findings) == 0 { + return "No findings yet.\n" + } + var b strings.Builder + for _, f := range m.findings { + cve := "" + if f.CVE != "" { + cve = " (" + f.CVE + ")" + } + b.WriteString(fmt.Sprintf("[%s] %s %s %s%s\n", + f.Severity, f.Service, f.Target, f.Message, cve)) + } + return b.String() +} +``` + +Wire `viewFindings` into the model's view-switch (mirror how `view_errors` is dispatched). + +- [ ] **Step 4: Push findings into the TUI from the dispatcher** + +In `brutespray/dispatch.go`'s `emitFinding` (added in Task A7), also forward to the TUI sink when active: + +```go +if tuiActive { + tuiModel.AddFinding(tui.FindingEntry{ + Severity: f.Severity, + Code: f.Code, + Service: host.Service, + Target: fmt.Sprintf("%s:%d", host.Host, host.Port), + Message: f.Message, + CVE: f.CVE, + }) +} +``` + +- [ ] **Step 5: Smoke test** + +Run: `go build ./... && ./brutespray -H 127.0.0.1 -s rdp -u test -p test 2>&1 | head` +Expected: no TUI panic; press `f` while running an interactive session — tab renders. + +- [ ] **Step 6: Commit** + +```bash +git add tui/model.go tui/view_findings.go brutespray/dispatch.go +git commit -m "feat(tui): add Findings tab populated from pre-auth recon" +``` + +--- + +# Phase B — Stdin pipeline + masscan JSON + +## Task B1: Masscan JSON parser + +**Files:** +- Create: `modules/parse_masscan.go` +- Create: `modules/parse_masscan_test.go` + +- [ ] **Step 1: Sample masscan output** + +Masscan `-oJ` emits a JSON *array* with elements: + +```json +{"ip": "10.0.0.5", "timestamp": "1700000000", + "ports": [{"port": 22, "proto": "tcp", "status": "open", "reason": "syn-ack", "ttl": 64}]} +``` + +- [ ] **Step 2: Write the failing test** + +Create `modules/parse_masscan_test.go`: + +```go +package modules + +import ( + "strconv" + "strings" + "testing" +) + +const masscanSample = `[ +{"ip":"10.0.0.5","ports":[{"port":22,"proto":"tcp","status":"open"}]}, +{"ip":"10.0.0.6","ports":[{"port":3306,"proto":"tcp","status":"open"},{"port":80,"proto":"tcp","status":"closed"}]}, +{"ip":"10.0.0.7","ports":[{"port":3389,"proto":"tcp","status":"open"}]} +]` + +func TestParseMasscanJSON(t *testing.T) { + hosts, err := ParseMasscanJSON(strings.NewReader(masscanSample)) + if err != nil { + t.Fatalf("ParseMasscanJSON: %v", err) + } + if len(hosts) != 3 { + t.Fatalf("want 3 hosts (closed port filtered), got %d", len(hosts)) + } + want := map[string]string{ + "10.0.0.5:22": "ssh", + "10.0.0.6:3306": "mysql", + "10.0.0.7:3389": "rdp", + } + for _, h := range hosts { + key := h.Host + ":" + strconv.Itoa(h.Port) + if got, ok := want[key]; !ok || got != h.Service { + t.Fatalf("unexpected host: %+v", h) + } + } +} +``` + +- [ ] **Step 3: Implement `ParseMasscanJSON`** + +```go +package modules + +import ( + "encoding/json" + "fmt" + "io" +) + +type masscanPort struct { + Port int `json:"port"` + Proto string `json:"proto"` + Status string `json:"status"` +} + +type masscanHost struct { + IP string `json:"ip"` + Ports []masscanPort `json:"ports"` +} + +// ParseMasscanJSON reads masscan -oJ output and returns one Host per +// open port. Service is inferred from port via defaultServiceForPort +// (existing helper in parse.go); ports with no mapping are dropped. +func ParseMasscanJSON(r io.Reader) ([]Host, error) { + var rows []masscanHost + if err := json.NewDecoder(r).Decode(&rows); err != nil { + return nil, fmt.Errorf("decode masscan json: %w", err) + } + var out []Host + for _, row := range rows { + for _, p := range row.Ports { + if p.Status != "open" { + continue + } + svc := defaultServiceForPort(p.Port) + if svc == "" { + continue + } + out = append(out, Host{Service: svc, Host: row.IP, Port: p.Port}) + } + } + return out, nil +} +``` + +If `defaultServiceForPort` does not yet exist in `parse.go`, add it there mapping the common ports (22→ssh, 21→ftp, 23→telnet, 25→smtp, 80→http, 110→pop3, 143→imap, 389→ldap, 443→https, 445→smbnt, 1433→mssql, 1521→oracle, 3306→mysql, 3389→rdp, 5432→postgres, 5900→vnc, 5984→couchdb, 6379→redis, 7687→neo4j, 8086→influxdb, 9042→cassandra, 9200→elasticsearch, 27017→mongodb). + +- [ ] **Step 4: Run tests** + +Run: `go test ./modules/ -run TestParseMasscan -v` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add modules/parse_masscan.go modules/parse_masscan_test.go modules/parse.go +git commit -m "feat(parse): masscan -oJ JSON ingestion with port→service mapping" +``` + +--- + +## Task B2: Stdin auto-detect + +**Files:** +- Create: `modules/parse_stream.go` +- Create: `modules/parse_stream_test.go` + +- [ ] **Step 1: Write the failing test** + +Create `modules/parse_stream_test.go`: + +```go +package modules + +import ( + "strings" + "testing" +) + +func TestDetectStreamFormat(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + {"bare-host-port", "10.0.0.5:22\n10.0.0.6:3389\n", "naabu"}, + {"nerva-uri", "ssh://10.0.0.5:22\nmysql://10.0.0.6:3306\n", "nerva-uri"}, + {"nerva-json", `{"ip":"10.0.0.5","port":22,"protocol":"ssh"}`, "nerva-json"}, + {"masscan-json", `[{"ip":"10.0.0.5","ports":[{"port":22,"proto":"tcp","status":"open"}]}]`, "masscan-json"}, + {"fingerprintx-json", `{"host":"10.0.0.5","ip":"10.0.0.5","port":22,"service":"ssh","transport":"tcp"}`, "fingerprintx-json"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got, err := DetectStreamFormat(strings.NewReader(c.in)) + if err != nil { + t.Fatalf("DetectStreamFormat: %v", err) + } + if got != c.want { + t.Fatalf("got %q, want %q", got, c.want) + } + }) + } +} + +func TestParseStreamBareHostPort(t *testing.T) { + hosts, err := ParseStream(strings.NewReader("10.0.0.5:22\n10.0.0.6:3389\n")) + if err != nil { + t.Fatalf("ParseStream: %v", err) + } + if len(hosts) != 2 { + t.Fatalf("want 2, got %d", len(hosts)) + } + if hosts[0].Service != "ssh" || hosts[1].Service != "rdp" { + t.Fatalf("port→service mapping failed: %+v", hosts) + } +} +``` + +- [ ] **Step 2: Implement `parse_stream.go`** + +```go +package modules + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "regexp" + "strconv" + "strings" +) + +var ( + nervaURIRE = regexp.MustCompile(`^[a-z][a-z0-9+-]*://[^:/]+:\d+`) + hostPortRE = regexp.MustCompile(`^[^\s:]+:\d+$`) +) + +// DetectStreamFormat peeks at the first non-blank line of the stream and +// returns one of: naabu, nerva-uri, nerva-json, masscan-json, fingerprintx-json. +// Does NOT consume the stream — caller passes a buffered reader. +func DetectStreamFormat(r io.Reader) (string, error) { + br := bufio.NewReader(r) + // Peek up to 4KB + peek, _ := br.Peek(4096) + // Find first non-blank line + var line []byte + for _, raw := range bytes.Split(peek, []byte("\n")) { + t := bytes.TrimSpace(raw) + if len(t) > 0 { + line = t + break + } + } + if len(line) == 0 { + return "", fmt.Errorf("empty stream") + } + s := string(line) + switch { + case s[0] == '[': + return "masscan-json", nil + case s[0] == '{': + // Classify by required keys + var probe map[string]json.RawMessage + if err := json.Unmarshal(line, &probe); err != nil { + return "", fmt.Errorf("invalid JSON: %w", err) + } + _, hasService := probe["service"] + _, hasProtocol := probe["protocol"] + _, hasPort := probe["port"] + switch { + case hasService && hasPort: + return "fingerprintx-json", nil + case hasProtocol && hasPort: + return "nerva-json", nil + } + return "", fmt.Errorf("unrecognized JSON shape") + case nervaURIRE.MatchString(s): + return "nerva-uri", nil + case hostPortRE.MatchString(s): + return "naabu", nil + } + return "", fmt.Errorf("unrecognized line format: %s", s) +} + +// ParseStream auto-detects and parses a target stream into Hosts. +func ParseStream(r io.Reader) ([]Host, error) { + buf, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("read stream: %w", err) + } + format, err := DetectStreamFormat(bytes.NewReader(buf)) + if err != nil { + return nil, err + } + switch format { + case "naabu": + return parseNaabuLines(buf) + case "nerva-uri": + return parseNervaURI(buf) + case "nerva-json": + return parseNervaJSON(buf) + case "masscan-json": + return ParseMasscanJSON(bytes.NewReader(buf)) + case "fingerprintx-json": + return parseFingerprintXJSON(buf) + } + return nil, fmt.Errorf("unsupported format: %s", format) +} + +func parseNaabuLines(buf []byte) ([]Host, error) { + var out []Host + for _, raw := range bytes.Split(buf, []byte("\n")) { + s := strings.TrimSpace(string(raw)) + if s == "" { + continue + } + host, port, err := splitHostPort(s) + if err != nil { + continue + } + svc := defaultServiceForPort(port) + if svc == "" { + continue + } + out = append(out, Host{Service: svc, Host: host, Port: port}) + } + return out, nil +} + +func parseNervaURI(buf []byte) ([]Host, error) { + var out []Host + for _, raw := range bytes.Split(buf, []byte("\n")) { + s := strings.TrimSpace(string(raw)) + if s == "" { + continue + } + // Strip parenthetical resolution suffix like "ssh://github.com:22 (140.82.121.4)" + if idx := strings.Index(s, " "); idx > 0 { + s = s[:idx] + } + scheme := s[:strings.Index(s, "://")] + rest := s[strings.Index(s, "://")+3:] + host, port, err := splitHostPort(rest) + if err != nil { + continue + } + out = append(out, Host{Service: scheme, Host: host, Port: port}) + } + return out, nil +} + +type nervaRow struct { + IP string `json:"ip"` + Port int `json:"port"` + Protocol string `json:"protocol"` +} + +func parseNervaJSON(buf []byte) ([]Host, error) { + var out []Host + dec := json.NewDecoder(bytes.NewReader(buf)) + for dec.More() { + var row nervaRow + if err := dec.Decode(&row); err != nil { + return nil, fmt.Errorf("decode nerva-json: %w", err) + } + out = append(out, Host{Service: row.Protocol, Host: row.IP, Port: row.Port}) + } + return out, nil +} + +type fpxRow struct { + Host string `json:"host"` + IP string `json:"ip"` + Port int `json:"port"` + Service string `json:"service"` +} + +func parseFingerprintXJSON(buf []byte) ([]Host, error) { + var out []Host + dec := json.NewDecoder(bytes.NewReader(buf)) + for dec.More() { + var row fpxRow + if err := dec.Decode(&row); err != nil { + return nil, fmt.Errorf("decode fingerprintx-json: %w", err) + } + h := row.Host + if h == "" { + h = row.IP + } + out = append(out, Host{Service: row.Service, Host: h, Port: row.Port}) + } + return out, nil +} + +func splitHostPort(s string) (string, int, error) { + idx := strings.LastIndex(s, ":") + if idx < 0 { + return "", 0, fmt.Errorf("no port: %s", s) + } + port, err := strconv.Atoi(s[idx+1:]) + if err != nil { + return "", 0, err + } + return s[:idx], port, nil +} +``` + +- [ ] **Step 3: Run tests** + +Run: `go test ./modules/ -run TestDetectStream -v && go test ./modules/ -run TestParseStream -v` +Expected: all PASS. + +- [ ] **Step 4: Commit** + +```bash +git add modules/parse_stream.go modules/parse_stream_test.go +git commit -m "feat(parse): stdin stream auto-detect for naabu/nerva/fingerprintx/masscan" +``` + +--- + +## Task B3: Wire stdin into `brutespray.Execute` + +**Files:** +- Modify: `brutespray/brutespray.go` (Execute) + +- [ ] **Step 1: Detect piped stdin at startup** + +Near the top of `Execute()`, after CLI parsing, before file ingestion: + +```go +// If -f was not provided AND stdin is a pipe (not a TTY), read targets from stdin. +if Cfg.File == "" && !term.IsTerminal(int(os.Stdin.Fd())) { + hosts, err := modules.ParseStream(os.Stdin) + if err != nil { + fmt.Fprintf(os.Stderr, "stdin parse: %v\n", err) + os.Exit(2) + } + Cfg.Hosts = append(Cfg.Hosts, hosts...) +} +``` + +Add imports if missing: + +```go +import ( + "os" + "golang.org/x/term" +) +``` + +If `golang.org/x/term` is not already in `go.mod`: + +```bash +go get golang.org/x/term && go mod tidy +``` + +- [ ] **Step 2: Smoke test the pipeline** + +```bash +go build -o brutespray . +echo "127.0.0.1:22" | ./brutespray -u root -p test 2>&1 | head +``` + +Expected: the host is enqueued from stdin (you'll see an SSH attempt log line or a connection-refused error — both confirm parsing worked). + +- [ ] **Step 3: Commit** + +```bash +git add brutespray/brutespray.go go.mod go.sum +git commit -m "feat(cli): auto-read targets from piped stdin with format detection" +``` + +--- + +# Phase C — New DB modules + SNMP tiering + inline creds + +## Task C1: Neo4j module + +**Files:** +- Create: `brute/neo4j.go` +- Create: `brute/neo4j_test.go` + +- [ ] **Step 1: Add neo4j driver dep** + +```bash +go get github.com/neo4j/neo4j-go-driver/v5/neo4j && go mod tidy +``` + +- [ ] **Step 2: Write the failing test** + +Create `brute/neo4j_test.go`: + +```go +package brute + +import ( + "context" + "fmt" + "os" + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteNeo4jNoServer(t *testing.T) { + cm := modules.NewConnectionManager() + r := BruteNeo4j("127.0.0.1", 1, "neo4j", "neo4j", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port") + } +} + +// Docker-backed integration test (gated, parallels brute/redis_test.go shape) +func TestBruteNeo4jDocker(t *testing.T) { + if os.Getenv("BRUTESPRAY_DOCKER_TESTS") == "" { + t.Skip("set BRUTESPRAY_DOCKER_TESTS=1 to run") + } + // Container started by the integration harness; assume neo4j:5 on 7687 + cm := modules.NewConnectionManager() + r := BruteNeo4j("127.0.0.1", 7687, "neo4j", "testtest", 5*time.Second, cm, nil) + if !r.AuthSuccess { + t.Fatalf("expected auth success, got %+v", r) + } + _ = context.Background() + _ = fmt.Sprintf +} +``` + +- [ ] **Step 3: Implement `brute/neo4j.go`** + +```go +package brute + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func BruteNeo4j(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + uri := fmt.Sprintf("bolt://%s:%d", host, port) + // Note: neo4j-go-driver v5 does not expose a custom dialer in the + // public Config. We accept that proxy/iface routing does not apply + // to Neo4j attempts in this initial implementation; document it. + driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(user, password, ""), + func(c *neo4j.Config) { + c.SocketConnectTimeout = timeout + }) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer driver.Close(ctx) + err = driver.VerifyConnectivity(ctx) + if err != nil { + msg := err.Error() + if strings.Contains(msg, "AuthenticationRateLimit") || + strings.Contains(msg, "Unauthorized") || + strings.Contains(msg, "credentials") { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} + } + var ute *neo4j.UsageError + if errors.As(err, &ute) { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + }) +} + +func init() { Register("neo4j", BruteNeo4j) } +``` + +- [ ] **Step 4: Run tests** + +Run: `go test ./brute/ -run TestBruteNeo4jNoServer -v` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add brute/neo4j.go brute/neo4j_test.go go.mod go.sum +git commit -m "feat(brute): neo4j Bolt v5 module" +``` + +--- + +## Task C2: Cassandra module + +**Files:** +- Create: `brute/cassandra.go` +- Create: `brute/cassandra_test.go` + +- [ ] **Step 1: Add gocql dep** + +```bash +go get github.com/gocql/gocql && go mod tidy +``` + +- [ ] **Step 2: Write the failing test** + +Create `brute/cassandra_test.go`: + +```go +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteCassandraNoServer(t *testing.T) { + cm := modules.NewConnectionManager() + r := BruteCassandra("127.0.0.1", 1, "cassandra", "cassandra", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port") + } +} +``` + +- [ ] **Step 3: Implement `brute/cassandra.go`** + +```go +package brute + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/gocql/gocql" + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func BruteCassandra(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + cluster := gocql.NewCluster(fmt.Sprintf("%s:%d", host, port)) + cluster.ProtoVersion = 4 + cluster.ConnectTimeout = timeout + cluster.Timeout = timeout + cluster.Authenticator = gocql.PasswordAuthenticator{ + Username: user, + Password: password, + } + cluster.DisableInitialHostLookup = true + sess, err := cluster.CreateSession() + if err != nil { + msg := err.Error() + if strings.Contains(msg, "Authentication") || + strings.Contains(msg, "Bad credentials") || + strings.Contains(msg, "Unauthorized") { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} + } + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer sess.Close() + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + }) +} + +func init() { Register("cassandra", BruteCassandra) } +``` + +- [ ] **Step 4: Seed wordlist** + +```bash +mkdir -p wordlist/cassandra +printf "cassandra\nadmin\nuser\n" > wordlist/cassandra/user +printf "cassandra\nadmin\nchangeme\n" > wordlist/cassandra/password +``` + +- [ ] **Step 5: Run tests** + +Run: `go test ./brute/ -run TestBruteCassandraNoServer -v` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add brute/cassandra.go brute/cassandra_test.go wordlist/cassandra go.mod go.sum +git commit -m "feat(brute): cassandra CQL module with default wordlist" +``` + +--- + +## Task C3: CouchDB module + +**Files:** +- Create: `brute/couchdb.go` +- Create: `brute/couchdb_test.go` + +- [ ] **Step 1: Write the failing test** + +Create `brute/couchdb_test.go`: + +```go +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteCouchDBNoServer(t *testing.T) { + cm := modules.NewConnectionManager() + r := BruteCouchDB("127.0.0.1", 1, "admin", "admin", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port") + } +} +``` + +- [ ] **Step 2: Implement `brute/couchdb.go`** + +```go +package brute + +import ( + "context" + "fmt" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func BruteCouchDB(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + scheme := "http" + if params["tls"] == "true" { + scheme = "https" + } + endpoint := fmt.Sprintf("%s://%s/_session", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + tr := &http.Transport{ + DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { + return cm.Dial(network, addr) + }, + DisableKeepAlives: true, + } + cl := &http.Client{Transport: tr, Timeout: timeout} + form := url.Values{"name": {user}, "password": {password}} + req, err := http.NewRequestWithContext(ctx, "POST", endpoint, strings.NewReader(form.Encode())) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := cl.Do(req) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + case 401: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("couchdb 401")} + default: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("couchdb status %d", resp.StatusCode)} + } + }) +} + +func init() { Register("couchdb", BruteCouchDB) } +``` + +- [ ] **Step 3: Seed wordlist** + +```bash +mkdir -p wordlist/couchdb +printf "admin\ncouchdb\nuser\n" > wordlist/couchdb/user +printf "admin\ncouchdb\npassword\n" > wordlist/couchdb/password +``` + +- [ ] **Step 4: Run tests** + +Run: `go test ./brute/ -run TestBruteCouchDBNoServer -v` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add brute/couchdb.go brute/couchdb_test.go wordlist/couchdb +git commit -m "feat(brute): couchdb HTTP _session module with default wordlist" +``` + +--- + +## Task C4: Elasticsearch module + +**Files:** +- Create: `brute/elasticsearch.go` +- Create: `brute/elasticsearch_test.go` + +- [ ] **Step 1: Write the failing test** + +```go +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteElasticsearchNoServer(t *testing.T) { + cm := modules.NewConnectionManager() + r := BruteElasticsearch("127.0.0.1", 1, "elastic", "elastic", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port") + } +} +``` + +- [ ] **Step 2: Implement `brute/elasticsearch.go`** + +```go +package brute + +import ( + "context" + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func BruteElasticsearch(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + scheme := "http" + if params["tls"] == "true" { + scheme = "https" + } + endpoint := fmt.Sprintf("%s://%s/_cluster/health", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + tr := &http.Transport{ + DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { + return cm.Dial(network, addr) + }, + DisableKeepAlives: true, + } + cl := &http.Client{Transport: tr, Timeout: timeout} + req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + req.SetBasicAuth(user, password) + resp, err := cl.Do(req) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + case 401, 403: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("elasticsearch %d", resp.StatusCode)} + default: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("elasticsearch status %d", resp.StatusCode)} + } + }) +} + +func init() { Register("elasticsearch", BruteElasticsearch) } +``` + +- [ ] **Step 3: Seed wordlist** + +```bash +mkdir -p wordlist/elasticsearch +printf "elastic\nadmin\nkibana\nlogstash\n" > wordlist/elasticsearch/user +printf "elastic\nchangeme\nadmin\n" > wordlist/elasticsearch/password +``` + +- [ ] **Step 4: Run tests** + +Run: `go test ./brute/ -run TestBruteElasticsearchNoServer -v` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add brute/elasticsearch.go brute/elasticsearch_test.go wordlist/elasticsearch +git commit -m "feat(brute): elasticsearch HTTP basic-auth module with default wordlist" +``` + +--- + +## Task C5: InfluxDB module + +**Files:** +- Create: `brute/influxdb.go` +- Create: `brute/influxdb_test.go` + +- [ ] **Step 1: Write the failing test** + +```go +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteInfluxDBNoServer(t *testing.T) { + cm := modules.NewConnectionManager() + r := BruteInfluxDB("127.0.0.1", 1, "admin", "admin", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port") + } +} +``` + +- [ ] **Step 2: Implement `brute/influxdb.go`** + +```go +package brute + +import ( + "context" + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +// BruteInfluxDB targets InfluxDB 2.x. Treats `password` as the Influx +// token; the endpoint /api/v2/orgs returns 200 on valid auth, 401 on +// invalid. For InfluxDB 1.x, the operator can pass -m mode:v1 to use +// /ping with basic auth instead. +func BruteInfluxDB(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + scheme := "http" + if params["tls"] == "true" { + scheme = "https" + } + v1 := params["mode"] == "v1" + var endpoint string + if v1 { + endpoint = fmt.Sprintf("%s://%s/ping", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + } else { + endpoint = fmt.Sprintf("%s://%s/api/v2/orgs", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + } + tr := &http.Transport{ + DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { + return cm.Dial(network, addr) + }, + DisableKeepAlives: true, + } + cl := &http.Client{Transport: tr, Timeout: timeout} + req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + if v1 { + req.SetBasicAuth(user, password) + } else { + req.Header.Set("Authorization", "Token "+password) + } + resp, err := cl.Do(req) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200, 204: + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + case 401, 403: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("influxdb %d", resp.StatusCode)} + default: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("influxdb status %d", resp.StatusCode)} + } + }) +} + +func init() { Register("influxdb", BruteInfluxDB) } +``` + +- [ ] **Step 3: Run tests** + +Run: `go test ./brute/ -run TestBruteInfluxDBNoServer -v` +Expected: PASS. + +- [ ] **Step 4: Commit** + +```bash +git add brute/influxdb.go brute/influxdb_test.go +git commit -m "feat(brute): influxdb v2 token + v1 basic-auth module" +``` + +--- + +## Task C6: SNMP wordlist tiering + +**Files:** +- Modify: `brute/snmp.go` +- Create: `wordlist/snmp_default.txt`, `wordlist/snmp_extended.txt`, `wordlist/snmp_full.txt` +- Modify: `modules/wordlist.go` (or wherever the `go:embed` directives live) + +- [ ] **Step 1: Author the three tiered lists** + +`wordlist/snmp_default.txt` — ~20 strings: + +``` +public +private +manager +admin +cisco +default +read +write +community +secret +test +rw +ro +guest +snmpd +snmp +internal +external +local +network +``` + +`wordlist/snmp_extended.txt` — superset (~50) adding vendor-specific: + +``` +[contents of snmp_default.txt] +proxy +proxy@cisco +tivoli +ILMI +all +all private +0392a0 +1234 +admin@123 +agent_steal +c0nfig +cable-d +cisco_router +fuckyou +hp_admin +juniper +juniper_admin +juniper_ro +juniper_rw +not_public +NoGaH$@! +OrigEquipMfr +private@123 +proxy@123 +read-only +read-write +readonly +readwrite +regional +router +SAEM +SECURITY +SNMP +snmpd +SNMPV2 +snmpwrite +snmp_trap +SuN_MaNaGeR +SwitcHeS +SyStEm +TeNet +test2 +trap +work +xerox +``` + +`wordlist/snmp_full.txt` — superset (~120) adding SCADA / IP camera / storage: + +``` +[contents of snmp_extended.txt] +nimbus +NIMBUS +ICAM +ICAM_RW +ICAM_RO +SCADA +SCADA_RW +SCADA_RO +ifak +schneider +plc_admin +plc_user +modbus_admin +plc_default +plc_user +SitelA +SiteIIA +SiteIII +SiteIV +PUBLIC +SECRETID +device +device_admin +iLO +iLOAdmin +PRTG +prtg +solarwinds +intermapper +ENTPASS +ENTERPRISE +NETMAN +ONS_dmsadm +NET_OPS +TEAM +mtg +camera +hikvision +dahua +axis +arecont +foscam +trendnet +sony_camera +sony +amcrest +emc +isilon +oncue +sanstation +netapp +synology +qnap +buffalo +``` + +The `full` tier above is the starting set committed in this PR. Additional documented vendor defaults can land as follow-on `chore(snmp): ...` PRs in the same cadence as the monthly wordlist refresh — do not block this PR on an exhaustive list. + +- [ ] **Step 2: Embed the new lists** + +In `modules/wordlist.go`, locate the existing `//go:embed` block(s) and extend: + +```go +//go:embed snmp_default.txt snmp_extended.txt snmp_full.txt +var snmpLists embed.FS + +func SNMPCommunities(tier string) ([]string, error) { + var fname string + switch tier { + case "extended": + fname = "snmp_extended.txt" + case "full": + fname = "snmp_full.txt" + default: + fname = "snmp_default.txt" + } + data, err := snmpLists.ReadFile(fname) + if err != nil { + return nil, err + } + var out []string + for _, line := range strings.Split(string(data), "\n") { + s := strings.TrimSpace(line) + if s != "" && !strings.HasPrefix(s, "#") { + out = append(out, s) + } + } + return out, nil +} +``` + +(Move the wordlist files into `modules/` if the embed paths require it — adapt to existing layout.) + +- [ ] **Step 3: Wire tier selection into `brute/snmp.go`** + +Find the existing community-string source in `brute/snmp.go` and replace with: + +```go +tier := params["mode"] +communities, err := modules.SNMPCommunities(tier) +if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} +} +// ... existing per-community probe loop continues, iterating over communities +``` + +- [ ] **Step 4: Test** + +```go +// brute/snmp_test.go (existing file) — add: +func TestSNMPCommunitiesTiering(t *testing.T) { + def, _ := modules.SNMPCommunities("default") + ext, _ := modules.SNMPCommunities("extended") + full, _ := modules.SNMPCommunities("full") + if !(len(def) < len(ext) && len(ext) < len(full)) { + t.Fatalf("tier sizes not strictly increasing: %d %d %d", len(def), len(ext), len(full)) + } +} +``` + +Run: `go test ./brute/ -run TestSNMPCommunitiesTiering -v` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add brute/snmp.go modules/wordlist.go wordlist/snmp_*.txt brute/snmp_test.go +git commit -m "feat(snmp): default/extended/full community-string tiering" +``` + +--- + +## Task C7: Inline credential pairs `-c/--creds` + +**Files:** +- Modify: `brutespray/config.go` +- Modify: `brutespray/dispatch.go` +- Create: `brutespray/dispatch_creds_test.go` + +- [ ] **Step 1: Add the flag** + +In `brutespray/config.go`: + +```go +flag.StringVar(&Cfg.Creds, "c", "", "Inline credential pairs, comma-separated: user:pass,user2:pass2") +flag.StringVar(&Cfg.Creds, "creds", "", "Alias for -c") +``` + +Add to `Config`: + +```go +Creds string +``` + +- [ ] **Step 2: Write the failing test** + +Create `brutespray/dispatch_creds_test.go`: + +```go +package brutespray + +import ( + "reflect" + "testing" +) + +func TestParseInlineCreds(t *testing.T) { + pairs := ParseInlineCreds("admin:admin,root:toor,user::") + want := []CredPair{ + {User: "admin", Password: "admin"}, + {User: "root", Password: "toor"}, + {User: "user", Password: ":"}, + } + if !reflect.DeepEqual(pairs, want) { + t.Fatalf("got %+v want %+v", pairs, want) + } +} + +func TestParseInlineCredsEmptyInput(t *testing.T) { + if pairs := ParseInlineCreds(""); pairs != nil { + t.Fatalf("empty input should yield nil, got %+v", pairs) + } +} +``` + +- [ ] **Step 3: Implement `ParseInlineCreds`** + +In `brutespray/dispatch.go`: + +```go +// ParseInlineCreds parses "user:pass,user2:pass2" form. Splits on the +// FIRST colon per pair so passwords with colons survive. +func ParseInlineCreds(s string) []CredPair { + if s == "" { + return nil + } + var out []CredPair + for _, part := range strings.Split(s, ",") { + idx := strings.Index(part, ":") + if idx < 0 { + continue + } + out = append(out, CredPair{User: part[:idx], Password: part[idx+1:]}) + } + return out +} +``` + +- [ ] **Step 4: Hook into `ProcessHost`** + +Where credentials are assembled, prepend the inline pairs (so they fire first): + +```go +if Cfg.Creds != "" { + for _, p := range ParseInlineCreds(Cfg.Creds) { + queueCred(p.User, p.Password) + } +} +``` + +- [ ] **Step 5: Run tests** + +Run: `go test ./brutespray/ -run TestParseInlineCreds -v` +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add brutespray/config.go brutespray/dispatch.go brutespray/dispatch_creds_test.go +git commit -m "feat(cli): -c/--creds inline credential pairs" +``` + +--- + +## Task C8: Update service lists in `brutespray/config.go` + +**Files:** +- Modify: `brutespray/config.go` + +- [ ] **Step 1: Mark stable vs beta** + +`BetaServiceList` at line 20 currently does not include the new modules. Promote couchdb/elasticsearch/influxdb to stable (well-defined HTTP endpoints), leave neo4j/cassandra in beta until docker integration tests cover them: + +```go +var BetaServiceList = []string{ + "asterisk", "nntp", "oracle", "xmpp", "ldap", "ldaps", "winrm", "ftps", + "smtp-vrfy", "rexec", "rlogin", "rsh", "wrapper", "http-form", "https-form", + "svn", "socks5-auth", + "neo4j", "cassandra", // new — gated until docker harness covers them +} +``` + +(couchdb/elasticsearch/influxdb implicitly stable by not being listed.) + +- [ ] **Step 2: Verify recognized-service list also covers the five new ones** + +In `brutespray/config.go`, locate `ServiceList` / `AllServices` / equivalent — add `"neo4j", "cassandra", "couchdb", "elasticsearch", "influxdb"`. + +- [ ] **Step 3: Build + smoke test** + +```bash +go build -o brutespray . && ./brutespray -s influxdb -H 127.0.0.1 -u admin -p admin 2>&1 | head +``` + +Expected: brutespray recognizes the service (will error on connection refused, which is fine). + +- [ ] **Step 4: Commit** + +```bash +git add brutespray/config.go +git commit -m "feat(cli): register 5 new DB services (couchdb/elasticsearch/influxdb stable; neo4j/cassandra beta)" +``` + +--- + +# Phase D — Documentation + comparison table + +## Task D1: README comparison table + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Insert the table** + +After the existing feature list / before the services list, add: + +```markdown +## How brutespray compares + +| Feature | brutespray | hydra | medusa | ncrack | brutus | +|---|---|---|---|---|---| +| Single static binary | ✅ | ❌ | ❌ | ❌ | ✅ | +| Interactive TUI | ✅ | ❌ | ❌ | ❌ | ❌ | +| Checkpoint / resume | ✅ | ❌ | ❌ | ✅ | ❌ | +| Spray mode (lockout-aware) | ✅ | ❌ | ❌ | ❌ | ❌ | +| Per-attempt JSONL output | ✅ | ⚠️ | ❌ | ❌ | ❌ (success-only) | +| SOCKS5 + proxy rotation | ✅ | ⚠️ | ❌ | ❌ | ❌ | +| Embedded SSH bad-keys (CVE-tagged) | ✅ | ❌ | ❌ | ❌ | ✅ | +| Pipeline stdin (naabu / fingerprintx / masscan) | ✅ | ❌ | ❌ | ❌ | ✅ | +| Pre-auth RDP recon (NLA / sticky-keys) | ✅ | ❌ | ❌ | ❌ | ✅ | +| Nmap gnmap + XML / Nessus / Nexpose import | ✅ | ⚠️ | ❌ | ❌ | ⚠️ (nmap only) | +| Per-module params (`-m KEY:VAL`) | ✅ | ❌ | ❌ | ❌ | partial | +| Service count | 41 | 50+ | 34 | 14 | 23 | + +> Verify each ✅/⚠️/❌ against the named tool's current documentation before merging — competing tools update fast. +``` + +- [ ] **Step 2: Commit** + +```bash +git add README.md +git commit -m "docs(readme): add brutespray-vs-others comparison table" +``` + +--- + +## Task D2: `docs/services.md` — five new modules + +**Files:** +- Modify: `docs/services.md` + +- [ ] **Step 1: Add rows for the new services** + +Follow the existing table format. For each of neo4j, cassandra, couchdb, elasticsearch, influxdb, add a row: + +```markdown +| neo4j | Neo4j Bolt v5 graph DB | 7687 | TCP | Beta | `-s neo4j` | +| cassandra | Apache Cassandra CQL | 9042 | TCP | Beta | `-s cassandra` | +| couchdb | CouchDB HTTP `_session` | 5984 | TCP | Stable | `-s couchdb` | +| elasticsearch | Elasticsearch HTTP basic auth | 9200 | TCP | Stable | `-s elasticsearch` | +| influxdb | InfluxDB v2 token / v1 basic auth | 8086 | TCP | Stable | `-s influxdb -m mode:v1` for 1.x | +``` + +(Match column shape to actual `docs/services.md` — read the file first.) + +- [ ] **Step 2: Commit** + +```bash +git add docs/services.md +git commit -m "docs(services): document 5 new DB modules" +``` + +--- + +## Task D3: `docs/advanced.md` — SSH bad-keys + RDP recon + +**Files:** +- Modify: `docs/advanced.md` + +- [ ] **Step 1: Add SSH bad-keys section** + +```markdown +## SSH bad-keys + +Brutespray ships an embedded bundle of known-compromised SSH private keys +(Rapid7 ssh-badkeys + HashiCorp Vagrant + per-vendor defaults). Whenever +you target SSH, the bundle is tried first with each key's metadata-suggested +default username (root for F5 BIG-IP, vagrant for Vagrant, mateidu for +Ceragon FibeAir, etc.). + +| Flag | Effect | +|---|---| +| (default) | Bad-keys pass runs first; passwords follow if no key matches | +| `--no-badkeys` | Skip the bad-keys pass entirely | +| `--badkeys-only` | Run the bad-keys pass only; skip password attempts | + +### CVE mapping + +Successful bad-key authentications surface as a `BADKEY` line and carry the +CVE identifier in JSONL output as `key_match.cve`. + +| Vendor | Default user | CVE | +|---|---|---| +| HashiCorp Vagrant | vagrant | (no CVE — documented insecure default) | +| F5 BIG-IP | root | CVE-2012-1493 | +| ExaGrid EX | root | CVE-2016-1561 | +| Ceragon FibeAir | mateidu | (no CVE) | +| (others) | varies | varies | + +The bundle is refreshed monthly alongside the existing wordlist update cadence. +``` + +- [ ] **Step 2: Add RDP pre-auth recon section** + +```markdown +## Pre-auth RDP recon + +When the target service is `rdp`, brutespray runs two pre-auth probes +before any credential attempt. Both are best-effort and add only one TCP +round-trip per host. + +### NLA fingerprint + +Sends a single X.224 Connection Request and reads the server's RDPneg +response to classify NLA enforcement: + +- `[INFO] rdp NLA (CredSSP) enforced` — NLA required, standard RDP refused +- `[INFO] rdp HybridEx (NLA + CredSSP early-user) enforced` +- `[WARN] rdp NLA not enforced — server accepts standard RDP` + +### Sticky-keys backdoor + +When NLA is not enforced, brutespray connects to the logon screen, sends +five Shift keypresses (the sticky-keys trigger), and snapshots the +framebuffer before and after. If the post-trigger frame matches the +heuristic for a cmd.exe console (predominantly black with monospaced +white text in the top region), the finding is: + +``` +[CRITICAL] rdp sticky-keys backdoor detected +``` + +If the framebuffer changed but the console signature does not match, the +result is downgraded: + +``` +[INFO] rdp sticky-keys inconclusive; manual verification recommended +``` + +Opt out of both probes with `--no-rdp-scan`. +``` + +- [ ] **Step 2 (continued): Commit** + +```bash +git add docs/advanced.md +git commit -m "docs(advanced): SSH bad-keys + pre-auth RDP recon" +``` + +--- + +## Task D4: `docs/output.md` — Finding and KeyMatch JSONL schema + +**Files:** +- Modify: `docs/output.md` + +- [ ] **Step 1: Add new sections** + +```markdown +## Finding records (JSONL) + +Pre-auth recon results emit one JSON object per line: + +```json +{"type":"finding","severity":"WARN","code":"rdp-nla-missing","service":"rdp","target":"10.0.0.5:3389","message":"NLA not enforced — server accepts standard RDP without pre-auth"} +{"type":"finding","severity":"CRITICAL","code":"rdp-stickykeys","service":"rdp","target":"10.0.0.5:3389","message":"sticky-keys backdoor detected (cmd.exe shell at logon screen)"} +``` + +| Field | Description | +|---|---| +| `type` | Always `"finding"` for these records | +| `severity` | `INFO`, `WARN`, `HIGH`, `CRITICAL` | +| `code` | Stable machine identifier — `rdp-nla-missing`, `rdp-stickykeys`, `rdp-stickykeys-inconclusive`, `rdp-nla-required`, `rdp-nla-hybridex`, `ssh-badkey` | +| `service` / `target` | Target identification | +| `message` | Human-readable description | +| `cve` | Present only when a CVE applies (e.g. F5 bad key) | + +## KeyMatch on SSH success + +When SSH authentication succeeds against an embedded bad key, the per-success +JSONL record gains a `key_match` object: + +```json +{"type":"success","service":"ssh","target":"10.0.0.5:22","username":"vagrant","password":"::badkey::0","key_match":{"fingerprint":"sha256:abc...","vendor":"HashiCorp Vagrant","cve":"","description":"Vagrant insecure default key (any Vagrant VM pre-2014)"}} +``` +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/output.md +git commit -m "docs(output): Finding and KeyMatch JSONL schema" +``` + +--- + +## Task D5: `docs/wordlists.md` — SNMP tiering + +**Files:** +- Modify: `docs/wordlists.md` + +- [ ] **Step 1: Add SNMP tiering subsection** + +```markdown +## SNMP community-string tiers + +The `snmp` module ships three embedded tiers, selected via `-m mode:`: + +| Tier | Size | Contents | +|---|---|---| +| `default` (default) | ~20 | classic public/private/cisco-style community strings | +| `extended` | ~50 | + per-vendor (Cisco / HP / Juniper) enterprise defaults | +| `full` | ~120 | + SCADA controllers, IP cameras, NAS / storage arrays | + +Example: + +``` +brutespray -s snmp -H 10.0.0.0/24 -m mode:full +``` +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/wordlists.md +git commit -m "docs(wordlists): SNMP tiered community-string lists" +``` + +--- + +## Task D6: `docs/usage.md` — new flags + +**Files:** +- Modify: `docs/usage.md` + +- [ ] **Step 1: Add flag rows** + +Locate the existing flags table (or list) and add: + +```markdown +| `--no-badkeys` | Skip the embedded SSH bad-keys pre-pass | +| `--badkeys-only` | Run the embedded SSH bad-keys pre-pass only; skip passwords | +| `--no-rdp-scan` | Skip pre-auth RDP recon (NLA fingerprint + sticky-keys probe) | +| `-c, --creds STR` | Inline credential pairs, comma-separated: `admin:admin,root:toor` | +``` + +Add a "stdin pipeline" subsection: + +```markdown +### Reading targets from stdin + +When `-f` is not supplied and stdin is a pipe, brutespray reads targets +from stdin and auto-detects the input format (naabu line, Nerva URI, +Nerva JSON, fingerprintx JSON, masscan JSON): + +``` +naabu -host 10.0.0.0/24 -p 22 -silent | brutespray -u root -P wordlist/ssh.txt +masscan -p22,3389 10.0.0.0/24 -oJ - | brutespray -u admin -p admin +``` +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/usage.md +git commit -m "docs(usage): new flags + stdin pipeline section" +``` + +--- + +## Task D7: `docs/pipeline.md` — end-to-end recon workflow + +**Files:** +- Create: `docs/pipeline.md` + +- [ ] **Step 1: Write the new doc** + +```markdown +# Pipeline integration + +brutespray accepts targets on stdin and auto-detects the format. This makes +it a natural terminator for modern Go recon pipelines. + +## naabu → brutespray + +``` +naabu -host 10.0.0.0/24 -p 22,3306,3389,5984 -silent \ + | brutespray -u root -P wordlist/_base/password +``` + +naabu emits `host:port` lines; brutespray maps each port to a service via +the default-port table (22→ssh, 3306→mysql, 3389→rdp, 5984→couchdb). + +## naabu → fingerprintx → brutespray + +``` +naabu -host 10.0.0.0/24 -silent \ + | fingerprintx --json \ + | brutespray -u root -P wordlist/_base/password +``` + +fingerprintx emits JSON with `service` already classified — brutespray +uses that directly and skips the port-table fallback. + +## masscan → brutespray + +``` +masscan -p22,3389,5984 10.0.0.0/24 -oJ - \ + | brutespray --no-badkeys -u admin -p admin +``` + +masscan's JSON array is decoded; only open ports are forwarded; closed +and filtered are dropped. + +## SSH bad-keys only + +``` +masscan -p22 10.0.0.0/24 -oJ - \ + | brutespray --badkeys-only --output-format json -o results.jsonl +``` + +Skips password attempts entirely. Each successful match emits a +`key_match` record (see `output.md`) carrying the vendor and CVE. + +## RDP recon scan + +``` +naabu -host 10.0.0.0/24 -p 3389 -silent \ + | brutespray -s rdp -u test -p test --output-format json -o rdp-findings.jsonl +``` + +The NLA fingerprint and sticky-keys probe run before any credential +attempts. Findings flow into the same JSONL stream as auth attempts; filter +by `type=="finding"` downstream. +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/pipeline.md +git commit -m "docs(pipeline): end-to-end recon workflow with naabu/fingerprintx/masscan" +``` + +--- + +## Task D8: CLAUDE.md (local-only) update + +**Files:** +- Modify: `CLAUDE.md` (local only — do NOT `git add`) + +- [ ] **Step 1: Update the Services + Module Parameters sections** + +Add to "Services (36+)" line: + +``` +Stable: ssh, ftp, telnet, smtp, imap, pop3, mysql, postgres, mssql, mongodb, redis, vnc, snmp, smbnt, rdp, http, https, vmauthd, teamspeak, couchdb, elasticsearch, influxdb +Beta: asterisk, nntp, oracle, xmpp, ldap, ldaps, winrm, ftps, smtp-vrfy, rexec, rlogin, rsh, wrapper, http-form, https-form, svn, socks5-auth, neo4j, cassandra +``` + +Add to "Module Parameters": + +``` +- `snmp`: `-m mode:default|extended|full` (community-string tier) +- `influxdb`: `-m mode:v1` (use 1.x basic auth instead of 2.x token) +- `couchdb` / `elasticsearch` / `influxdb`: `-m tls:true` for HTTPS endpoints +``` + +Add new "Pre-auth recon" subsection under Conventions: + +``` +- `--no-badkeys` / `--badkeys-only` — SSH bad-keys pre-pass control +- `--no-rdp-scan` — disable RDP NLA fingerprint + sticky-keys probe +- Stdin targets: when no `-f` and stdin is a pipe, parse-stream auto-detects format (naabu / Nerva URI / Nerva JSON / fingerprintx JSON / masscan JSON) +``` + +- [ ] **Step 2: Verify CLAUDE.md is NOT staged** + +```bash +git status -s CLAUDE.md +``` + +Expected: line begins with ` M` (working-tree modified) NOT `M ` (staged). + +If accidentally staged: + +```bash +git restore --staged CLAUDE.md +``` + +- [ ] **Step 3: No commit** — CLAUDE.md is intentionally not committed per project policy. + +--- + +## Task D9: Full regression + integration sweep + open PR + +**Files:** none modified — verification only. + +- [ ] **Step 1: Full unit test run** + +Run: `go test ./... -count=1` +Expected: all tests pass, including the 106 from prior work plus all new tests in this PR. + +- [ ] **Step 2: Race-detector run (skip the known IMAP race per the codebase convention)** + +Run: `go test ./... -race -count=1` +Expected: race-clean. + +- [ ] **Step 3: Lint** + +Run: `golangci-lint run` +Expected: zero issues. If new issues appear, fix inline; do not silence. + +- [ ] **Step 4: Docker-backed module tests** + +Run: + +```bash +docker run -d --name btest-couchdb -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=admin -p 5984:5984 couchdb:3 +docker run -d --name btest-es -e "discovery.type=single-node" -e "xpack.security.enabled=true" -e "ELASTIC_PASSWORD=changeme" -p 9200:9200 elasticsearch:8.11.0 +docker run -d --name btest-influx -e DOCKER_INFLUXDB_INIT_MODE=setup -e DOCKER_INFLUXDB_INIT_USERNAME=admin -e DOCKER_INFLUXDB_INIT_PASSWORD=adminadmin -e DOCKER_INFLUXDB_INIT_ORG=test -e DOCKER_INFLUXDB_INIT_BUCKET=test -e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=mytoken -p 8086:8086 influxdb:2 +docker run -d --name btest-neo4j -e NEO4J_AUTH=neo4j/testtest -p 7687:7687 neo4j:5 +docker run -d --name btest-cass -p 9042:9042 cassandra:4 +sleep 30 +BRUTESPRAY_DOCKER_TESTS=1 go test ./brute/ -run "TestBrute(CouchDB|Elasticsearch|InfluxDB|Neo4j|Cassandra)Docker" -v +docker rm -f btest-couchdb btest-es btest-influx btest-neo4j btest-cass +``` + +Expected: each docker-gated test passes against its real server. + +- [ ] **Step 5: Stdin pipeline smoke** + +```bash +echo "127.0.0.1:22" | ./brutespray -u root -p test 2>&1 | head +printf '[{"ip":"127.0.0.1","ports":[{"port":22,"proto":"tcp","status":"open"}]}]' | ./brutespray -u root -p test 2>&1 | head +``` + +Expected: both invocations parse and attempt connect (refused is fine; we are testing the parse path). + +- [ ] **Step 6: Comparison-table verification** + +Open the docs/release pages of hydra, medusa, ncrack, brutus. Verify each ✅/⚠️/❌ in the README table against current behavior. Edit the table if any cell is out of date. + +- [ ] **Step 7: Open the PR** + +```bash +gh pr create --base master --head dev \ + --title "feat: Brutus feature-parity — bad-keys, RDP pre-auth recon, stdin pipeline, 5 DB modules" \ + --body "$(cat <<'EOF' +## Summary + +Borrows the four high-value capabilities Brutus introduced into the multi-protocol cred-test space and lands them on brutespray, plus a brutespray-vs-others comparison table for the README so positioning is in sync with the new feature set. + +### Pre-auth recon +- Embedded SSH bad-keys bundle (Rapid7 ssh-badkeys + Vagrant + per-vendor keys, CVE-tagged) +- RDP NLA fingerprint and sticky-keys backdoor probe (coordinated change in sibling grdp repo) +- New `Finding` and `KeyMatch` BruteResult fields, surfaced in text/JSONL output and a new TUI Findings tab +- Flags: `--no-badkeys`, `--badkeys-only`, `--no-rdp-scan` + +### Pipeline integration +- Stdin auto-detect: naabu, Nerva URI, Nerva JSON, fingerprintx JSON, masscan JSON +- Masscan -oJ ingestion via existing port→service mapping + +### New modules +- Neo4j (Bolt v5), Cassandra (CQL), CouchDB, Elasticsearch, InfluxDB (v2 token + v1 basic) +- SNMP wordlist tiering: default / extended / full (SCADA + camera + storage vendors) +- `-c/--creds` inline credential pairs + +### Docs +- README comparison table vs hydra / medusa / ncrack / brutus +- `docs/advanced.md`: bad-keys CVE table + RDP recon details +- `docs/output.md`: Finding + KeyMatch JSONL schemas +- `docs/services.md`: 5 new module rows +- `docs/wordlists.md`: SNMP tiering +- `docs/usage.md`: new flags + stdin section +- `docs/pipeline.md` (new): end-to-end recon walkthrough + +Spec: `docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md` + +## Test plan + +- [x] `go test ./... -count=1` clean +- [x] `go test ./... -race -count=1` clean (IMAP race skip respected) +- [x] `golangci-lint run` clean +- [x] Docker-backed integration tests for couchdb / elasticsearch / influxdb / neo4j / cassandra +- [x] Stdin pipeline smoke (naabu + masscan JSON forms) +- [x] Comparison table verified against each named tool's current docs +EOF +)" +``` + +Expected: PR URL printed; CI starts. + +- [ ] **Step 8: Final commit if any verification surfaced doc/code drift** + +Any small fixes from Steps 6-7 land as a separate commit so the diff stays auditable: + +```bash +git add -A +git commit -m "docs/test: post-verification adjustments before PR" +git push +``` + +--- + +## Spec coverage map + +| Spec section | Covered by | +|---|---| +| Goal 1 — SSH bad-keys bundle | A2, A3, A4 | +| Goal 2 — RDP NLA + sticky-keys recon | A5, A6, A7 | +| Goal 3 — Stdin pipeline auto-detect + masscan JSON | B1, B2, B3 | +| Goal 4 — 5 DB modules | C1, C2, C3, C4, C5 | +| Goal 5 — SNMP tiering | C6 | +| Goal 6 — Inline cred pairs `-c` | C7 | +| Goal 7 — README comparison table + docs sweep | D1, D2, D3, D4, D5, D6, D7, D8 | +| BruteResult Finding/KeyMatch fields | A1 | +| Output text + JSONL rendering | A8 | +| TUI Findings tab | A9 | +| Stable/beta service list updates | C8 | +| Final regression + PR | D9 | From 62a180a927ef9acddc59edcdc6e86d918734471a Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:25:39 -0500 Subject: [PATCH 04/49] feat(brute): add Finding and KeyMatch fields to BruteResult --- brute/result_test.go | 32 ++++++++++++++++++++++++++++++++ brute/run.go | 30 +++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 brute/result_test.go diff --git a/brute/result_test.go b/brute/result_test.go new file mode 100644 index 0000000..2d71424 --- /dev/null +++ b/brute/result_test.go @@ -0,0 +1,32 @@ +package brute + +import "testing" + +func TestBruteResultCarriesFinding(t *testing.T) { + r := &BruteResult{ + ConnectionSuccess: true, + Finding: &Finding{ + Severity: "CRITICAL", + Code: "rdp-stickykeys", + Message: "sticky-keys backdoor detected", + }, + } + if r.Finding == nil || r.Finding.Code != "rdp-stickykeys" { + t.Fatalf("Finding not carried on BruteResult") + } +} + +func TestBruteResultCarriesKeyMatch(t *testing.T) { + r := &BruteResult{ + AuthSuccess: true, + ConnectionSuccess: true, + KeyMatch: &KeyMatch{ + Fingerprint: "SHA256:abc", + Vendor: "Vagrant", + CVE: "CVE-2015-1338", + }, + } + if r.KeyMatch == nil || r.KeyMatch.Vendor != "Vagrant" { + t.Fatalf("KeyMatch not carried on BruteResult") + } +} diff --git a/brute/run.go b/brute/run.go index f55e734..bfd4a04 100644 --- a/brute/run.go +++ b/brute/run.go @@ -17,6 +17,25 @@ func sanitizeCred(s string) string { return strings.NewReplacer("\r", "", "\n", "", "\x00", "").Replace(s) } +// Finding represents a pre-auth recon result (e.g. SSH bad-key match, +// RDP NLA missing, RDP sticky-keys backdoor). Modules can return findings +// without a successful authentication attempt. +type Finding struct { + Severity string // INFO, WARN, HIGH, CRITICAL + Code string // e.g. "rdp-nla-missing", "rdp-stickykeys", "ssh-badkey" + Message string + CVE string // optional, e.g. "CVE-2012-1493" +} + +// KeyMatch records a successful SSH key authentication originating from +// the embedded bad-keys bundle. +type KeyMatch struct { + Fingerprint string + Vendor string + CVE string + Description string +} + // BruteResult captures the outcome of a single credential attempt including // whether the connection itself succeeded (to distinguish auth failures from // network failures). @@ -27,6 +46,8 @@ type BruteResult struct { Banner string // service banner if captured RetryDelay time.Duration // if > 0, module requests this delay before next retry (e.g. VNC anti-brute) SkipUser bool // if true, skip remaining passwords for this user (e.g. FTP 530 user-not-found) + Finding *Finding // pre-auth recon result, nil if none + KeyMatch *KeyMatch // SSH bad-key match, nil if none } // CircuitBreaker tracks consecutive connection failures per host and trips @@ -259,5 +280,12 @@ func RunBrute(h modules.Host, u string, p string, timeout time.Duration, maxRetr } modules.PrintResult(service, h.Host, h.Port, u, p, modResult.AuthSuccess, modResult.ConnectionSuccess, false, output, 0, modResult.Banner) - return BruteResult{AuthSuccess: modResult.AuthSuccess, ConnectionSuccess: modResult.ConnectionSuccess, Banner: modResult.Banner, SkipUser: modResult.SkipUser} + return BruteResult{ + AuthSuccess: modResult.AuthSuccess, + ConnectionSuccess: modResult.ConnectionSuccess, + Banner: modResult.Banner, + SkipUser: modResult.SkipUser, + Finding: modResult.Finding, + KeyMatch: modResult.KeyMatch, + } } From 27d9e7a444cd142db4f58f59a1ec597c71cb64a9 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:29:50 -0500 Subject: [PATCH 05/49] refactor(brute): move Finding and KeyMatch to brute/types.go Output value types belong in their own file, not alongside orchestration logic. Also tighten BruteResult field assertions in result_test.go: add inline comments explaining intent and assert KeyMatch.CVE round-trips. --- brute/result_test.go | 5 +++++ brute/run.go | 19 ------------------- brute/types.go | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 19 deletions(-) create mode 100644 brute/types.go diff --git a/brute/result_test.go b/brute/result_test.go index 2d71424..1fe4fe3 100644 --- a/brute/result_test.go +++ b/brute/result_test.go @@ -3,6 +3,7 @@ package brute import "testing" func TestBruteResultCarriesFinding(t *testing.T) { + // shows Finding can coexist with non-auth result r := &BruteResult{ ConnectionSuccess: true, Finding: &Finding{ @@ -17,6 +18,7 @@ func TestBruteResultCarriesFinding(t *testing.T) { } func TestBruteResultCarriesKeyMatch(t *testing.T) { + // success path: KeyMatch + AuthSuccess together r := &BruteResult{ AuthSuccess: true, ConnectionSuccess: true, @@ -29,4 +31,7 @@ func TestBruteResultCarriesKeyMatch(t *testing.T) { if r.KeyMatch == nil || r.KeyMatch.Vendor != "Vagrant" { t.Fatalf("KeyMatch not carried on BruteResult") } + if r.KeyMatch.CVE != "CVE-2015-1338" { + t.Fatalf("KeyMatch.CVE = %q, want CVE-2015-1338", r.KeyMatch.CVE) + } } diff --git a/brute/run.go b/brute/run.go index bfd4a04..593a94b 100644 --- a/brute/run.go +++ b/brute/run.go @@ -17,25 +17,6 @@ func sanitizeCred(s string) string { return strings.NewReplacer("\r", "", "\n", "", "\x00", "").Replace(s) } -// Finding represents a pre-auth recon result (e.g. SSH bad-key match, -// RDP NLA missing, RDP sticky-keys backdoor). Modules can return findings -// without a successful authentication attempt. -type Finding struct { - Severity string // INFO, WARN, HIGH, CRITICAL - Code string // e.g. "rdp-nla-missing", "rdp-stickykeys", "ssh-badkey" - Message string - CVE string // optional, e.g. "CVE-2012-1493" -} - -// KeyMatch records a successful SSH key authentication originating from -// the embedded bad-keys bundle. -type KeyMatch struct { - Fingerprint string - Vendor string - CVE string - Description string -} - // BruteResult captures the outcome of a single credential attempt including // whether the connection itself succeeded (to distinguish auth failures from // network failures). diff --git a/brute/types.go b/brute/types.go new file mode 100644 index 0000000..b0c97d3 --- /dev/null +++ b/brute/types.go @@ -0,0 +1,20 @@ +package brute + +// Finding represents a pre-auth recon result (e.g. SSH bad-key match, +// RDP NLA missing, RDP sticky-keys backdoor). Modules can return findings +// without a successful authentication attempt. +type Finding struct { + Severity string // INFO, WARN, HIGH, CRITICAL + Code string // e.g. "rdp-nla-missing", "rdp-stickykeys", "ssh-badkey" + Message string + CVE string // optional, e.g. "CVE-2012-1493" +} + +// KeyMatch records a successful SSH key authentication originating from +// the embedded bad-keys bundle. +type KeyMatch struct { + Fingerprint string + Vendor string + CVE string + Description string +} From f10e13a2945044157c3aa584a849103bb77c9a7c Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:31:09 -0500 Subject: [PATCH 06/49] feat(tui): propagate KeyMatch through AttemptResultMsg event bus Add KeyMatch *brute.KeyMatch to AttemptResultMsg so the TUI success view can render [+] BADKEY lines for SSH bad-key matches. Populate it from result.KeyMatch in processCredential. Add tui/messages_test.go to verify the field round-trips correctly through the struct. --- brutespray/pool.go | 1 + tui/messages.go | 7 +++++- tui/messages_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 tui/messages_test.go diff --git a/brutespray/pool.go b/brutespray/pool.go index b685e42..e13c549 100644 --- a/brutespray/pool.go +++ b/brutespray/pool.go @@ -539,6 +539,7 @@ func (hwp *HostWorkerPool) processCredential(cred Credential, timeout time.Durat Duration: duration, Timestamp: startTime, Banner: result.Banner, + KeyMatch: result.KeyMatch, }) // Write to session log for resume replay diff --git a/tui/messages.go b/tui/messages.go index 67120bb..3ae7ba5 100644 --- a/tui/messages.go +++ b/tui/messages.go @@ -1,6 +1,10 @@ package tui -import "time" +import ( + "time" + + "github.com/x90skysn3k/brutespray/v2/brute" +) // AttemptResultMsg is sent by workers after each credential attempt. type AttemptResultMsg struct { @@ -16,6 +20,7 @@ type AttemptResultMsg struct { Retrying bool Timestamp time.Time Banner string + KeyMatch *brute.KeyMatch // non-nil when the attempt matched a known-bad SSH key } // HostStartedMsg is sent when a host begins processing. diff --git a/tui/messages_test.go b/tui/messages_test.go new file mode 100644 index 0000000..c6d162f --- /dev/null +++ b/tui/messages_test.go @@ -0,0 +1,54 @@ +package tui + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/brute" +) + +// TestAttemptResultMsgCarriesKeyMatch verifies that KeyMatch round-trips +// through AttemptResultMsg so the TUI success view can render [+] BADKEY lines. +func TestAttemptResultMsgCarriesKeyMatch(t *testing.T) { + km := &brute.KeyMatch{ + Fingerprint: "SHA256:xyzzy", + Vendor: "Cisco", + CVE: "CVE-2015-1338", + Description: "Cisco default key", + } + + msg := AttemptResultMsg{ + Host: "192.0.2.1", + Port: 22, + Service: "ssh", + User: "admin", + Password: "cisco", + Success: true, + Connected: true, + Duration: 100 * time.Millisecond, + Timestamp: time.Now(), + KeyMatch: km, + } + + if msg.KeyMatch == nil { + t.Fatal("KeyMatch is nil, expected non-nil") + } + if msg.KeyMatch.CVE != "CVE-2015-1338" { + t.Fatalf("KeyMatch.CVE = %q, want CVE-2015-1338", msg.KeyMatch.CVE) + } + if msg.KeyMatch.Vendor != "Cisco" { + t.Fatalf("KeyMatch.Vendor = %q, want Cisco", msg.KeyMatch.Vendor) + } + if msg.KeyMatch.Fingerprint != "SHA256:xyzzy" { + t.Fatalf("KeyMatch.Fingerprint = %q, want SHA256:xyzzy", msg.KeyMatch.Fingerprint) + } +} + +// TestAttemptResultMsgKeyMatchNilByDefault confirms that a zero-value +// AttemptResultMsg has a nil KeyMatch (normal credential attempts). +func TestAttemptResultMsgKeyMatchNilByDefault(t *testing.T) { + var msg AttemptResultMsg + if msg.KeyMatch != nil { + t.Fatal("KeyMatch should be nil for a zero-value AttemptResultMsg") + } +} From eb1d068e8e9da1528d3b3e0b76b5a9abb36cc73a Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:36:27 -0500 Subject: [PATCH 07/49] feat(badkeys): vendor Rapid7 ssh-badkeys + Vagrant + vendor key bundle --- brute/badkeys/embed.go | 6 + brute/badkeys/keys/Actiontec_q2000_rsa.key | 15 + brute/badkeys/keys/Alice_1121_rsa.key | 15 + brute/badkeys/keys/Cisco_RV315W_dsa.key | 12 + brute/badkeys/keys/Cisco_RV315W_rsa.key | 15 + brute/badkeys/keys/Cisco_rtp300_dsa.key | 12 + brute/badkeys/keys/Cisco_rtp300_rsa.key | 15 + brute/badkeys/keys/Cisco_rv120w_dsa.key | 12 + brute/badkeys/keys/Cisco_rv120w_rsa.key | 15 + brute/badkeys/keys/Comtrend_AR5387UN_rsa.key | 15 + brute/badkeys/keys/EVW3226_rsa.key | 15 + brute/badkeys/keys/Edimax_AR-7167_dsa.key | 12 + brute/badkeys/keys/Edimax_AR-7167_rsa.key | 15 + brute/badkeys/keys/Huawei_bm626_dsa.key | 12 + brute/badkeys/keys/Huawei_bm626_rsa.key | 15 + brute/badkeys/keys/Innacomm_w3400v_rsa.key | 15 + brute/badkeys/keys/Linksys_X1000_rsa.key | 15 + brute/badkeys/keys/Moxa_6150_rsa.key | 15 + brute/badkeys/keys/Moxa_ia240_dsa.key | 12 + brute/badkeys/keys/Moxa_ia240_rsa.key | 15 + brute/badkeys/keys/Ont_g4020w_rsa.key | 15 + brute/badkeys/keys/Pace_V5542_dsa.key | 12 + brute/badkeys/keys/Quanta_LTE.key | 15 + brute/badkeys/keys/Sagemcom_2740_rsa.key | 15 + brute/badkeys/keys/Sagemcom_sx682_dsa.key | 8 + brute/badkeys/keys/Seagate_GoFlex_dsa.key | 12 + brute/badkeys/keys/Seagate_GoFlex_rsa.key | 27 ++ .../badkeys/keys/Telefonica-de-Espana_rsa.key | 15 + brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key | 15 + brute/badkeys/keys/Tplink_w8950n_rsa.key | 15 + brute/badkeys/keys/Tplink_w8950nd_rsa.key | 15 + brute/badkeys/keys/Trendnet_tdmc500_rsa.key | 15 + brute/badkeys/keys/Trendnet_tew715apo_dsa.key | 12 + brute/badkeys/keys/Trendnet_tew715apo_rsa.key | 15 + brute/badkeys/keys/Trendnet_tew816drm_dsa.key | 12 + brute/badkeys/keys/Trendnet_tew816drm_rsa.key | 15 + brute/badkeys/keys/Trendnet_tvip310pi_dsa.key | 12 + brute/badkeys/keys/Trendnet_tvip310pi_rsa.key | 15 + brute/badkeys/keys/Westermo_MRD310_dsa.key | 12 + brute/badkeys/keys/Westermo_MRD310_rsa.key | 27 ++ brute/badkeys/keys/Zhone_6512a1_rsa.key | 15 + brute/badkeys/keys/Zyxel_fsg2200_rsa.key | 27 ++ brute/badkeys/keys/Zyxel_p870h_rsa.key | 15 + brute/badkeys/keys/Zyxel_pmg1006_dsa.key | 12 + brute/badkeys/keys/Zyxel_pmg1006_rsa.key | 15 + brute/badkeys/keys/Zyxel_sbg3300_rsa.key | 15 + brute/badkeys/keys/advantech_eki_rsa.key | 27 ++ .../badkeys/keys/array-networks-vapv-vxag.key | 12 + .../keys/barracuda_load_balancer_vm.key | 12 + .../keys/ceragon-fibeair-cve-2015-0936.key | 15 + brute/badkeys/keys/exagrid-cve-2016-1561.key | 15 + brute/badkeys/keys/f5-bigip-cve-2012-1493.key | 15 + brute/badkeys/keys/kali-rpi2.key | 27 ++ .../keys/loadbalancer.org-enterprise-va.key | 12 + .../keys/monroe-dasdec-cve-2013-0137.key | 12 + brute/badkeys/keys/moovbox_host_dsa.key | 12 + brute/badkeys/keys/moovbox_host_rsa.key | 15 + brute/badkeys/keys/quantum-dxi-v1000.key | 12 + brute/badkeys/keys/tandberg-vcs.key | 12 + brute/badkeys/keys/vagrant-default.key | 27 ++ brute/badkeys/keys/zyxel-q100_rsa.key | 15 + brute/badkeys/keys/zyxel-vmg1312_rsa.key | 15 + brute/badkeys/metadata.yaml | 375 ++++++++++++++++++ brute/badkeys/registry.go | 60 +++ brute/badkeys/registry_test.go | 32 ++ 65 files changed, 1393 insertions(+) create mode 100644 brute/badkeys/embed.go create mode 100644 brute/badkeys/keys/Actiontec_q2000_rsa.key create mode 100644 brute/badkeys/keys/Alice_1121_rsa.key create mode 100644 brute/badkeys/keys/Cisco_RV315W_dsa.key create mode 100644 brute/badkeys/keys/Cisco_RV315W_rsa.key create mode 100644 brute/badkeys/keys/Cisco_rtp300_dsa.key create mode 100644 brute/badkeys/keys/Cisco_rtp300_rsa.key create mode 100644 brute/badkeys/keys/Cisco_rv120w_dsa.key create mode 100644 brute/badkeys/keys/Cisco_rv120w_rsa.key create mode 100644 brute/badkeys/keys/Comtrend_AR5387UN_rsa.key create mode 100644 brute/badkeys/keys/EVW3226_rsa.key create mode 100644 brute/badkeys/keys/Edimax_AR-7167_dsa.key create mode 100644 brute/badkeys/keys/Edimax_AR-7167_rsa.key create mode 100644 brute/badkeys/keys/Huawei_bm626_dsa.key create mode 100644 brute/badkeys/keys/Huawei_bm626_rsa.key create mode 100644 brute/badkeys/keys/Innacomm_w3400v_rsa.key create mode 100644 brute/badkeys/keys/Linksys_X1000_rsa.key create mode 100644 brute/badkeys/keys/Moxa_6150_rsa.key create mode 100644 brute/badkeys/keys/Moxa_ia240_dsa.key create mode 100644 brute/badkeys/keys/Moxa_ia240_rsa.key create mode 100644 brute/badkeys/keys/Ont_g4020w_rsa.key create mode 100644 brute/badkeys/keys/Pace_V5542_dsa.key create mode 100644 brute/badkeys/keys/Quanta_LTE.key create mode 100644 brute/badkeys/keys/Sagemcom_2740_rsa.key create mode 100644 brute/badkeys/keys/Sagemcom_sx682_dsa.key create mode 100644 brute/badkeys/keys/Seagate_GoFlex_dsa.key create mode 100644 brute/badkeys/keys/Seagate_GoFlex_rsa.key create mode 100644 brute/badkeys/keys/Telefonica-de-Espana_rsa.key create mode 100644 brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key create mode 100644 brute/badkeys/keys/Tplink_w8950n_rsa.key create mode 100644 brute/badkeys/keys/Tplink_w8950nd_rsa.key create mode 100644 brute/badkeys/keys/Trendnet_tdmc500_rsa.key create mode 100644 brute/badkeys/keys/Trendnet_tew715apo_dsa.key create mode 100644 brute/badkeys/keys/Trendnet_tew715apo_rsa.key create mode 100644 brute/badkeys/keys/Trendnet_tew816drm_dsa.key create mode 100644 brute/badkeys/keys/Trendnet_tew816drm_rsa.key create mode 100644 brute/badkeys/keys/Trendnet_tvip310pi_dsa.key create mode 100644 brute/badkeys/keys/Trendnet_tvip310pi_rsa.key create mode 100644 brute/badkeys/keys/Westermo_MRD310_dsa.key create mode 100644 brute/badkeys/keys/Westermo_MRD310_rsa.key create mode 100644 brute/badkeys/keys/Zhone_6512a1_rsa.key create mode 100644 brute/badkeys/keys/Zyxel_fsg2200_rsa.key create mode 100644 brute/badkeys/keys/Zyxel_p870h_rsa.key create mode 100644 brute/badkeys/keys/Zyxel_pmg1006_dsa.key create mode 100644 brute/badkeys/keys/Zyxel_pmg1006_rsa.key create mode 100644 brute/badkeys/keys/Zyxel_sbg3300_rsa.key create mode 100644 brute/badkeys/keys/advantech_eki_rsa.key create mode 100644 brute/badkeys/keys/array-networks-vapv-vxag.key create mode 100644 brute/badkeys/keys/barracuda_load_balancer_vm.key create mode 100644 brute/badkeys/keys/ceragon-fibeair-cve-2015-0936.key create mode 100644 brute/badkeys/keys/exagrid-cve-2016-1561.key create mode 100644 brute/badkeys/keys/f5-bigip-cve-2012-1493.key create mode 100644 brute/badkeys/keys/kali-rpi2.key create mode 100644 brute/badkeys/keys/loadbalancer.org-enterprise-va.key create mode 100644 brute/badkeys/keys/monroe-dasdec-cve-2013-0137.key create mode 100644 brute/badkeys/keys/moovbox_host_dsa.key create mode 100644 brute/badkeys/keys/moovbox_host_rsa.key create mode 100644 brute/badkeys/keys/quantum-dxi-v1000.key create mode 100644 brute/badkeys/keys/tandberg-vcs.key create mode 100644 brute/badkeys/keys/vagrant-default.key create mode 100644 brute/badkeys/keys/zyxel-q100_rsa.key create mode 100644 brute/badkeys/keys/zyxel-vmg1312_rsa.key create mode 100644 brute/badkeys/metadata.yaml create mode 100644 brute/badkeys/registry.go create mode 100644 brute/badkeys/registry_test.go diff --git a/brute/badkeys/embed.go b/brute/badkeys/embed.go new file mode 100644 index 0000000..edf95ef --- /dev/null +++ b/brute/badkeys/embed.go @@ -0,0 +1,6 @@ +package badkeys + +import "embed" + +//go:embed keys/* metadata.yaml +var assets embed.FS diff --git a/brute/badkeys/keys/Actiontec_q2000_rsa.key b/brute/badkeys/keys/Actiontec_q2000_rsa.key new file mode 100644 index 0000000..46b4a5f --- /dev/null +++ b/brute/badkeys/keys/Actiontec_q2000_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCK/N4qfs6LpD5wDnKh/oKCSTDWaaC5uUPrAdLq3uE1WqtmWphE +G0STxKhaGi8TxTxPJnIT5jBlpBGx7t1CVFCCDL+I119IO478eb+JE62pjmMxp2b3 +nzP4BdVIF0lYSvYeWc5TzbGxgG0NLqDb076lTu61ayflWotlcVDn2264fSTlAgMB +AAECgYIBQPsXs30IhMhLfp07XWf0HkFutmImbLCoLvDjiwOMGCv2bZlQ+O64tA/t +nmYXe94Aok+ngYq5jH12DklzOHl1sekWLNCwFhmmXDS7OkpAldUCnP22Bai/Z7qa +BPACDejTi5KJsvG4qKuwE7tAMVptq9hbpG680c3kwpOJjlS3/mXBAkIA7y5WeKsL +1Mj5mqysXKp7yyAuNkhkXmGJKBIIht2ZCuYUG8WExCWV70w4ByGQoYtqBWPuhQLD +qewxsPx3Gva6MuECQgCUwuHmoXPORDC3tCI5+KdVHXMgVB8gZnN+derTcG8Uh9Uv +ReT/1dMLqa083StMSbEckLfRtG4y4ozxXK3hHO12hQJBOUXCRQDIQ3qgck44s7PL +Etew2SS6i/MVEbhHvGuhsv9m/0NryEArx/JbVDHQXS5yA7prKgSAb9b0CypZJua/ +rWECQRVZJkwPrWvTc15ZlnPVUEYxtLzV9aWTrk1epLV1NCuxFpHzKNriF6hb0EWk +w7rt9GHSDt8kUpAw3OMiVr2whg91AkEMKqSqrFTKXjKilkRgql6XguWfjws+NFYH +BsgpJLXJvQ94WrWpTmCXcXjTvuTptTsfyrhLiuzLJD5T2ehqBE5IKw== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Alice_1121_rsa.key b/brute/badkeys/keys/Alice_1121_rsa.key new file mode 100644 index 0000000..8f8b958 --- /dev/null +++ b/brute/badkeys/keys/Alice_1121_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZgIBAAKBgwCGS+2tZHa5R22/6HBSLpN9yik+zUYms8pZ56poPd+e/Fiz9RuV +81zctq5zobQL/X/GUqum2eaDvg/M4Zh892jBlasN285c/aMche7tNK2NWhQShoOH +QLg6dpY9irX4k7GuJIZqkUlRfauOVms9GrAWHjZ7Ts3wLTlYImzdb+dIIekZAgMB +AAECgYIQbVw63/WXz1Fw7o1CuDjJY7+s02430Lk5Sxmwm5TtfR+aj6paqsSaqOvo +8Ag2iUEmcLlXdQL57XdAT/4Xlynkt+zikkUWYzEjOecOabNeyvkrt9pY/TNUM6GI +5gP2bTyPP5Jr6M05dr0XgTmTAME5EdVOsumGqvnG8+OjxgcqQpppAkIA0HyM4iFj +YlHSXDIIxig5c2gHGuufph/DR95iRIVoOc9kwbsWcYbWUVI2O7X8N83ANiZlzcHB +uGDY3rC+KyUEwY0CQgCk5wO6kfm4KgofIq2D9+LBocNQgLDyouaZzJg7qJ8FuNbN +qNitV7lUYhXWIwr7mfkb/FFeyJ0lqyaqFG9YhoAUvQJCAMQE9qj932VnrLMGT/2P +gESjgFqtQtN8dayyyA3IqLAB8Ke9cWKX9hVLiLYnatZE2v2OqJUGIU9rrzvhTUtf +aNNZAkIAgLMgMOOvH3IIFkbNX7r/ChrDQjg+YhCLo6uPgLhY7HFXjIlkGt3lchtf +aZJOBxIj3xitNSmjpHuQoJt0T4Yhvo0CQXG//++Zy7aqwcwlalxRkbDMq+pDCULR +4aO1pxRy7lTV1gTdyuypy5f5rTLEByZ8/K6+q+XD94/F5WrRUBBD+ewz +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_RV315W_dsa.key b/brute/badkeys/keys/Cisco_RV315W_dsa.key new file mode 100644 index 0000000..1e720f4 --- /dev/null +++ b/brute/badkeys/keys/Cisco_RV315W_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvAIBAAKBgQDSXkD+i6T6MbU9DT+gk/jskwl5z3yxUQWhxNcoxdAL2FplMALR +FEaKXpQYA1L5JzhdXIJWiermPpwUjvnPrTgZTsGsj0ehOgtCQNRELCaJAEN2ZZGB +NMWClVq8KfMOj6GRog1WzObw1NA9t1dAPE+J22mSx2VWtIIfEa71qyXnfwIVAIjx +b6TBDZKeKXINEmiSxVKcKAoVAoGBAIBtRnFh5aZgakK7/uCcpkH0aXJn2XA88JyV +SzWQXkWZs3kJS0Ml9Z/ZWwW1D6XqgKgzK/yNp3girItoxtFGU/z97etwxyco/D8i +uEV6X1W/ZZeJBOgMu4KYtUqWS/Rm4JTgQeLdivAGIzXO8nmyC26ZSouJfuCLBcWB +DxN1hWckAoGBAIDCCmsH9HlIhwAwkDHWrq2jVsiQOPfKDNS3zzVW8NlRMtpzMrG0 +Qa7x1u8MEoICYhcQ8dsvWpaQEJgv+aKGbHdHxSFzu8Wj9n3iZdWWwdE/GjiE+iv1 +UzW1Cus0KAcy+kXVeSGnMXK2jqAB+bP6T5mBjpZUI+UGkYFiXIZ3W2+8AhRpt3Ke +dLIXnfBScDMdRepvTzJvzQ== +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_RV315W_rsa.key b/brute/badkeys/keys/Cisco_RV315W_rsa.key new file mode 100644 index 0000000..700b90c --- /dev/null +++ b/brute/badkeys/keys/Cisco_RV315W_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCACRXscGPHm5ds/wtu5oCeYERXSanZfR4MD64KsEbPmRJV3f1n +6Qox1AxUhAr5j0XFtpGeyy0ARtLAifD6DhPRGaCJt8wMjeJMoSsolOL7D1wfFQU5 +TIBHFQ6ociGv/NQX2SJqp9kWts6UAWp1wVYtuC58eWelRv9CPPNZVQtfG88rAgMB +AAECgYIzMm7f0rkzchYE8Z9Cw00fB2OMe9o9K3i+Xe68O2y1SoI4b8IKsPbwodgk +W4B/9jhyLOTh2GCwQaG4d/AyLTrjW4GyW4zKhADRhB1CRf8aopNfXHi3mqeG25YN +1Hy0AoLZ1i9+8JUQ2UF0iCNiOdxzOt+/+oyR3rfztYoCSmZMkV5RAkIAg1Bmdkz4 +dveg0t79jZtMFq9ktScwvMw9HABujAC6e625IdegRGjKhO9+jiLYYh+fnI5VqZ6v +SpvovnnyTSKBrlUCQgD5m7xKpoJgb08r0PlM936ExwreTZY2soO7d/JJ7s2Frff5 +9ax6MSxAIUMcTj91dfvIHnP1PBLfMr//a201nvoHfwJBXjXRvTBN1rSkqoWXrf/s +IB5oB1v9qIZzlWJt7X4cTN1/hFs6PbpdCfD1gC5ZxlRf5CduKAqUQtgGrJm7EEIg +fh0CQQKez+s4tEWu5XXgS72zG3DpZgPPbLRGS3u8Vp7QEvhES0YkfhsKSRyjPMEf +USs6gdon96+rYcdWEQ3tYsgdUyKhAkFfIyu5hc7gsW5VdZ2VSqwSZD+AvMbnPJse +pGuOzilLgRBt6sQypoW/jC/6cx8ed3gvPZJMTmTrmKHNAv9fAgWRSA== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rtp300_dsa.key b/brute/badkeys/keys/Cisco_rtp300_dsa.key new file mode 100644 index 0000000..7a6dd76 --- /dev/null +++ b/brute/badkeys/keys/Cisco_rtp300_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvQIBAAKBgQDFJj2bXyRskFtIYj9x1uwWFPDEYSrVz9nV2XIpflSZ1XvJ2Umw +4xREgFEdv/sKr9aazE7+Or+Odryk1DQy/oE9vaCcchVP3/6siRvjRuGwlQc3tNFl +gSKDGDpfHGsEpVlLcx6hsErFnr64/e/HwydZ1vP4OvmU/yeUTLFzcZv4PwIVANYH +nr0a8eLhdfgh5qZKNjiut2QRAoGBAIsn4griDXnu1F1MXoVm/DuPcMSBeGubZeqf +RXnVF39vDL30wKp7l0KabJzbRxBe2I9YhCzrhw8AEjtgMtVneqk3JUwFJPkh439G +wmrW/ZdVej4EDFhHYF7U4Kgw/ieexx/e0wMbuiqz2QzgFVFdod8OfQhBBj20ekB3 +fm+5YFedAoGBAIPtAfYWtowYMAKunj3x3SGTk7w00KBZisZ1O8F11Km1XhhQGYf3 +ONOpWnpzVFPIxrS6Neo0Z9UBwe7CZ+8EP8bi1eJc5sPDMAIp1bWddBJ3lRtryQ2k +DN3L8xzoaKPvuIHxhuh7TzQhVIn/6ffqBYzk53iZe/xJVfelyQNuDzEnAhUA/DI4 +Hn2XDwL5WIoiC8jEqb8afqE= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rtp300_rsa.key b/brute/badkeys/keys/Cisco_rtp300_rsa.key new file mode 100644 index 0000000..52e2ac2 --- /dev/null +++ b/brute/badkeys/keys/Cisco_rtp300_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwCCe1FOkJuYLqOaeRmGSO1Ads2wOdyoePlLLnqFMIzImdza5Sjf ++QyEbNZdhMQDx0R+z0Wu714cnAQndaM3Y5I0URhpk8dfhxIIt/MNr99/B9glhHmx +d/twqKKNVvqc+f+Wztc3CbBUb+BbYvwnxce3JKZAEOWv9B+8R/TaPuZ4apmxAgMB +AAECgYIEIoC4f/JXwFr3fwsAy4FFLNL4Et+ByHs9hfrwOaOtLgqmwgV1EmhbLtZ5 +0HTqPNPj4QWA9YBuScBJOLv211PIVKzgNVISLRlY3HcJGZP0Q5zQ4mOo7PoGpPoM +Gv9NhUWvDj2GDM8oeJ9+SG+mO3lwr9wRELAT4RR2yN9xK5TkGVW/AkIA9+KuMxM4 +lOgDtfS+tmfAqTTTPOAKPnuHRLgC3IrxjXgEjxETMOJQw73ZKAoOYbXSaqAIiq+U +qUXcuO25TVexulMCQgCGwMZnIFROulBTYlhvjuPxTnaBi8yGrqScwGge4XTBqpGe +ftfRT80wCT6IAVaYSMeho4lte8GR5pzwceJIXWpDawJBEwWFAoxWCi8nob3PKKYb +haB1GTXD83l9LsvEBHJxCL8N8oCH7XdgZTTbRhRHeD4AkIgJP8MrcDXZMyOi1YmG +LNsCQSE7NTKFdIUdyWVm4WxRjsEZmnwEH+Iu+4V0pbjH3OVNzS+LFGoYBgAMp5Ee +014mxKSGEgQf+vKiLp3VV/qEnwoVAkIAtlL0mQyx+wfaHcYOZZ6T0JqFJMaO/Nmp +R1pDSwCwZRhvOCU0yXYpJo8F/Bc+syzGa0nbr8vta5jCWw9pHBzplMI= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rv120w_dsa.key b/brute/badkeys/keys/Cisco_rv120w_dsa.key new file mode 100644 index 0000000..e1b52c2 --- /dev/null +++ b/brute/badkeys/keys/Cisco_rv120w_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQCKdXLk8atJ5VPmLWf+TWifepkpHKNnRaOKFbhS2+/wovwbf74/ +x52deg6ixaARLiymE4enAMDXoxJeh5f1MnHAa0pyNI16k2YdtesVbeJYLp2+v48t +YWXYKgafUBZSsxH3Rzf77NB4rzqaNanvW3iS/44hZ+hh1/FapqXhlfM6uwIVALVs +ORLzNMG9rX5V/Weep8qyvTk7AoGAfEQzgwLT3EJBraxVLBnmjGTEXIB3XH9cn/gh +wmfICAQLV1aqI16P7N6mIoPRmN3BOT3LS1J6SQtBN3D7j+uk4WS6z8lGJCJXfB/m +i89sJssV4pTJiEBhGlAKlW4kpcoE2woqEmcEunJTZWbbK0YHaL251UlEWu7Q3ogb +8u38rSACgYAUAlMvXHESUBx46Bmbpe9u4zNdYwM5wf2KLnq+fRgp8GWQX53f4SNu +E1PxrjV52uGinIPNHNe/BE2lpeJ2I8uv/qnwjFArBFnS9a//JM+8CivOCjrJ25Ya +iU95GglOmkWrtUyYB2pZsXBQI5J4uvNQIuCmE5gbUP3BR4d9eSZ8DgIURWb7OxbY +GOCVPvqOxlsI7e1XbvU= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rv120w_rsa.key b/brute/badkeys/keys/Cisco_rv120w_rsa.key new file mode 100644 index 0000000..75e7dfe --- /dev/null +++ b/brute/badkeys/keys/Cisco_rv120w_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCdyRRI3+5n89wzquJvtX40jIibpxFiJLEf0+3ltVonBIRT3z5d +B0EQcQ7efzhH33S7ICgUacNZFsiNGytuyYdlQ4WG8a2D5mSypIFE11T3dLo8JBv9 +8h94jXqKzOtV3S+ypdRBHALYA1X63tWBOI3AP9/KWWzSyOy+LmXxz8n1nQIBIwKB +gQCZRv3DIq0Uhn4jkA8qsE62tGdyoktmp0z6WNh4vsya7nHrE2EnK55KfHTYI9BF +0cHaWcCI28UUtwv2ye+tc1BSN2NiZVN1zXyNIp7J6RrrlfDDKMrRu57RgzLKpN9A +28BSKu3IwHRzmemuqCfeXg8IXEXrLY7uAIFR4HpC5bIcCwJBAMzZxVnxbx9xjzcx +R6F1bE9FzVfqeNq8Ai9+mJcPUUR8otW7vtKd4AyWKYdB0UuJcwELCYT7El0HhEBJ +vEECg00CQQDFLtxNi9A3Lyi6nXiny1xldQ9PYLw/qWJ1C2zmfbpNErs6fhWDR3Hn +XlJBEEsEqYhnX5oHba1NQfw7vHg46nORAkARjwJJibkRUuBj0QYjyDx70sh1P/u6 ++iwSsxRkuCuJhwakmxBbMhqEvGn1po5ITZwkqSyzoH3qt4BdSCYUM2pXAkBDmwm0 +L++d5Eh6fyINpM84umumL8uDiogoISymzAVcQO+8SHxnhjWuaXtJge3Vtnfo7ZPl +SizKB/61ZTCIiuXLAkEAvufOOrbbeYIUUoP5331AHhqHNk2nNi+0YfEdw4gMq8i7 +7+PV84Ij7QE0URFdGnHqNwKAvxa0DllB+5sMXPBW5g== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Comtrend_AR5387UN_rsa.key b/brute/badkeys/keys/Comtrend_AR5387UN_rsa.key new file mode 100644 index 0000000..cca2f7a --- /dev/null +++ b/brute/badkeys/keys/Comtrend_AR5387UN_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCCuYhaXf8RgI8tXtKZMCoCTcuC0VCNPjnII/3gee48KhD0uAs3 +NpwYzvKf08G/PDllpml++kb8YxdnT4wqQEiP0y1hAhpDr/o/UR+GIBqm4hDVD5kd +FntVcxFIxObnwIbqcLHGdbpxHQAspzhzNvKCOfE2ttIoXJa1hspSQAevc0q1AgMB +AAECgYIWxrGhoQ/wYjhP/N3vj8s59Ij5RXPl8AoMO20f386oz+F9adWWbJ7c2a2/ +OBthMOKFOe3WkE+u6Krqtpriv6mE2VGkOsV+hTuTLxxL5fDmV/tuvmuS6a9SYGii +H3pSMM2tT2SN2b2K6IxlWdsCnymF5RcXDzgB+Bs0FhHeN8syrPU/AkIAikqgTuPq +DhhDK3jSTlRXzHt6TOGmyrZBvClVVS351j7aKKKXc9GOSm8bXIPozgjfnMrVDRcy +OGwx5TRLAyQ/2DcCQgDx/iAzTjl+graplaTywTTopedB3eUvxiztQzcbOOeSWFHJ +3ygEUEG5kLOAF78igtKZ1GJ66NRt9BulrIJuqDsmcwJBWH+xX0sTebmlIqjLTT1E +gqNyfMjbSFaicpuw9DXb8HOAgQisC35LpO69f9MapN+g2mIjPaFejiUPOTLh3jzQ +brkCQU6H/cMom5fmq4iVO5ZcBOOLE2VYsUuzsFi++18mNGBVsRUmCkJ8GRgzUU08 +Z5a3hcjOF7dzX5zfHynAtgNbLPbpAkEZas5pS0EJuZVO7VSgO+tdb0GLuCKP/l+K +FTraQJX2XpCT149AVee4powVSRDVABgWA88LEbyHdhC/4irUm9DBFg== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/EVW3226_rsa.key b/brute/badkeys/keys/EVW3226_rsa.key new file mode 100644 index 0000000..cd43bd1 --- /dev/null +++ b/brute/badkeys/keys/EVW3226_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZgIBAAKBgwCDq1kGqqwdQVryCNcoyDbBpnL/okvM2d9NmR0OjprcToCZ2TZ5 +WUZt2BGwPE1QLJYskjhv7GwlfQ4qhEqHDg35wMrkO7j9LTQC7KW3xisOLuUil4Fm +MxPkol6s39945zBGpjw0l/BmJnUDlutxavkdd84fppFMwXNp2vbjxV1SYVc9AgMB +AAECgYIt+AYSWkbRxe36/1Qi9FeNn0+Z6S8em1gnTtQCr43oaW3jiJ7Imf8JPXzb +cwoo+hAKCpiylq+hHPpzpJEieqktbv1cSZhZ9omyP00iGJE73YSdeiwZZq6UtvxI +AIqDEql06a7OHhGT6BfK4q0vHm5n52SKT2oiOdX3WkJ+cPwPcxZPAkIApSaXXJj2 +GsgGR9Wxz8nB6Y9ZIYz0BE4QDiqMaY9wcZCQyrEPb0Qh1fR8EsZe5sjvBc545z8T +xPt9CT2AntcHRRsCQgDMGbl0lJtljLjNeTOEwVfev3Uoiu81UqDTe4rJs62vo7Gc +abprhy11Fdio3Jgsr808GYbmOVYpiWyGiI1L2xAShwJCAJjNjQx65nI/EjiSytND +jKwqGsDFJt140LgavBHLSrF4nc88ZdiABIJulAHXEuWbkjQgJpNnNEZ0nerXwdK1 +h59zAkIAxMNkDCUcLulcXBKlfS2cFc1EGILgm+p9y4RFx7BmGHbaSq2PIAzg4Qjf +p+OK/UG6vV7qs1bBXyfyLTzF40RcmU8CQV8gTmC0kZLGfXmqYksfPQfdRmYP9Zco +1QONXpmZmH79Y+QQURO2hpbC2kUGxbQemrBz+yORl7LS29tZkXkYquQ2 +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Edimax_AR-7167_dsa.key b/brute/badkeys/keys/Edimax_AR-7167_dsa.key new file mode 100644 index 0000000..d8b33e3 --- /dev/null +++ b/brute/badkeys/keys/Edimax_AR-7167_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDe6mJHhZhZOCouQP8MH20Jc3yD8QE6DX2EVeM5DAqrOOUcE2wV +lpzndsY3xk1vuWIBDuYc/+sUIjdm1lJa8yzW2/D9CFYCaEPYqV5Y+4zKQSt0ndWQ +Ic4Wj8KQ68tBtgYCCY5LHlT8qa5M1Wv+zt4WzcZDtucHER7xEefCJvXnJQIVAPHq +V268+AvcrlrfQdspoMObuxeJAoGAPjo83vFBdFKwB+HyFXF3/91hKNcLAqlblCXG +4fXo16lCFwQfUXgWU7cgCLsm1tquL6bQhXl9lCTZISnOdI4xLqrRWwRv8NUdjV8W +6MAbMTusmxLznI+Fxy7mz/aYpxBBGlMw1bBI9KdI53mq4Yt8G12CHyKq4JoIAeIb +FmkCgnYCgYBjjW5/w96z5w1skBQbC3j8RFNglupKS7iaXa04yw2dAz7P79OQe+tj +4KysKMjrHrfz5zFNo4ivpZCNiFezuMdVNwSNoQlARzNPy9ZW2dKXruPeya/XtpdW +ynBFRDtjrmPr9tpXaM2fRYlHnutPL7S7Cc0cCiFIGeS2prZQJ1MsPQIVAO13q/E7 +HoIn8eDK8LFtngs/6ZzG +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Edimax_AR-7167_rsa.key b/brute/badkeys/keys/Edimax_AR-7167_rsa.key new file mode 100644 index 0000000..61a9ab1 --- /dev/null +++ b/brute/badkeys/keys/Edimax_AR-7167_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCW6ToMbteHdLBddvV/CP3Hk1tF2Av180UFvyyK17lpOIKevtf+ +NWso/opmKKwePxAaGCz9cDWkJd2/Q1jcBJmh34V75CdUSGl5jhFso08FPXVKATeJ +jRUN9Q6ocVKOdpfpHP14FRdDLcLxi6wXsGtjcvcMcST4G9tV8Itgogu4Ox9NAgMB +AAECgYI8b2NRVlRXCUqV641uFeY39hG/20ES+pziK8jFQ5Ybhsd8llX+llr3XFSF +i/U92ahzosnlpvm8ZnOy5GAiIG98LN3m8RzrqhRZnVLxRgupKMqYYSWMzt7rcvs7 +OZPq3gpKyrDVD2iyRdRlzXEZhs5h0NAgNnLeaQLVjWtu+6QxSVKbAkIA5ArcK6GJ +j7Ihii29WVzDxCByjZO1GamCFVjNirJSxe+yFzFztfa+ETUgxrpMWV+ePw2z/R8k +v9FW7fQNzU4osR8CQgCpaZa0sh3KqIf5xF9zrHDbdYi77/80qQGmRxeak8enEV5r +AbD27IoBEb4iFEfCl2Xpy0zXgTgOrlcNrLsSrsjGEwJBbRbA4mJhQwtsvgRJe1FE +GTOIeS+6x67uGrYjhYDu1d9na27tspN5sxOePVLrqSlzsygj/SeD1fsXwbcpTxhf +oPMCQQuFc8tcJayHo5+33Cn4u0AhkSf+3WNObx9IzHElxbk19C7g0ZEpawVBmKWm +rW6tby/kNJifYBmXf7IdYieWHW9jAkEF0tpU7vobC/FHQ5II+bNPV7vHJASgx9C8 +hUsBFrCmNlFZ5SGJ3EyBtjgpj/4t4zmxb3HPbsltJQ9MShVf/3B9jA== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Huawei_bm626_dsa.key b/brute/badkeys/keys/Huawei_bm626_dsa.key new file mode 100644 index 0000000..e9d4377 --- /dev/null +++ b/brute/badkeys/keys/Huawei_bm626_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCCPuQYHOG31M5kc5H8M/GIA7jXeYdoi3dDpkRUeQBBh1LJSt0N +MmXMTMp7PoSvl7XtZOqn7tIbRqhrE57o2ulgc+0sv+APsLsgsJkjhXQdeVc0FUHz +BfR5/gbeQrjjz6zxd8QBn0XYS4TIYROk/xMwRq+uSsIxcpGrVWG+avrjxQIVALVc +6epdRIHJtE6j8F3XNIL2ZpwNAoGACIC6+qoj7GMhuGtqVVL3HvWcuEeUj63ECg4G +ohZrNGvTCxCTItBZj04j8W2Q9771tY8DH1NkDOSO0FDzNKcPeflwp6JGH6WMU8pu +UQos2HkOsZh+kTbpKePUonVgrCMvA/tAk/Bh5crvO6zmYlBwIpyXKJruFHEQ557D +ngeFxuQCgYB7ojEaYXgLraLwn0qeHJPm6DuSZy0hYiJDJJV/60Vzz4Pz2Q4wU/5O +0BGGMtAsaBRX5nltTwT2LXoe3MnRjdJRp2k0JJudUVdxd0Mu2BAsoWoh4oBCPJ9f +zKYiFrN1Yy9A69HQ9rzKBYcPvE5CVGcrfiv5MyBtBetRkMEHbNjnJwIVAKVv6Ocu +Q4T2F4f1grUvXjVWbCoE +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Huawei_bm626_rsa.key b/brute/badkeys/keys/Huawei_bm626_rsa.key new file mode 100644 index 0000000..e8c2814 --- /dev/null +++ b/brute/badkeys/keys/Huawei_bm626_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZgIBAAKBgwDa1LyHnJ7NjTYkCQ+6EE+jva3Do4gN4i7lO/8Ti8J2SuoIWfwv +zj/5VqxY85DEUM8kEOLDLaAid9mbKq02+4Z1y+mD+dzmQbFSoxHEtyejSmPdgPFb +urCHV514lmcCABU3mFCx9bP0oFCUiftg3L7wDLnG5DXbtJ7abvN7N5YdnfbZAgMB +AAECgYIEiT1hOENocmFpbNCpJDZqZ/+mmwiydPpQ4SX/8zk0N4hTZP4fxZA47G2F +KINbNmuczUbOEe+MV1yo5UES7zLR1T7r5gHmxiVf28Z0Vz9UhezU424Zx8kN62g+ +GpaRFiCDgjVNijNgvwZLqIO9/rCJOS51q505I7iMDWJbJCQw/sLBAkIA/OVhv36H +HLkq2YRk+HpfCGX9XqYiRFveL5ZGIdotvozRPAIbkJJ+OvbZ3vHvlWnwidHQN6oO +SrvuXI6vks2T3rECQgDdhFHc/7MDx7RGLJc3Zn3+UR3Q2x33M8XoFlsSzu8mxuh4 +g9mWoFZ2uVSkBC+tQxPcgUQQ5W9cvG47zpG/0wQ0qQJCAPg5FVZmFKwGu55AqvKQ ++hI+ORDbtCqwmUbQEwpLIjcz9HMYBoJCp4+sl3CSu3xGPYio3dylF94W1AEZNA+9 +pHQhAkIA28htcNhCxX1fYEDdyBno6GuTKaY+FQdJVPzZX8/xeIfiH7CyMsDjCOjZ +EeMVatMi4aVf42PBGkHbHXvXIHw9sAECQR95LN+rY+GaB6kQFw93NQfgaRo5Eb15 +wxVuDldiVQgOjhOto3j66sXJCGb3S4viHMkDzK2W0nmTHlmPfgHNfI6q +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Innacomm_w3400v_rsa.key b/brute/badkeys/keys/Innacomm_w3400v_rsa.key new file mode 100644 index 0000000..ca34347 --- /dev/null +++ b/brute/badkeys/keys/Innacomm_w3400v_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCzj2Ow4gPquTYzutWqUWX7ENRe1vBbqvqRIqG94Jd7lFyCrdf6 +jAjDf3WhcqX+4F7Aj30EtWQZ3pY1LdN15WklycJEhd6gQhGIreDkhlWZ3ThwsQDi +sEbT6lzvgJ5a8vMQyysuKNoQlN8SOWtLuJ1lpAJb+VUexSB4/RgHYbWeVUlLAgMB +AAECgYJJJB3oFdChoqsc/Dx/oaOIVu0i6qvCs01YpEI9DYi2+pKKWpTxBmylcbxx +NLnALhX6mxhCPD8XuI73/4A09GtVy/2z9Yq1+4OkkUSrK71U7s6VYaOipfXqdJgi +7TjSjcTuD6hUlRlM3LLD0pi371fEUHVhiIrBn8I3/AZVnRz06mAhAkIA/sHRBQB/ +t1GcSD4Ka0BRU277V9Rr5sVmh+JTCau582UIzdtQlB52LyDpunLzE2boIrlp2d2e +pkNRAsCV9E4orysCQgC0b6d/7CNxAQvg7FS8qcMsgBgRqpASAyV4KUvUMuC4tZQD +nD1ff2sWLqmLlgDUbaLEG6i5MAI5rITaieoMUDu+YQJBKn3DUjVCGB55lacbb8Yb +inIBZCXfFW+paVK7jOpiqpEYhdVlvD7UcYno0htoJb1NTLVmTVtkodCqi2351PTQ +Xl8CQTHrDej7GFcEFhg06cCEKsXd7kq86DzDeBNFQYBETx1qxrc0+1m3M5YgdLF0 +X2hlqHkeuc/58zOngd2/9+tOKykhAkFRcS+YUIrqfS9JhcowX3y+8YqXe1xCa90Y +EbqzOHIcqiO26AUYGg/Bp3y38g3xyswROaKF24+miNEm4PsqqzmXOQ== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Linksys_X1000_rsa.key b/brute/badkeys/keys/Linksys_X1000_rsa.key new file mode 100644 index 0000000..f241e63 --- /dev/null +++ b/brute/badkeys/keys/Linksys_X1000_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwD4BGDq2pSt3leo50uK2JNyAKo7mxCJDtyjLunr737R33liUXCT +yzMnk54/7XPVm+slE2Fwtn9zsZEEucZObBOlWEEBsxmv9xjKBpnujxPtgOAxiDv8 +/7BlCXKfCJ/n0dcwR5RHKYqiY5pkpHJ5wa7HY+JPrRRfH1JUP7aVdBls9udjAgMB +AAECgYIfdp687IHUFNLWEJGayaLa4Y63BRLx8Dq5UOhIEBfslAcYG/DGLw8MREPd +JEh/nsoT6AOCgwRkLvCQwuiPZlKPtQSJlh/vWnDg5dMpQnQCEOrIY0QuQ8e8X0ru +PMf/vRoDVrArNwK45UAtdUj2znveDh/powhHjdaKttLlQYHAQzphAkIA/9gTgtvG +kHArIS24kGM14shq8nyIFoEmg2dxLSnd73m3YeNFvRpFioqTRaT3f4OMXvebVekK ++FOI/udj6zvutBsCQgD4KxS8Cn59SVTZhzVP+rb66DhN80JFNYOAoPgAvHDdSsWK +8wihoEI9VvlEmXDeY9180tYiZGbO3r1Ronkl8k9+WQJBNeJhgZ8ePA+T1eIWTDrI +/6GsfLPjybGb1fM0cAmwV56wE+rVJlXhLaOwDuwGwJGJP/sweTCjAdg+M0myzrmJ +td8CQgDr2aWfACouT58ADOux0whLdJ7uFh4JQE511IVdzblF1mXfJBIz6OaK/Tlw +5JLMCATbOFSh4uGCAVXTW6HfWmYKIQJBOZK8wpQRbXnybYsdctMvIzpeQ0hKNB1G +GkE7JG1YfcZgbPnBXlEUG+wBAme34Lexz8TVzUktGGDuW7sS2IdvXV0= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Moxa_6150_rsa.key b/brute/badkeys/keys/Moxa_6150_rsa.key new file mode 100644 index 0000000..1e84a9e --- /dev/null +++ b/brute/badkeys/keys/Moxa_6150_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwCbYb0A2Hm6S9zEWopKGeno8ceXENbFD2eZLO0CVLZXBAUDubws +ipQDv7KxrSNW/5iUh24gYmIeLIBQ38YJVylAP/8bvuxSv7zc2rqgXsKDMeFwd2vZ +dcj/3ZldEMIOAZhUG5k5UKqw0YU9LmkGa48tPGmbY2iuTxd2iFBCUa2r81lDAgMB +AAECgYI/kUNNAsL+E8YsMGxN938J9Uw1wcZTlUNJV6dY4kY8oCDbDtPslv8J75WP ++eiw0V1fOm6z4fwJIySVsY6nyJhQN5sXXxj7ozezAQXD+DbqTFb1tof+P6jbD8iJ ++DjKGB5TnjnQwtMLBCvnFZfutZGfx+eCh6z8qeFHBCyYRj72lou1AkIAxPsWIIks +EypovXG4qNeg1HGusSq3lsTF+SlOOiacpyqmRdDK3h/9WJwerLOT32TqbpGItdc1 +kJ0jLtkfVZdGdCUCQgDJ7+edWkDLzkCKlxxHWmer4qfFl+vjShnDfCRQ0VCq1TWz +0OWWgXRJs2dKvGvMjDbjx6QUDQq2pZJDCHUUsd+BpwJBTNCxp0x24J54K/BuF4HH +GTGf8zz6TRQ9M9YUyH6INnReVPmAMzD8ZhKzr5tz/fjnpuigDHF5VQTCVKGdI46T +jfUCQTA/AAGSWEWPDMHE7tgsUznTR0Px16KRNqyb5qXrINQ+MY7QiGN9f485hkrA +J1MgwA+Q+jqmpnssBAXHIq/x/AvNAkIAl77E4ErEl1FwbvO4xfvCNmjyfH0l7V1u +87Uv9vPfwqAcrmhgTeywd+C7GcpNXgLptVOPb3XAk2gZzfXLewLrzeY= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Moxa_ia240_dsa.key b/brute/badkeys/keys/Moxa_ia240_dsa.key new file mode 100644 index 0000000..2eb54fd --- /dev/null +++ b/brute/badkeys/keys/Moxa_ia240_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCfew4NoNvEofEzw9LUjXIu6NCBUkgVdK8PQmn6HU/ixs1APKyp +MvwMIZvuYXbBgpnDHAqjkM4JzDDk0pyrTtCT6ryuOIillV4cwEbdjv2nzyiyTXms +iMBrHPfNtmUPDZB2sSoqXfSI5RTMWSMXM5Cd+WdGoa8g7WaHTZQNhKli6QIVANZq +/Gr/QLVELfQ89apfpPXGBUJ9AoGAaeeUGj1zZyS920KFhBg1gOkxH1YSqGtFHa3D +zNq+swm7dZPVnRuUGTt9ZIxlVVdrzzUyY1jvIJBAF8/XCVaiVB4HEAhcAZQTttkt +MTj3DfROPMpb/Cvi4JAF1zeLK7YwZvaEQb4j/0CfqZXJacS6ZEVQm6VpQfOm0z0m +vYgbjBICgYAaC8xgchB6PKuBkESHeBYt9zAK7MdlMfijl34wVVP9bJka00CEFKX6 +Qho4iMITQWDvDvJjYh9fCftoYvGSVDg7LyaYw/vvNgu9VKNj1sdru/Ts75qF0EMo +XQ3Kr7dXL15gh4MLTrm9oF5hRTBip6qEq2uPrkA8leDVe6YeigWdRAIVALHG4/bu +QwtnP0OPFQYghR4+o/lw +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Moxa_ia240_rsa.key b/brute/badkeys/keys/Moxa_ia240_rsa.key new file mode 100644 index 0000000..dd8f958 --- /dev/null +++ b/brute/badkeys/keys/Moxa_ia240_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWgIBAAKBgQC1bUUrwOmIqXXkFbaxa634g74qRqelhkK90Ki7BmjdJNi1rkzD +YD1dNMl+YpSVyzzMWtFevMcDagwuInC3ALffiKnJgwrmmHBnKNEjbktccjFw6/8V +azse8pzhEOYSYL8WQAZZ00Lrn5zK2jWfAHIazPjA7/eQ3N3SB3OYk29HSwIBIwKB +gHc5LXSGFc7Vwn/xAwbgXGF7JTG5Ztp8zMXg4+idz+HHwZv2MnHC5n8UD2GufuYX +13752gsOVuT8i6lJ1QqLcYMn8uICLsWixNJNAEZH81vL0J2Wfqrs1hYRP+B/2wLX +KgLLsIndYehEZng+099XMC0bHTOgnwai+Hfb9l+0w3UDAkEA5t/VnhWiB9aj8XEZ +ZG4aBUo6KfRQVcsLlHg06cwSZGPdqR9z+CDXEafB+sKkEBpbuaC9ij7yduQuoFlm +ouiD1QJBAMkr057anWFButh14d8oJgo6c3jatdmOd1onpG+rVLTuxQflYfmNF0dC +13S2IYHtCyG8RYeO+HkbXFAHc8cwzp8CQQCeUFf3X0qJCDXnY38gS3+OmUx0ikW3 +LCUym5H/Z10Ro5Cue/fAFoTY6A/8aDX8adh/SaaKrtIlo8gzcIDkvLIrAkBndaAI +jbAGIc3OaILKia0p1Ortsk7k6i7ApqxWr+Jrrf7uHjJjFVxtypqxDTXNyl1/EF5F +tz31JAOW30LbeC+/AkAOsL5BjSmiSeFx73m+fMnON44eoJo9kvmL3YlB4YeyWG7K +mN8axofazZHJlLUCUFb58WW5pZKM8wXHE37tXDs7 +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Ont_g4020w_rsa.key b/brute/badkeys/keys/Ont_g4020w_rsa.key new file mode 100644 index 0000000..73b2080 --- /dev/null +++ b/brute/badkeys/keys/Ont_g4020w_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZgIBAAKBgwCBKHkkEOzOs78wAqvrusZmUJUginRiukXA8YlJZjyOY3Po2wxr +Mjyt65b0diTz3fPuwaF2ug0giCYfQ+cAIF8ohzRRiLAVOa3ukZl5jF9soiWsEjOG +1NWv2lOS2Jb2bhtjoIJM3y+avsg8yQObqUGzHP7thI3EVheYta0RF+/iHBzfAgMB +AAECgYIJ4aTTA5B6h/n6nFm24UVIkqW2JaGd5A5uIoyUVwQPCMDXcdrTYLaPv9R+ +w4M0PSjqfVnzEWzrrj7saW+E7PUazpEdB1xLc2B7Edn7HToBFsbp7KB4oNlcpgSD +ZcbwTH3ieEMctnpnmPVy7prvWm274mH0gV/NoeWtrUlCzA8XGcRhAkIA1YyRwg8P +3hsdu7lt4LkkjYYY5Xd3YDHe/mRsXyattaXSB16DN4fmZS7VwZ5LwPprXA7hZ3vi +EMeah1TO0T7onfcCQgCa1UdT/p+nrB8o9ZC0FzdW9rfuhO7esf1Q0f6h53iaE61Q +gQQYCA29Og+M12lK5DBED/4eD//bIjzMEzgsq4feWQJCAMCva72MVab1FKkUMZ65 +r8+7Fa/HUgGMPkeQWXCpt8fVbWOU1hU/HJZj4iAoMvZXfpO8IYp8b4jwcfB2h36q +or4VAkFnsQHt3I4rinfrxFk+YnXrRZt0n44hke3l3Fy9LPl1pkvhqCWHuo0I9wNG +/VRElYFFc7hphamBpfI1cYGjWNTLMQJCAICRILA6fk1BJxAKjcQxv6i+f9mYolXR +D0Knq3lESOb2eJODEl0H/BXNAfu8qwFdrHZEM7VrBHeQ4eIpjUFXJZ/H +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Pace_V5542_dsa.key b/brute/badkeys/keys/Pace_V5542_dsa.key new file mode 100644 index 0000000..c0686a6 --- /dev/null +++ b/brute/badkeys/keys/Pace_V5542_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvAIBAAKBgQCVzNKLKMZH6q8xTaMR+JTRuvzcWom7LykjKrj72vtPiP1w0gHv +arsto6lrlT1up10QRqnzwbuX4VRYnz1Jubk1p/h5TmIYLnFXkQBxjWwMT+/bmImq +s8OHsIHhuhA+Qt4nkcwIzOzI/li3XOMiP7q987hRSeSiIv/W6/R6IhL0VQIVAOtt +A8w6CBdrqMfavOy6ytt2mST3AoGBAI4aXycOAC4aaA9JrQWpjuohsyOxUJZDZs// +WbHwVf70FM8quhxWlg3gPnpTIES6pGApru67GEYk1zlxtG6KSZX/TOuk3fiM0lHF +NvwBCxN5LCDMqQG+3RDciJGouo35qouZ0KyOHogJagDJPnNkJLmBXfol7G/WZzI6 +Twg/NRAtAoGADmYHtam0z6qQH2NChWIHAG7BCk0tej6E2O+iuWO0kTLCiAg+DpPk +dwkJQl3BvnxhlbdayfxC6+cBzee66yt9L7opXpfTT6fBJ8JTkHt7mq21xbGkcHQo +YjJhxkZ+xUQXGLhdZb3kQAOLHXDrVI+uhmGCjBPVnloeMzvNX+fq4GoCFQD3our6 +NlzbWw3qjEap9XBVH1AcTA== +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Quanta_LTE.key b/brute/badkeys/keys/Quanta_LTE.key new file mode 100644 index 0000000..68392fa --- /dev/null +++ b/brute/badkeys/keys/Quanta_LTE.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwC9P88UlGdb6WIsIcyni8zh4zLdrSORieNeZNXtDHiHzgs80XQ8 +FOBBaNBTBAib+GX6V8Aixvmh315+H6xyb4fQSlicpJ1lq4k7pKsrXGgdYS2FTPrX +A8YG+1beVvWeK9/8LjXAdZCzwE7D8jOSPh9dw0HIuPQhBCfxE4o/WOvYwVMZAgMB +AAECgYIByX/YPQgGVkt86PNMrD1qrylwbjWBJvUQk8Fw5/6d4rBYuk4fkJ9nArk/ +5XB635M/9FLuSSR7lrdG19/6Iys8SGlEKZUtSrpnOKheuZLClowpibir7TudHaKG +TRJqvOgCip6wurfu6m/+ZKF/Xrv3VeMOD9oxExmQRFDi8BVC5/WhAkIA9SLigj01 +xEdne2+UReA3/sJ3YX9RuyG5bSRXW/iwlmMAGZWsnlNeiXw17wqhaKfEd8ZpKTL8 +glH3NKRNpQnLXXkCQgDFouA20kCjNeGgtabxuRP7lndn8/4jXR0/HvEVdZIki9Z5 +gifC5+gxn8rKcyTSZZmyMYGwLQsN3Z9LumQT/DdaoQJBGKCAkQUFOcrSopv7EWoN +NhLjW3AnDd29ezGDdUHuu60GfYuD5AQMI1PPN0yiGpmAK2hLeFAe/hit9SPsiQAK +5kECQTP03rbqzT1a8+b4+lt/yWYRp3B3r28Ckqa+bqiykOn0rTyiX+uYZe1t0bUp +UhvRw/cZlruHC+noQnF5Hcg3PSIhAkIA8SD8IMws3TTRDwDEtoLdcN+O4Lcq3uNv +1XuvwBtjZzqyy+YOB6Cc2DXYOsBi1iJRQeqf2lrlFrGnTCmj4Ey6IaA= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Sagemcom_2740_rsa.key b/brute/badkeys/keys/Sagemcom_2740_rsa.key new file mode 100644 index 0000000..8db64da --- /dev/null +++ b/brute/badkeys/keys/Sagemcom_2740_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZwIBAAKBgwDh+jRrA8DSW4Euj9XNH4xVda+hp1E/vxxqkLF1k73SsUA9l+9z +3Fm8kvefX7H0f2Bcg3t9mqy33JtGuVqq89S9Hyn5tmSfSBVY/J0NM9WWSqbMw6GU +wCvFZq+Uoh2furY7dAQrfVyI97kufDnRyDbJQNnn8oCyFaWyd14d2O7wDrCzAgMB +AAECgYIKHd924wYg5MPVx860jjMEKG6ieBElH3MwOiYXs3OOGS1dFI49y6Gg1ZuM +YQZggctYmPJQXzpYSOISun2apavig4sWuPHq3PW7NAcv1Siypadd6rO+NGIIzCnb +AOosURncKKyqX2Ek0kFyL8bTDECTn3WBO21plzRS5bM7tYW2AnkxAkIA5DKadRxL +tqBCFqdkErMyRKBI8/FTga3ASPLxecPViaSRgVY+cPnbXpTSuG0P/ty3/vHBm8fd ++bRsRBIXq58M2P0CQgD9glnUTjVhrIjBveglU0WUS1xYDSn7e8yPiePXlGlCd5oE +7EbQqujZ820JyPdsHLHwtvBFU+oGM/asVVSYsMt3bwJCAJB4ZQctkdWeIhkbgqTA +JFwEKguexiJ8cRb+D9jqHb/Vm3UJt+BonvSDPeEa0xykeiyCu/M3FxZmnoB9/9Rc +jpK1AkIAmO66IknMClB7b+WQ1nOV1hBgdP38Bap5jV4yBSuTFHXyhGXFkryIHHOt +o+mI2b+12PGDoU8uLu/KL8yKbFK7bCECQgCc7A5lY6odxBm2EoZCmyaT4LaUzaoW +zOHvI6sKcC+1C8kzT7XT6CcHdjHBAeN+m4P4NHaDRAnW020i3UYCR2+pRQ== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Sagemcom_sx682_dsa.key b/brute/badkeys/keys/Sagemcom_sx682_dsa.key new file mode 100644 index 0000000..493ebc4 --- /dev/null +++ b/brute/badkeys/keys/Sagemcom_sx682_dsa.key @@ -0,0 +1,8 @@ +-----BEGIN DSA PRIVATE KEY----- +MIH5AgEAAkEA/UNXOgmhyXWRcOBZLun6hspiuS/Q/N/Ku0stYrD5kQr7vTcNrCNZ +DYDnCVFfrC6/xyBQGP0fhUBpRUc7eoJpMwIVAOLUu3B5ClDwieTcsYqmu517ABBX +AkEAgTfPLQcVxAiqkTowSvA/tVyXwCPovs3nBBK8S9nFW+ZzGm9oR2Rccrbfuln0 +AkaoUVmacGBxvD+z9qhhHgiNjQJAeBvdcIgYEly7EUl0bb1KxTGe97hxdM31vhJn +zGkgrzlyEhf2+d1V756A0mArKvnkq1MKlaFj4vxnqqGVypRfngIVAN/UwBqwgIP2 +IOnHijeFIH2NQF7G +-----END DSA PRIVATE KEY----- \ No newline at end of file diff --git a/brute/badkeys/keys/Seagate_GoFlex_dsa.key b/brute/badkeys/keys/Seagate_GoFlex_dsa.key new file mode 100644 index 0000000..3871c8c --- /dev/null +++ b/brute/badkeys/keys/Seagate_GoFlex_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDkoYOnXcJmvAShYB5LmN99VBBa5PuTNmeLQkM1z0ErN/MiZdlH +p0NDDssPXetMHV3tyiAfx6n2iD7E6wD1NkaVqRJqnMIUhXYsOsEMHN4Dq89R4rRP +gXUZKv5SxD8nu+ZpiCR0wrjYKlamogc6yY7OqIXozP5thVJ8iSn8XAVr4QIVANnS +7+avMkcsNNkYxnH5SzvsUh9pAoGALi2ywyhDtjiWJJCKTfPnreGJzJmuwtoR2p2L +JntZl8/rPm3Dqbi4ZWs08leTewpW8PArPrqXT1ThXRdBo2daECU8EDSx5Z4AWKBZ +zRwtDaYOp/zA+k+INoc8HcAutEuzS6fI+wjNroJ6Oo6HbLFr+wKmNXnW1a5nqFw7 +cgniomACgYEAk5ZKDyvACL+aB9yWFtiTu5pP9bi64Mj4kjLf2yg1zM+DKevxHaEE +3ZYOJBoyj+Y8HH3DZBPWBIb2KgrEFoitHco+QBm4QaQj3Gfl774zV+wmKAnf2iv4 +N6Ke/7+v9Ymb2hVAw2G1+bIZlmnuXy7kkWZwmh/3vtvt1VswYvK7p3ICFAaImJuy +4gGChIcehXfwjkqTD1M6 +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Seagate_GoFlex_rsa.key b/brute/badkeys/keys/Seagate_GoFlex_rsa.key new file mode 100644 index 0000000..3c49b02 --- /dev/null +++ b/brute/badkeys/keys/Seagate_GoFlex_rsa.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAr9h6zIROyA+A+JgIA+SAWReL9l9HaFSBhJNSW8or4EMzIZz3 +GrbJVGD5/mJfMRipUfH3Gt5MTPN682n4e/VkqPdsQWwb0RC5bFiMU4KwEZyuYrxB +WoQv0UMS7V/EWh4rypLtag99x3Oid5u0uVS/5y06YLV8XpHO9slKRg5qpEgbXAqP +o0C/vIImgHUxOOTNg6yOIuRHPqLhUeN03BjLwcgliuMWvUDkNxwf7BhqudQnhmHA +6gJAbH9PJn6ECxzTXpHRVtLadYkCwOzq3V4HLAO8FmpzNfvXFBzX3nHeNtCvgqyG +gnr9YDosBwYjzubmRKAbePweCDzrii+aRsKlXwIBIwKCAQEAm7+9OMz1UhxcSeW9 +9NGzghwrgnGlpYytOujii9BSv1F2bjqRuJNEmzFSc5j1Oh0oQUQGwAa4m+2YyPd1 +r57Vf7aTFV/A3b5bFtIV45ECWL35iqa9iq+YER4mtP0M/1yNL8P22kg8NFB59Oj/ +J864mY51iNtCRSIPFRivf+DiHHjoVz0Zzdd+bSZsqQs+rWdDidraRvEcsVit1jIK +tRc4K1GM/m8+QzolV7JF2nu6JNC3eeF1HvZt8WCpiBl0nx6oe8kHt0zwe5D62Kbm +Jb2I39stqP602V217r3p8jTZf//yYC9PLUP1ktVHFxY/PXDzrCS8fxuRzz+KQnik +ZVZ4SwKBgQDlu/CpaTxVUxJ9h79DfuxdkArehEOpPIGN4EufwbXyVAksB8UM97om +nWOotRegW6jY7BtPU5AYUitLHK140zA/TXubCvtd69dR7zyQA4xR7fczFBj8Y2SQ +Hh+r+wzepheiZAMM2i03NvhMdRoNTebSnX+5ZtDEDQMkTgCJOBuafwKBgQDD80iH +B0rsMERHEJB1ac4r9ORmmNq3a3LhwdLyTe3Lj2soxvMFiiQPT3uTs/D4NJp/c8PE +EGQwGlI8X6PJnQwHZEwES1dUQD+NWPPrpvN1i6F2fVLss+Mm+PU0RYxtGPstBKWc +Ip3y9xdE+/xCt4CM7QdsOFaMETxcrxI2arDFIQKBgQDYm0H+0O++KcD6A6W0qt7b +35U4M5BAeuCNFU6d8Sfkex6XOoaXMrbT83s4qr6BQHqgpBm/0nHrC6UpkBFGCPL5 +21dJClq3o9mlBiMosuNjMNMhh/pNDUGPMlhgUxrDTCTpkX84AOjNqNt7Sda8FkAI +aJz/Q67ku8/DJPkw68JBNQKBgQC4wM9avburffc7sIg0MI8wwlOxMQi7kTHNitzV +1HJ+GYJLBLk/vMLp3ToASpK+IvgRxOvHfStDTARkzzQHPE0rinOPBTUU7B6p14bl +gCdgMzHWHmQhAWEkvtiQXtTb5FqJiAncWyc1ieK9Yp1jedD55sx7+pq+k+h0pREr +/jGj6wKBgCWGAnfrVS2EkcZt46cqSkKMO+NlqiIjmnZroJwFLx1oY2dae4UJjsYV +95bGhcCOSPc3Jy+job6LD7iYoFUFLnRs0y6EilRQoG6OYFHg18YrP8ix9RdY6Roz +MzHb/5e3XVMcPBJbu0HHcOZar7RmHtfMl/XRHTk+dQXj3hqfNzQW +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Telefonica-de-Espana_rsa.key b/brute/badkeys/keys/Telefonica-de-Espana_rsa.key new file mode 100644 index 0000000..b4e8d77 --- /dev/null +++ b/brute/badkeys/keys/Telefonica-de-Espana_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwCKj10BLi11/oSbukFArKJZTXvBvw+AUGfie6fdE7psCNwCLM5b +YnJgjQZMP/VOhJkxkA539e2mM4fW9U4ECAUwgvlF9AZGhcmn0kF0jIjMUDgCV8kF +IS85OuBU/ayyswdYp6bxp3zn0tGAh0Ty8ikf7CgWU5c+PCbpygbBxMDfZM9PAgMB +AAECgYIFSRq/JMfPLHpahmxezzcSOQZziZpJxsdvuE/a+xmtbVkXLCZjDgpW7IpE +9luhNyWw4lHq+ZKrOGQCKzFirfuksgvsGeHTTGSey6t2KI0Xn3amADLGRvbF+lGK +73UpytVNVrFWjSE5rRFYjj4GP6tq3tPQhvnhjBCobQ6XUPWpoFThAkIArMhjsFGL +EOQmvEPP9Q06C1/GB8L+tYMHMlSgRQBNaTGevoa0urYpZdxWUAseWEn2f3PzjL1C +fZXJ/H9+asa36u0CQgDNS2hoRJ/MghqzSbiO0Duf/gc7EM39galIs6rPMpojqht5 +sskAk1JGnHN1D2KYdeIh8SRnBROmaR+uD4fFpwMPqwJBM0fqZntdNe8xG/FYeFer +oZKUWNtj84VnDmYVh0U2tID4p32diEjmcof5yhnysKuLEHrejyfg4xsg7uL8Jz7B +eWUCQVsbB+6d5bzAUFEYeksGpi9OcK8JuiCylgmpkjf5YOZK636KlSEoP+8OJz7f +4QV/6ybc+Sau3hWPuXtpcPuKLpWZAkIApF/sZxeLHZeijSCLHRVkbtq4K5T+BqsI +6d5XuMiu6Y9Br4zuCchnmJayb59YPIkiKAXzAAeVSLXFgnfduhp4EA0= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key b/brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key new file mode 100644 index 0000000..81b74ad --- /dev/null +++ b/brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwC0frfsMNpuhz4RDjRpzZthkGs1V8X/xjVhvmN4sySasHL9n//s +xqTyMdrAWQTVMCauJDJTweWjYRUoHifxifC0P55B3wvqCvr1BPpekNfvjRVZn5/N +m/xVU4i4SFZkHwmufyyzJgSuO9YgMsOYCCe/wve8ulttUrHxHlSfeexnG/A1AgMB +AAECgYFSqrcSNk3U0Zxbs2XOD51a0gpIJCtLOjy4x7pHTcU2FBqWjvFoQJLLWooW +Dq9n9OXCXFGwit3iNlS6SxWASu0zoCitDgfzd6gW3mEvjxbGr8YVq9p9Qet2zA87 +K50HtVuj6kAzCbWXY5zw3REalN1TOjh6Mi2qpJxcLHAn4qHFneECQgC6PzhfR6j/ +c6nXu9LmnFCjj9Pj7jMBjEJMtz4e3LspdOv9MInbi+vvafqH9vsgMqV7Oh8U4pOT +CzKV3KiGodsw4QJCAPgYBKcpwUUR8vJ7k1AdsA3aSoEd8IwKpAO4fKMq/GkECDvQ +lLfcxnLRkBjENPHr5BK3MH8QUnA40FkPnxkCY+XVAkIArPSgqPqnGfKTOuAVTkrD +J+Ec6IH/o+RYfV19trNMq4cEz68Plm4tv7svCKx3MMNXoUOsMXznhpnTdA/iAIS2 +RIECQgDcp/gTeYbFWOZ9Fr36Jr18RKe5WRimZZxlFsP8F/JxsL1l/ekX8suqOYtx +C6mPdd/faYE/shOwbkeYvtUhWEfjlQJBYMtzWoXbgPtf9QRbzd5LZ6ZUbvHtZk5q +LZ14Sw7Vj/mNXLJ7+SISx5QddvbL4PDRvca0pr7PwpVc6R+TxJZAVl4= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Tplink_w8950n_rsa.key b/brute/badkeys/keys/Tplink_w8950n_rsa.key new file mode 100644 index 0000000..af03e53 --- /dev/null +++ b/brute/badkeys/keys/Tplink_w8950n_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZgIBAAKBgwCPP9CoRpVtXwWvZXhqhWNqREIdEcCrtqI+XdVAuMH43MLizZUm +IrQHRSTCyEjwTnFJ4FudjL//nkrdPuZVg5dDP4njXb8nxAk6JC3ULDCjLWsCG2vp +zaPi3sxGr2H6AFTFNB3H/dyVlLEX6t5tFSboa45SnQF0vz2OE8dugN5qw1+NAgMB +AAECgYIJfk3ufQEFpsy6Z0Pf8VeZkQz9nrHRX3Dop7DMkYH/Y2gB/mRiSt359l3d +j8bvsA8neXgw3IVT5DJrb7H1nFsvsGHN/yhHFD4tM33qOY2NKRdZyMRb5jqEokNP +/Dr4z1s/rsg48waftgriLII2EbukakJBnfEN+Xy6gpXGJkdbYt3pAkIAk72QKxxF +MAAE5sTx5OPeZ7OKbGUV6x7NRM0x9vdvkKv4qdGMDa7exxNgHae3z14mnZ/oUPXT +mRaEAFOoqhV3UckCQgD4N8DNGgI+p6QrmC5ybh9QO80Yh45z05kOChJ8JSFgeC+g +PnUEJ/bgnlQArJy8g4p/GDjIfXlTvv7hqL6QcyrhpQJCAISWR87YpSLpsXxk3O1c +rpkEYMLciSojz3XibOfFsaL1IslMXFNfT2D7e3PBs8zvItqsynH127r0oOwmKCTT +9Ow5AkIA9By4072BeacbUPhwrX9Z8mltsnMWiRkPzHvhZBCMO7jXdewW3wiCxrNP +f/5Cgy+G1Km4poyn9tG/D6UKDTwGbzUCQWhNqaQetH8xFuOJePeZpGbGZ+ToD5Ip +bauSp6NW+/7H1UCYrmzbkUKey+0mBHjvMyGabdSd96zNRvESbi4PPW86 +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Tplink_w8950nd_rsa.key b/brute/badkeys/keys/Tplink_w8950nd_rsa.key new file mode 100644 index 0000000..f5dcfec --- /dev/null +++ b/brute/badkeys/keys/Tplink_w8950nd_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCAnQcDza1fw/2rHrXlxvU7TPhjBTQ2Dxdy3R9jVPfmAf5l1iGq +30oSSUeKj67+gExVY0S/3gvrYmR0luAtGC3VYhA686OH7BijWBFbo7fzXZt2L0pM +yVnE17+XQj8uEOvuLhmFunfha8jwTD5ei1UVcVt0cBBMAFqO7VUCk5vuA/IdAgMB +AAECgYIAkvLXkQDvlvvYWiw8qM0qZRcMtha8EQgtQpBjzJrIo4SjpreCwDlf6ITR +uP9yclck4GsLoi2ScN9657t7aAq4U5SYCt8IojzGloQPka+363Xsyfudg518L6ii +V7DUaL4yTXKeP77aGQyca+xY2NaJpZpmo6QOeRXwb9GZueIl8V8xAkIAp8ZjETOa +xBx+y7E9+1W0biU8y7U5ONxIvum7gMC6ZIbab27LrG8Xn1CsFoOiY341/U+N/CJJ +KhQMJ267WLThLaUCQgDEPsUEilf5OEMK87LxCSfIArJABFNNR9S9SQev5g3LMV/8 +oAXmRxeXDP9RoPvcmUv0d/3qDZWSC/B2LlWHNE35GQJBPjaUmdZW943Rftr0rvRy ++b0ZNDi5RVp6J0Eo5G+TLp/K9DNTl1eCnPMfPSIG33n3rz0G244jL1VLxHuNQTGh +pnkCQRpguYivs71684AAW7gMW/2FZ0I1kqDbm9vJejAJgychvt4tAD4ApkoWZCcr +tU7kiBIXItkX9FfKJQstlS90N1rZAkFR+gJ8Hmi3VgDjkYvVWzqrSHyetIlk+uUf +CXZ6XlnU7exvgiNCtUTQoG4yfi/XsCPSekxTohvzn6JQgMl4NsuKxQ== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tdmc500_rsa.key b/brute/badkeys/keys/Trendnet_tdmc500_rsa.key new file mode 100644 index 0000000..81791be --- /dev/null +++ b/brute/badkeys/keys/Trendnet_tdmc500_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZgIBAAKBgwCDK/l+dBrBXBwyndkS9Rqs4kekkuz+aRdBqMXOqs1gkroAUsst +T6sUrdnRZqUU6jnA3iMTBateRmcfeX2WIMZ24wlbwQK9P20RUomOfMcVEETh/E2A +N31vhXHMPiX0WdoawRKR7vAjTuSzPorYj6wvrVROQAoXad2sTKiVZ63JU7GtAgMB +AAECgYIS9gzbMCpb8BYtZO7nlCG2gwErM4fpoMRZW+24Ga2Ve7BCcrVnB+CpnRXi +9K0BUHhnvlSgn82tU4z9mkDCrlsx/RsdM64Jc8lyb/JfU/2nrnInGNUBtFtXkA27 +d1xu4VosMysowx8I11wegXi6d2GU7j5DzShev4k4Udgy1FjoekHrAkIA1QUzphsk +lUtkBG/vxprixABBV4sW/t/2UlAHKV4ZYCMDY9I4bdU54Wr3sxGlWJ75uu67ef11 +chj3+ARecl5z9mMCQgCdoy5Gnp+pklyWuxH7NAKtR2rV5OBePmYpe6v7/9lbL/rF +dp9QwoMvha73/WPmhSCFxFQJ9ofNmru6GdLv3dnsrwJCAIlcdVWjIw/yMWh7Fc6n +iZqB36Cn3Ag2OwwQ9s1CFHLdoQ35PNH2MQCejWM2+bwPp1FXKCUdv2H3n86aYpy1 +M2mpAkF7yZClVBr9Bjo/A1fzc7xGZja3EKxAVa/UE5HSqe79dIfwWF5zBMwPnWLw +ysKQWypW4P09daLfCgsw8OZ8BjMDcQJCAIsaP0rmzVvuL+EzHLozTgFTJbQ9HJHQ +X7d0ur5i71u5KHRdzi9IR6a0zxTAvMuP7b/bUc4CkvbiTcqTUG5H50zp +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew715apo_dsa.key b/brute/badkeys/keys/Trendnet_tew715apo_dsa.key new file mode 100644 index 0000000..f2ec640 --- /dev/null +++ b/brute/badkeys/keys/Trendnet_tew715apo_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDApyB0PjfCD6gSmDKWJaf92segd+0vqNSi9vHlFQUvRrVf/qFg +cG15t0cwpwZt90NFSqRZGwhsxUKKnF+X4SFyH644N5jEvuMjUqhZu7/t5Jn8o38H +HNqwScxVkQBaiW6Dx9rSrW1F4EyOh6XjrJf97bY9dCO+nF3Hv2Pul30GLwIVAPyL +nHSQF9TxP3YAfmvS8f1rc427AoGAGPxbXP7qPgyw9Z3bHmkLpUcXHcbQWDesX3Zx +xqZZw43OCuglLSn/Zq8AmcQrUdaiZl7QFZGrPxeL5rzOp5iABX54dlDpPjr9dLnN +clgUvbzkHY7G3Mf8KyGcx8AgU+4lzeCd621R2MhaM4wt07T+inASWaJQTPZEXxqM +o0ng99MCgYEAgRZVh7lLwDbGv8sJifQSKwxBpX8r7at6pcbH10BiT23E7CmkH8q0 +k4oTxilS5cJ1idlml/7mTGz7p6ialNF3QK6tIGQ4BNfh4EOluYHHuw41Twm5/58e +VIryd3mhv1Fdf4JkSQQUxrR2oe1HDdJrwmRSFnR6t1ldZvjmV4LtMPQCFFi2jrPV +Nici5GGOwFmbZ+cr6Qrf +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew715apo_rsa.key b/brute/badkeys/keys/Trendnet_tew715apo_rsa.key new file mode 100644 index 0000000..65239bb --- /dev/null +++ b/brute/badkeys/keys/Trendnet_tew715apo_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCaX+klFFjH2SREDCqGtja+6IwCYlFK+jF9XxWPEfW5w/fS4D/x +Qp9uZ/4GayFSDCx2hS7PwJJIJKpm3blG4Nq1hnu8TR7LyMZAFqVF9ezSRNLjEDSi +Ckucq25xlmX2xgVxw3rvxDkQRIhl38uu4QLy0Vl/f3tEi0+dfEBJEKSLlrR/AgMB +AAECgYIKFh4rmAq89AkUkR7uMlWdX6BWP4pENdip8l58PJYrjwxQMOq8nrfABPdp +//HrZHQ7QjRuyoxEPnELy4zhfquLlKVTEqzqt2bEFZNMJcKM52bqnj0v0vBB4V8s +/COueWJKbQKH0lMMu5KfZo9w/cOkAjUkHmV3ODhqYFV+Ai20J+zxAkIAoY3kF7B5 +rMlqvoRqqU8W3sjQ/mX8rc/0ziqu2bo2nPAbZklksbMNwIBHoxXpuNgOrnYIOeHu +YpILe7O6JWU6KWsCQgD0n4YVfdDdOxrmS1hnlTzKfakLEOF9d3tgKnzZ66q+fSea +4KwfCR5tUu4y6Enf8I+plSbPN/H2VeNS+JnU+zgCPQJBU5VoxETevuG6o3U5Bf3Q +VFVLo8M6Vub3vk7hBe7M4KdtVZ91RGbiH41/AsaMlMDb37Fbki7tOfxbipWzIjPc +Qp8CQQ/izDJZGVdEn1qVSghwCKKdxnyRfBNJzxlPqQv94fi85/WG4aaiUPeIiy+D +JkkEtk+s//g1CeGVck3RFyxdpCWBAkExKQanlhQXWoZhpntiXa7wpOEFxrJvNDgH +Nbi6Dl8mpXPpR6vL9YfqTQHXD+clNjijn0nS8lnrR8H1U58fivfmFw== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew816drm_dsa.key b/brute/badkeys/keys/Trendnet_tew816drm_dsa.key new file mode 100644 index 0000000..65a8638 --- /dev/null +++ b/brute/badkeys/keys/Trendnet_tew816drm_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvAIBAAKBgQDgiv9aWKPNJYUJSsxrqSVi9eyxhilR4FYDFryUF5xLPXxZHISo +PkJXD9S5kp7PYnd173aTDUECIFaOdVsnTpFKmgd9oQti4KEpYGvXyyE5W+Npe3J6 +k3Ei7qlkQbpoKx2Wl0VlXXmsm770Wl7V+p/kC+b9F0a7p2p15ddhGYyK4QIVALph +fzcSINDga1pc4X4ycjdhsKDbAoGBANZ71maYZt5WJBO0JSuAiFEhbGfbW1aqrpVy +mKhznl1vRh7FFok3mnHmzc5Q/rZt48UfRct9y1DfzK48Mp3hUM9hidOOdxKhLQyF +9f4+nz2yjrsQoQZkid7BpEk5kybciMjHBUiKDHc5X9K7nwYl9cu2i7WOjIERqsak +XJ9pKqhiAoGAOjZFfMY+Gd0mUrsdEIkTw3lL6xAf9V7oJ5MJ1eFnS+ekNOGTAFdc +tfEUYNyKh7WmWwWzfLMg69qNFwtKF0XNVKhgbU6b8RaEC3djkLa0IxmKWDW473xc +bP1QprJvRP/kbNl/639xHnpnI3bw3pa7sIJeFCYR3WmEyBmvhawlJy8CFQCCy/J7 +FMEe5+zliGLk7Xd604Mzgw== +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew816drm_rsa.key b/brute/badkeys/keys/Trendnet_tew816drm_rsa.key new file mode 100644 index 0000000..8438179 --- /dev/null +++ b/brute/badkeys/keys/Trendnet_tew816drm_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwCAmPVBs6DX2/2G6NcLwFI6jP055kbQzxGNNaYngPhR3TT9MMiG +nR2waCQYrZq0n7D+RKu9tEiYU05tPiaMqm5z4qHq2OePKIL4jFhcTJk8p0yz1IpP +p9FJjvZ6Daw4Mvr+r+RNNnSTn7Iq7bIxWyNgXnQc7Lx7IPmm8JDqskFEtOC7AgMB +AAECgYIDbM9/b/rVNPlEKhhsKjOmdpHaBG2XayRzB95EiBVVDNi386AroaykQanQ +dHM941Dx/L88Prx+Ph3FIBYjwfhOIs1lhSAjI0RcP8TKooBxsF67u1+2Q6+J1x4q +V+M006dJg7xghtkJFrAhl3L9kenfJAO5WqvtgpOhGIhYyTSX+9kFAkIAgSPoH9x2 +by8oToXuAql2X6lq5MlvWDJwMsnn2e4voeYLMwbh21IAbHUA9u4Xsc/PMcr0xbq5 +wFD7pElg1EddVa8CQgD+7I5raJJTN0uvuirN/E1j0pd8qI4BoS4pxWf1h7SSyI/h +2t3wdOvfFDeXQRPcLj0l0EK4d3ATnjze8Zu6Zlx0tQJBO4nZcC8Nb29XbvRyaknE +6I/MV5TDP+9pKRFLUn9s+IB981Wd9abVySscebwFspXzmapPtYXEM/ViPzkRam1I +i8MCQgCmeXjwL5QO//dPRLYbWn53h9khsTk7WzS4Vo/zSbHkgVFk3vc8xj4aqis7 +fWYozZFlFkHcETOBvD5oIUPfUGVkWQJBeJXfG/YOP7nNGpo1Xl1gSVLUWRBimAsX +7p/fmaIP+E0dLub9tsC43FKeWzuRKi7JHXY11v0gg70J3+/Gn449n1I= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tvip310pi_dsa.key b/brute/badkeys/keys/Trendnet_tvip310pi_dsa.key new file mode 100644 index 0000000..06f7feb --- /dev/null +++ b/brute/badkeys/keys/Trendnet_tvip310pi_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQCrj31iasM08/pIpnkvnyNxO+jf7+hJ53k9LWLCzbl5iBds5omx +GGKbeTjnvcJv1TxMdHBk3lSnDtZ4gso6aS74eQjdi0SFBgIKC8C4ucvpiNSKoLAd +o9gX4ejBQZsKbdTxJGurJZUF6vrK2KY1884PYZyk/hdk3cDQXyqZaX47WwIVAKEF +hny2F1sdTZGT7n2QfjwDjlkzAoGAduDOHzWd64PI/h5SU5rKrJZpfMy34zWNNeQM +qVR3nzAXuHXCx+u9EZdK4Od08g3vexwRSRibnDWMKn47NpIPPTFO64ZFRn+hmfcg +0rCH6eoGLWiy4JiGMEOwE66FMotUci/SA4UTDCwMd/BSYnTqqB3UHgaRYeiKhYY+ +Y1du8fQCgYA1yGK6T8kyCmBVyKwpg6EANkjRWuCoWRIfXCUQn7c/rphQousclvk1 +R4T85MUbzarkdrY4F0/wdDrATLh2mwXZcopn6AFcZo/8zFBfTlo/4wwvfo2D5CKO +z9MunCHfwLdZLN9bl9EFm21JzmdMMlldsLiCrgEl2yWIZ2AIrf6G1gIUTqYFbk4X +vekeTqGySK/IlK5NGfw= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tvip310pi_rsa.key b/brute/badkeys/keys/Trendnet_tvip310pi_rsa.key new file mode 100644 index 0000000..97ee832 --- /dev/null +++ b/brute/badkeys/keys/Trendnet_tvip310pi_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwCmgzZKxvy0Dfv6ZuMhZ4W1mLd3j/ZsxZpsO4j7PMElcAO2pX/9 +/C5pHGPetNR+koojxatVEJ4cOqY2cCNwneL5Omg37xmFeC4MXmYWFg234R6AotIY +CSXjdtwcEkG9D2MdVn3KpQ4cKKojzZnOtj6Mh42F+b0dyDCGX08XRNnBfW5lAgMB +AAECgYIX+2fzZyt4R1dWXkEyS3WvjPpHpx1n1yIBmqWFAeATo0l1oeqISyzoCKW9 +qq+8NyDcPBkMHGOZTz0nNAl2q9bH7E0an10JF18hP0omS9Ew6cevP/kCqsqN8rAK +/OQM/znc6qLBagOCiAaAlLsZJaeSMUbAYN/qa6t6E+ole4rZasXVAkIA1YqXq5m9 +b+vFPSqafFA/juWRGBBTLUemyZYqnWy1v+6cup8BsuGbNGm/gP9ANAhp93kZ/vki +n5bWUeMghdMLb9MCQgDHntGe/5k26OgLqr6ZYsbyAHVyCEI14hrtkLN1gVL4Jn6J +sthalxOhDO6eFr4/BwkHkC3tFR4K72OOKExZp7j95wJBRWIIsdr8MFdc+OjU1TuF +yzpQEI+NVxMG4E0If6oIy9oN1p0/gg3Hzhnl/VXyWHW7aItSpQPx+gSaknTH5nOS +Lb0CQWXaPDyzT9q7hcKGMVAUHUxXPZWcbyQNJQ43+ckn3kytX30k2s2GLkbLUWkq +U5HXJh1MzJIeZ0DPGm3rU+Ge+X3bAkIAp45fnLT/uL2pJ1f5tkgOaNao4uufHneM +mABjOR6Zy5r+HLhq+OdHEvtvlGiAAAmK+RLca4Q+dgZhq6/VAEEgd6Y= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Westermo_MRD310_dsa.key b/brute/badkeys/keys/Westermo_MRD310_dsa.key new file mode 100644 index 0000000..8d01487 --- /dev/null +++ b/brute/badkeys/keys/Westermo_MRD310_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCUGYfI1EQ1XPt4raMyLXOaZEcLJlkJIzo3wU5c+QhfAKeWd1lH +G5nvIYDjUli4c1NxW/meIdnABqV8hevGRyUj/MQyBYRQfQo1EKuQ0D16ewh+dKum +vZZQrU/REaIJdi1DMz9JRrEFgUTX156G7Om+5kmU+dBeDrPDgw5AH4OZqwIVAIAK +qBPsreAuLtTGKg1Wp08IfwhJAoGBAIqu4hhEmcKtZoFSg177etshTMnp9bfzcRRj +/cBQ/Zf4fkljjiXaD2JfIlvObOSQm5/DGnUZJQCuZzn8w0SfQbRCtXQXcnSMSRW9 +ueRHKRYWmhQbLNGm0Sh7aziIalpdbbuLA0eYT3nSjhCJ53/dK2cj0OvGBXeKrirs +aDsFk6dGAoGAaIK507b5EkTGobbR8+YjeZkfXVYmfly4zU8IL0LRRbFA0Gs22Rzi +ZFQaQM2BhFvAmQihRDXrT3f5lgtKV0wLTti669gFodWCRLLiElTFNLQhw3ABmWBz +/jVKn8fb3yNGis7L4e4t2kW4hSe2/uuI1QXhe7wmCnP9wdI2YVQDLHACFGZdxF4h +Q9wFXHUBy78zdjhrUD9U +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Westermo_MRD310_rsa.key b/brute/badkeys/keys/Westermo_MRD310_rsa.key new file mode 100644 index 0000000..26c56b0 --- /dev/null +++ b/brute/badkeys/keys/Westermo_MRD310_rsa.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEoQIBAAKCAQEAxS2g8708Gq6FOYCtQk+eag5NmZHkANuBOFcAg6UdnFEJ2Gy4 +b+JnHnn1LlJ4o0cQ7puPQiYEvtTffH39W3UxW3K7006b6a2NnP9DGq6eVcJT+ji4 +5lBBGSRawfjFSYsmldlU+Y07m8M/jD5Z2QLCrGgOy73GlLwQDlHFPIKoozznJ0Z7 +mROExxWMSgsHHa0qg9zq40igmIwTQgNvKS4ZEEo9eiYrniLEhLdt2gnoWzOHQ2iJ +Dh1Od3iZUZI0l+kCybG2IkbNASZk/I9gT/+R2PmbXDzsAa1RXEZuGufAIrQUcgpJ +7wUzG56jvJPxPdbmlvtUr1Y3ckhEU5m1RCbKaQIBIwKCAQAWiN8xzHvlys1lqE5Q +uKRj5GCj1iivpA7EnDqSs8jeqi0Cypi8VGOOdFaI83uAX+Stjh8APtv4jVter1An +tZ89prZhSs/9cuuc6fkKXRlo4wJIe4LYfjNTVJyodDPN5ARo5aNPuF6VdWZZK7J/ +M4P2ckrVdMY84kr6Unzw+P1Ue8mW3XuMHJmUuSTompIO+J5uc2i7yLeqpYroJXMH +m0m1yKzjisObZmGa2tPLy+TOE7ELVXjeTC3TEyfyVS37D/StDgPn5yjHFVMfPYST +T1cGFdmAygnHhsY0ARCEfay1/lQ0jwjWpmVnfTVMR+0FrxlaWwMH0e2xCNwpN8u5 +wLRLAoGBAPZRspuYkiA1UpKfdN7Z76UM0PvoOMa4qzO/d0ZOlBMNFRfjUIM3vG1u +9DHLRnSHVAlYpGBUVo86OhV0GAVWA9y+yuls2gsW7TV61XKdC3PpAFtPbugcINmJ +g3knZi4jusJw4eWk0LS+wn1yWq+aaVQNlnvseig1qbswM0qYEHaNAoGBAMztgeb2 +hyS8bva3TapAvhswmhqP85LKuGATRHpYC0EMmTzP13jWpoEKyH7Xi2HIszDHmEcg +rC5GVuZVENztGYkgrCqIRh0cPpgwX6Wp/eR6uhDl5i5TsTGLuaRsNerGh/E1RNb0 +BIcTVJPuw+exsXSyyH4+by9vkQK6vjQSwCpNAoGBAIW3UlR3ZUSvO3QqrSiE786g +jrSionqBgYJ94anS4qtBnbyCtq2h6fJDi+CEStGLSuCAk79DufX7CZaeG6sgELmp +ZtZ862UbEw5nQKvu2lTdknq2F6KS7ULkQA6RuyBcifSGbABSKCeawVoK4MW6OS2g ++euAX5IrwoowG9gJaAXXAoGANLIaFtGtuP1BGt6tki3nvdlMMrdNQwDtlQxEzwCy +b9AKJZSH3T6CljX5Cqx0TFguE9uNjqIAY6uv8hXfw8fwn5qv7a4DZpI7+z+jkP/T +koX1VM1nEzoXnwasFFZXAdtHhyr0ccm4BXn/zkS2CGDkfRgHq28j56BYfQtyvO7g +9O8CgYA6qo95a5k2nr1S/Q6qJH3O1gXVuMYatY6aMZX+43S1C+cM38hkbth2VExL +7GK6YUtwevpcQGURLpMyHoq2ozEpTuDeLQfcEYLeE6B/WniZnNx2wSJ24UMSRuYR +7tF7BLQhYc3U0n8bMS7t2l+BmvBKvz234NZgaQas1M555sXtJg== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zhone_6512a1_rsa.key b/brute/badkeys/keys/Zhone_6512a1_rsa.key new file mode 100644 index 0000000..5789725 --- /dev/null +++ b/brute/badkeys/keys/Zhone_6512a1_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwCq6dky9ovEf5lAH+DPMUAwB+k511Xvh+zq7YqMCmSIVtx5T1Ie +u37ee7LXmbhfBWkvteYR60uAQ7/lYTSlW5uJudSfQgKceQce8s49ZPIS8O8/ByTs +njZu0U+2oivcewhkWA6Dg6lbryDcRijLRUDxE+HY+X16f4Ev301v9YJW8+2zAgMB +AAECgYIL0zbSM4QDahSkqXqjZd6cCXVrKWk/s7bXoOxsuKywpi8w5s+2t/WU18LN +mfbLVD4tLTeoK7sYj8IaJmSUavHS+ymeJRgku7ewTqzcqfNxZI18nHmWqAH8D1ba +W0dzqq4PCVXnPodYfph9nxih5nuQoeF36Lhz6DpevuKoWDy82X9ZAkIAtrk9in/Q +NjKq0Schdo1usDX/RPJwWnl/ch5QDhPmOD9dAqhYZRtYMmNZhyaHZ1IxGY9CgU2w +d1V5r8umfomazIUCQgDvdChcOzreSIg1qF52pY70w8cRikOnTD89RWUnTya3ZOgZ +PdotoQ5AfmfAlTC3U0NlNz4IA757CMqFdvqMgE6i1wJBaKjdqRDgj8qhsL77Gc6U +0fV7y2AaHphs+U6HiCi5uwoAGl+WkgMBl4r0Yscc039uhDdcXnfDVfbthlXdfakP +s4kCQgCiazK5JL/QaHhjlPnGFpQ599W+Uv+Cqdg3UivDlw+W084O2QJ1csn8+wCb +A1cQ8lxDek4MF6YLRDJChyp5RMqR7wJBYyH1YwQIbsSAp37LJyDJnXdESd7bDWab +oMw/8xrrIFAqeoXV4/pWLW8M54uBMrFFdilJTaGZfsazjBuC8Bc+2aE= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_fsg2200_rsa.key b/brute/badkeys/keys/Zyxel_fsg2200_rsa.key new file mode 100644 index 0000000..1b63143 --- /dev/null +++ b/brute/badkeys/keys/Zyxel_fsg2200_rsa.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEoQIBAAKCAQEA1dmgf8O3ujr7JDMwm4hgEHb6V4RrTE/ywzKyyZK0yOqsqxfU +3gMA39wVeyvh7AsPs9WGiWItdYN34BPA+jyY63jwZnpvp6udB1NxoNM3b1t8WT5w +LrwQLZGDgfCH0aPMLMTcQuUZcf0JQkSvuVRWrsZ6T61m8mBVzK0HewtVVOpzsJc8 +4rW9teWu2sq3FJ/oMC4mnO7GPNOmBr5sQtw7Hwg0XQ6RaOTJNW7ueFy0cMxJqoxb +5SLw/VP6F7a0YP8dYWDbCL/KDSZ47TLr529RI+i9v/GoaRtpToIBRFHX9em+R61M +n1Bs+VoNSA+GsQvZ09HvAYuSw0M3QZlPePkCTQIBIwKCAQB0Fw38Nwv3YdjKgjBU +bpqMmFwDn6f2OgdxR2hey/u9hrWBc1ZMootyNaVC3U7B92BLrm2bCWkiiTJyU93J +q938K7Wz+VKOOJcSmwMra1ibiW9jpY1bMuQ1/ok36PlF6zRS0URe1CPBiVzMM+pd +Swp8IporQOAdLPtgey/yVpwJhZhC68rYVOFUgXwdG390d0/mWkcoCiL8Vd8tbnXz +1MN7aXpYSrcw12g8367W5MFBnUuScuio0jCM1E4zVWeez54lDmcebRWtUp2EI1wK +GzJ4rlFSuKe6WbigRVphsscoe142EtC1ky6icehkc26no1lQPZ3sZ8qVuOh6gRcj +cROnAoGBAPU5U0u9Lh+ctxjK5I+/ml9TiEeh+q6KIL6WrAgm9nLsUuof8rJG8/tr +gtpjboS/DTpFOVN+t1AmO14Ivj5iI9xgcFB9v5Y79qG1H0ja71pIZSIXmR0lqtvB +O3W1Ihka5O1tIil4iZHF8mt2tbMFvLP+BG3u9Kxd91/ji00RDfnPAoGBAN8/W7Fh +ISdwCiqx2+u0X6TV/5HMJMnG7ExSRukk1xy7KOIhM+v93UOqoN97maNGWe/5O/2A +RsCrQK2waJwrJufKJtoPlZKfYcKICGVKuadbhd1uR8IUlWHfTYbARLvGV3QmF6Kv +4Cb6sL/U8PpbjggMGKGHht0ApmEDIDYZKDUjAoGAdxvYADdQ6sh2MJ0P7gybcCFC +MWvuyc4P54sDGecJ/U425r8PeynG9nYMW3IfvNHTOY9WW8E0d2MG5IfnYCEKGpU9 +3fPvV5l3yuLx8C/TV7zDaFSa06SU0SNXZQ7WDDGiZLFZvF8eP31nHkD5KxFqSMvW +RAZZlYy5+1/kkyWKcgUCgYATIrAWhKsSAkoDqNhWCCV0h18MfzZacCLh+Gx6Yj5S +63iIaT72+ICuB0+eIImDBge1e8NQPjHzQeD48d0Usz3ZWnhb1XM4dA+xlqGiSDvM +hDALql3sEGSTXEh5Q64euTqyLyaY6oDtZfHkjphP+TgPUX50PtELoPhRdUvnYT31 +7QKBgQDF2Z6zTTtRmr9+6fWVvrZtwoBAbHynGpCnIGQTNc5Fh8paNGFz1DZIXLmr +J/j5C8NiFuYGuogDjkbnTeqR/1shBVfXoZs+2YCJVKAsFQlL6BZNf7cwLdXo/jLc +1+QDAugakTZOaBEbW6o+yBWW22tJLdIkboOFcuFZX/zOqYt8Vw== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_p870h_rsa.key b/brute/badkeys/keys/Zyxel_p870h_rsa.key new file mode 100644 index 0000000..5097b57 --- /dev/null +++ b/brute/badkeys/keys/Zyxel_p870h_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCcmuRg2VZUuTGKYUktSrl8OUeqIBjlk7fsb+/I5tRDUzoE80+f +8X2kp2bRJVKv5dZJMQuDpyYLkIgWPwDQiBVzBkvtUXAy80k+v9I4s+fQbiPY6VxR +zP7yoXHXp6ZwPFuvVVzO9DNNdh3xPPnYsgsB2Z1/YhtaEfghmfU5QUsKABLdAgMB +AAECgYIJM52vJh4JON91EM3OrDkVWD1CfQPRe3o/WX1mmc+HjCyYzV4UmQkno1Fq +gQ2oebH7V3cke24EGHAylG2dEx3lhfV9/avDEzNt/IP+hzEFRO+Ffw6vu8zcDGvm +URiQ712PJ4/600ScRbrt5dnyKQHcQmZYyaAxEg8G5IpgYq0FMlBRAkIA8bGvB7i3 +paFbZkBMLr6mEGYoZBkw+E+jy2COgASNXkPEdL3RBC6268z6AQCH+cdTnRqko7cP +rFY1uekMu7S5D1ECQgCl3+FhslyUdh9dylrjkdv3snhcVo9Sav7ZsHRixCx47FBt +lYAYTEevBFDPdaBq/izPkVVxeWii66WQbhF25LQfzQJBTnIERDZQ7OJFPxfJYjag +wZvWqj5+5Wk72Wu6dJSFqb3HCrj9GSVsW3ZJAoBAofJvEgOuwjBNVvsYLwIUDuxm +UDECQUEIwRYL106B7ZRZRT9aLbM07wnNCk5XEuqIy2j53zHd/T6p0do09hoBiCl+ +xdGNLEaJhcWCw2q1F2nELea+jny1AkFU6WvwhFCyIG9udx9dbJ6TpKrddtd29HwR +M6gwtyOjIEorRO9ccDcspn0cL8eqWm9SL1jtmy8BJ8bZNC4bzoEDjg== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_pmg1006_dsa.key b/brute/badkeys/keys/Zyxel_pmg1006_dsa.key new file mode 100644 index 0000000..b271f6a --- /dev/null +++ b/brute/badkeys/keys/Zyxel_pmg1006_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQCL0ymD+2jstaAQeVxrdSBQOhYbmnCN8n2jzqY4BtELjfc7J4+Z +jYg0NOm7n736gY0XDvPPE6h4zJ+UyWf+JtiZqor7lPbrJY1fP3L2LH42wdQYsmHC +Qr2R8Oq6poiJSWfVhFIHjIGZL28HGI47aKM5TUqIO5YUYpZEXb81exK8/wIVAJ/Y +V/YFNWzlT8WZa407+9CJmS2hAoGAPSjppzgcA6hi7rTwX32dIG6+wFum9AsPOWp0 +Tyk2GG25iFVpSQb9N2JKg0xijmjyhFnielcYGVOhM24Cv7pzgxGTIX9S3LMTbfyh +yiXyBsOCE/z1rsNbXZNLXhxFra09UNx5BSg4qGmzzZPyI8DM7GxQ1Zd7wN85wjJL +Glrck8wCgYBMil3C6dCZZ+dtaD+4LIHXEXTWXljaPYDEljmxS9bTQqkyiHLhTNHE +WrT+TLHvnBW9gOK9WRvcb3BZy2d82IyVTFm1uHo4JLAhSK+cAWND7lIWqWHM+znw +2saRg/z3FR+aCEVyJZdIfPfmw+/i3s3LkY5SryWB8epg+dzzKPesiwIVAI7KvhXQ +jZ9IpFeRS4ewHiQ8Oje2 +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_pmg1006_rsa.key b/brute/badkeys/keys/Zyxel_pmg1006_rsa.key new file mode 100644 index 0000000..7dfbb20 --- /dev/null +++ b/brute/badkeys/keys/Zyxel_pmg1006_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZwIBAAKBgwC0GsFKI5kba1u8ofulkTnT9xFUXTGmyXab5C87Cz58l5AxAX6A +IyMh28pOgQJaUVko6VUD1U40nA1W3PLuXZ6ADwFwbqUBOUr1PBsLqp10XlZ+X7uI +9M1rnRTNczoDiVCOKwdG99NWYjFZ5FHgGmVYSoxZt/WiBWI8XehSnjZXKG4NAgMB +AAECgYIE/mrr8dcTILkZ9AnaD4/2wHaCttTOHERFfUsRTDYELAsA+NrM6xcpygIu +3A/jI4yhKvBtsJ1AWIVcKmMyrcuGdG1jr+BSBDWeimw0f956c+Uow6vOxg3UkdsO +uV+aXbHnord75ScGhiYdgD3eqvw0zbb9/hossTWyPA7IUe5jYOVXAkIA88W6M21W +If29FSe6VwxsOzh1NT2rp+nxtFhj9p4EpkPpP2sAMZGFPS/Ffq75xmYKlkwP0SHK +TTVGMiiBijkxAF8CQgC9I3iLs8rK2RuBK5CfHUA7QOpTm+lJk9cOAi7MJ++Nzzqy +Sf9wOayE02gBk8NOdsHhSp7YMnVl7GJB6OzunXf5EwJCALHTy8E4QV0KfKSyFnzp +0wpgZSAxnMchIfEtib6eB0ZCxCQ/KiT+wvOfpbKEcjEIvzBkzCUDQVCRTGPKqLTs +g5KBAkIArTPBXTWNHMtKe7RYYM1Zl6lvrJcXQFDJXEO1dTGRFuzRlJlc+Plnuc8a +7G7TKJRqIZTC97rldSvOIwtZhX3gcs8CQgC3qhrSiKTd8/bLIdLjGdtHETjuYae9 +k0U8kNw6R4OXVnDAliFoaZYtdKV74sC02xvGAz0O1DhsbywxXqjKjf/Pzw== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_sbg3300_rsa.key b/brute/badkeys/keys/Zyxel_sbg3300_rsa.key new file mode 100644 index 0000000..bc06f99 --- /dev/null +++ b/brute/badkeys/keys/Zyxel_sbg3300_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZAIBAAKBgwCAiY84QFxmlm5H/HjBPd0R2gzRG5ZoBtquNsKu8s7CO039Rvfl +Wtt832UZpQkmm3/TtSpf1fu+caxo25ZOw9VpcUfcRhMTAOIO43jHu0T9ON2XdhU5 +TGMF+vAMsEGf9TXXDbVfHuKK+ruJ9dFyQYXF4KQasNrVHXQRfY2IFqyn2fxDAgMB +AAECgYIlIRdI5XaLf7q9epWbJHaeeIEpSjHJPOmgV1snVCYbfywJFJ35F6S9Tn6S +yNR63YIwMm3aHyLIIWfvJjcl63XTLiRwRYI1QKqB0seFEENAXEEovmEx3Sc+EToI +2XsaOJIa9o3L+Yj0m7OfP+fSEtWMkin8bpi5dJF81jCsZADCYX+BAkIAi6tAdhiq +157raL8AQc8r8Ey2373lLQ1t/AwADygBfzMV+TuzQjKzDzmgMtu7htZshtD71Etk +MqokM62Gk6Z5eIMCQgDrmMh05kiTSAu70PcoVQQvJePXzm2sXCc0zYnAo0GkEQUn +HCrvKtIroa4xXVqNXY9ea02rMNkP1lJJApFTg2ihQQJBZ5HILUrBdT8dZgEs2aAc +/waEHYodZbkts9faO2L5KS/ivXytwwsBiOq8hro2ZxieRaK2+4gkdwV/7upgfyDV +tq0CQT7e4beYcIulMURoqlw/+8LdOKUxeEEDwo6rvvQCXRT8WcSrFqYGZlmwZrzT +rcOF0SmgNvTVL0ezqPfE0KVZrwVBAkEM7LqkF/5/I2MAoaqygL3VIGWExcLHVHtF +jTXkBshorUBUHUJc2hRzYMbP68T3wUA1NIaz1NaEfSBJMSyvrMi3WQ== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/advantech_eki_rsa.key b/brute/badkeys/keys/advantech_eki_rsa.key new file mode 100644 index 0000000..5f8d943 --- /dev/null +++ b/brute/badkeys/keys/advantech_eki_rsa.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEoAIBAAKCAQEAsZRTyCCGw6EZhjGwwNGaZEPEcNGWU614JHTxyrlF06qIwOtP +S6VE0orBspbocP9I+G/FEsQbVybr9nNcwYjAyg/fWYmvocBtl06CY9YRu7T/h0rd +n6JR0avzXVmP41xRnCDsvC3jPwJDviK+X3D0w3Hz8/MCci28d3I+LHh0fMvHUfG8 +7yGyHEZHoavezectg7+tTNtJ7nff7YA//lV6MEnvQs+zxihB/n1vVOotOgrz9O2D +2Rpg0ilY84wne9MAEpQYmMMjCJyiWWE33je+UeI7PIx111LBTcMAMFyP7oHJ0lmI +AGcPlzCJRNirqFoBICNDvCt6+/Y5EAHKEs8ANQIBIwKCAQBvnxAXZOb+n8bmouQh +bc7CrkD2dRypoDzjtzjl04r6E2vm+k8ZmxVRJAS5Ziu0vbjWrKfCpycg1qLyrunY +vFvsuYUTtaGY31rUIrhcAuaTPpH9RQBVtnySbBVQnrI3JBYKXdaiK3jlxujAqB/k +OF9WR6Cn99Wm2ut+R89PJySmNeW0woNa8Z1TjRVlTLAf38vwWO8i0RqG2+3kkOff +c+Qv7wuPlVKEBv1JenFp3VoXPpMKbr/YasbkLDZnajumgHgrKxaLrcSUBdEKrbDQ +olJcS4UpnfILHXSyuuQIEB7BAnRxo+og0Hp/oQt7tivXIoGAiLjq0VdkO0Jfpn/A +vciLAoGBAOOPyGQAzISo86jAc3tNu/QTt4eLTC6JZtFEa9XOMZK9WK48ZGMUIeta +JXrqP0oMGn7XXNhDhOXYzxv2mHgeKjGBGDOb5T3F6hA2euWYkgyJclqeKzbPCdhP +cfv9ZX42Hg55nbBa1026IsWs8Mg9NOspIXEzSRNw6D0AcQnPPNIVAoGBAMfFf4f0 +7BA/2uWRhDQHpEZxqTTy5Vnrm+WmLeIG1EI99PRzch7f+zgMiYXN4gJmDOhHJkvU +joRfzhT7VlB3reDYcYbwGua0TDnvAIpuF9Gp97tQiODfVDr5PvaDOoOz39OMq27O +cqHjdkiY6PrDbjCqhWaHrKJUUp4aTa8z2J2hAoGAboeqebc+xBeMSqaefbgK1aMt +QdXxzXXwHIBRolzWP/WReTNGs8fzN8yzHnHG+BSB5dZRqt73aFNdSBiwdNtzlGNG +RPPzAL83LHI7sVi793X1tvxeIe+IcGEaG08xS+5mXs1bOGajFx/lAO2ZhdSWC9Im +L6syH2K58b5iykAWOicCgYAzXqvD3+TuPE47CClPNSo9//hPcaiwuO2S0vXbCRIC +Z7QEWDNJxJEOaZ+0sUi+ycjOA6rDCsWPwN04mGam+jQGng6QaaCEd4FQuczwZXPW +0+8+y5DpXf+3ZKnKXEI/H5/0keLwm3yQB0pNLqJPHE+I22QhrdvvOkEhVziMI0ZU +awKBgA2PDK8Xkv1RG/Opbub2V5FCX+IpneBzQPiZ9h2PAfkZumyQjGIHsilYfH3Y +cEPtFs66HqUByFdhsmEBSl1jp88wycLx6aMz1M9o3NCOfYEGescdlhJEQ4/J0vy3 +Jg1l8RV3izpkl7/4XnDZ2RDeRYWWAp/OOKZiDXTYeLY8+2pr +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/array-networks-vapv-vxag.key b/brute/badkeys/keys/array-networks-vapv-vxag.key new file mode 100644 index 0000000..609a01e --- /dev/null +++ b/brute/badkeys/keys/array-networks-vapv-vxag.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQCUw7F/vKJT2Xsq+fIPVxNC/Dyk+dN9DWQT5RO56eIQasd+h6Fm +q1qtQrJ/DOe3VjfUrSm7NN5NoIGOrGCSuQFthFmq+9Lpt6WIykB4mau5iE5orbKM +xTfyu8LtntoikYKrlMB+UrmKDidvZ+7oWiC14imT+Px/3Q7naj0UmOrSTwIVAO25 +Yf3SYNtTYv8yzaV+X9yNr/AfAoGADAcEh2bdsrDhwhXtVi1L3cFQx1KpN0B07JLr +gJzJcDLUrwmlMUmrXR2obDGfVQh46EFMeo/k3IESw2zJUS58FJW+sKZ4noSwRZPq +mpBnERKpLOTcWMxUyV8ETsz+9oz71YEMjmR1qvNYAopXf5Yy+4Zq3bgqmMMQyM+K +O1PdlCkCgYBmhSl9CVPgVMv1xO8DAHVhM1huIIK8mNFrzMJz+JXzBx81ms1kWSeQ +OC/nraaXFTBlqiQsvB8tzr4xZdbaI/QzVLKNAF5C8BJ4ScNlTIx1aZJwyMil8Nzb ++0YAsw5Ja+bEZZvEVlAYnd10qRWrPeEY1txLMmX3wDa+JvJL7fmuBgIUZoXsJnzs ++sqSEhA35Le2kC4Y1/A= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/barracuda_load_balancer_vm.key b/brute/badkeys/keys/barracuda_load_balancer_vm.key new file mode 100644 index 0000000..b8f94e3 --- /dev/null +++ b/brute/badkeys/keys/barracuda_load_balancer_vm.key @@ -0,0 +1,12 @@ +----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDKuRHCBXXwoyWpMkJz/wQafaHOpqWmVYLn9GZ6eQuNLcIhtZQE +kCWZTNajgf4ZAVmHgQh1JHDixJ1V0mcweti/lvyxiqHap7IkD0a+ewAOoz3OpjQZ +3ox2ovHEnQJfZ/9LNiEI3XK8TPAj6trhMn5tCdwFei6228a+TYBOccTPgwIVAKYW +T8ztHHaN7Gwn0I6keQfBSNw1AoGAHYNfKAcqf7Y4wyoVoZpr/h21SETpEaksQb7h +GRJnFpYN/JiyE9W8nX6UqLv1eKyOXLccAnyda0a+uqcOhsAq8+H15slZYa4+065L +ckPfs0V4cpxeMHTT1hK4TR2/LRpUjhYjgXFE5aLl91f5Gug5HemUK2S0BWh/oI38 +k2WfNh0CgYEArsJgp7RLPOsCeLqoia/eljseBFVDazO5Q0ysUotTw9wgXGGVWREw +m8wNggFNb9eCiBAAUfVZVfhVAtFT0pBf/eIVLPXyaMw3prBt7LqeBrbagODc3WAA +dMTPIdYYcOKgv+YvTXa51zG64v6pQOfS8WXgKCzDl44puXfYeDk5lVQCFAPfgalL ++FT93tofXMuNVfeQMLJl +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/ceragon-fibeair-cve-2015-0936.key b/brute/badkeys/keys/ceragon-fibeair-cve-2015-0936.key new file mode 100644 index 0000000..2f14cf0 --- /dev/null +++ b/brute/badkeys/keys/ceragon-fibeair-cve-2015-0936.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDBEh0OUdoiplc0P+XW8VPu57etz8O9eHbLHkQW27EZBEdXEYxr +MOFXi+PkA0ZcNDBRgjSJmHpo5WsPLwj/L3/L5gMYK+yeqsNu48ONbbqzZsFdaBQ+ +IL3dPdMDovYo7GFVyXuaWMQ4hgAJEc+kk1hUaGKcLENQf0vEyt01eA/k6QIBIwKB +gQCwhZbohVm5R6AvxWRsv2KuiraQSO16B70ResHpA2AW31crCLrlqQiKjoc23mw3 +CyTcztDy1I0stH8j0zts+DpSbYZnWKSb5hxhl/w96yNYPUJaTatgcPB46xOBDsgv +4Lf4GGt3gsQFvuTUArIf6MCJiUn4AQA9Q96QyCH/g4mdiwJBAPHdYgTDiQcpUAbY +SanIpq7XFeKXBPgRbAN57fTwzWVDyFHwvVUrpqc+SSwfzhsaNpE3IpLD9RqOyEr6 +B8YrC2UCQQDMWrUeNQsf6xQer2AKw2Q06bTAicetJWz5O8CF2mcpVFYc1VJMkiuV +93gCvQORq4dpApJYZxhigY4k/f46BlU1AkAbpEW3Zs3U7sdRPUo/SiGtlOyO7LAc +WcMzmOf+vG8+xesCDOJwIj7uisaIsy1/cLXHdAPzhBwDCQDyoDtnGty7AkEAnaUP +YHIP5Ww0F6vcYBMSybuaEN9Q5KfXuPOUhIPpLoLjWBJGzVrRKou0WeJElPIJX6Ll +7GzJqxN8SGwqhIiK3wJAOQ2Hm068EicG5WQoS+8+KIE/SVHWmFDvet+f1vgDchvT +uPa5zx2eZ2rxP1pXHAdBSgh799hCF60eZZtlWnNqLg== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/exagrid-cve-2016-1561.key b/brute/badkeys/keys/exagrid-cve-2016-1561.key new file mode 100644 index 0000000..867ea40 --- /dev/null +++ b/brute/badkeys/keys/exagrid-cve-2016-1561.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWAIBAAKBgGdlD7qeGU9f8mdfmLmFemWMnz1tKeeuxKznWFI+6gkaagqjAF10 +hIruzXQAik7TEBYZyvw9SvYU6MQFsMeqVHGhcXQ5yaz3G/eqX0RhRDn5T4zoHKZa +E1MU86zqAUdSXwHDe3pz5JEoGl9EUHTLMGP13T3eBJ19MAWjP7Iuji9HAgElAoGA +GSZrnBieX2pdjsQ55/AJA/HF3oJWTRysYWi0nmJUmm41eDV8oRxXl2qFAIqCgeBQ +BWA4SzGA77/ll3cBfKzkG1Q3OiVG/YJPOYLp7127zh337hhHZyzTiSjMPFVcanrg +AciYw3X0z2GP9ymWGOnIbOsucdhnbHPuSORASPOUOn0CQQC07Acq53rf3iQIkJ9Y +iYZd6xnZeZugaX51gQzKgN1QJ1y2sfTfLV6AwsPnieo7+vw2yk+Hl1i5uG9+XkTs +Ry45AkEAkk0MPL5YxqLKwH6wh2FHytr1jmENOkQu97k2TsuX0CzzDQApIY/eFkCj +QAgkI282MRsaTosxkYeG7ErsA5BJfwJAMOXYbHXp26PSYy4BjYzz4ggwf/dafmGz +ebQs+HXa8xGOreroPFFzfL8Eg8Ro0fDOi1lF7Ut/w330nrGxw1GCHQJAYtodBnLG +XLMvDHFG2AN1spPyBkGTUOH2OK2TZawoTmOPd3ymK28LriuskwxrceNb96qHZYCk +86DC8q8p2OTzYwJANXzRM0SGTqSDMnnid7PGlivaQqfpPOx8MiFR/cGr2dT1HD7y +x6f/85mMeTqamSxjTJqALHeKPYWyzeSnUrp+Eg== +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/f5-bigip-cve-2012-1493.key b/brute/badkeys/keys/f5-bigip-cve-2012-1493.key new file mode 100644 index 0000000..1c41560 --- /dev/null +++ b/brute/badkeys/keys/f5-bigip-cve-2012-1493.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWgIBAAKBgQC8iELmyRPPHIeJ//uLLfKHG4rr84HXeGM+quySiCRgWtxbw4rh +UlP7n4XHvB3ixAKdWfys2pqHD/Hqx9w4wMj9e+fjIpTi3xOdh/YylRWvid3Pf0vk +OzWftKLWbay5Q3FZsq/nwjz40yGW3YhOtpK5NTQ0bKZY5zz4s2L4wdd0uQIBIwKB +gBWL6mOEsc6G6uszMrDSDRbBUbSQ26OYuuKXMPrNuwOynNdJjDcCGDoDmkK2adDF +8auVQXLXJ5poOOeh0AZ8br2vnk3hZd9mnF+uyDB3PO/tqpXOrpzSyuITy5LJZBBv +7r7kqhyBs0vuSdL/D+i1DHYf0nv2Ps4aspoBVumuQid7AkEA+tD3RDashPmoQJvM +2oWS7PO6ljUVXszuhHdUOaFtx60ZOg0OVwnh+NBbbszGpsOwwEE+OqrKMTZjYg3s +37+x/wJBAMBtwmoi05hBsA4Cvac66T1Vdhie8qf5dwL2PdHfu6hbOifSX/xSPnVL +RTbwU9+h/t6BOYdWA0xr0cWcjy1U6UcCQQDBfKF9w8bqPO+CTE2SoY6ZiNHEVNX4 +rLf/ycShfIfjLcMA5YAXQiNZisow5xznC/1hHGM0kmF2a8kCf8VcJio5AkBi9p5/ +uiOtY5xe+hhkofRLbce05AfEGeVvPM9V/gi8+7eCMa209xjOm70yMnRHIBys8gBU +Ot0f/O+KM0JR0+WvAkAskPvTXevY5wkp5mYXMBlUqEd7R3vGBV/qp4BldW5l0N4G +LesWvIh6+moTbFuPRoQnGO2P6D7Q5sPPqgqyefZS +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/kali-rpi2.key b/brute/badkeys/keys/kali-rpi2.key new file mode 100644 index 0000000..8e52235 --- /dev/null +++ b/brute/badkeys/keys/kali-rpi2.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyYuV4+7aWjBXb0Ofl3+GfqNWFQgVi6x6wQQp4/Oe5qPowDDM +dSzO6A7wpnx07fGdMw4WcOFT3by8fF8VYDj8O+ndi7KSB90BWOXuPxXpmnUFQQ8v +EVOJ+bCac6+KawsWe4mj5AO71PMRo9fzuTJWAdQ+LcQJbk0Mf57mYekJoXVjktaO +lFEtUkY5tT+j4KPgzrC7PDjAA9ZCHDllXXIIdL7EO239jOAp3kAxeUe2QNhGh0WI +ZoYUgSgo7jogShRwjJpqRtTarhJL0m3wFy7OAYPgvhSQZX/ICJfZ6YJtMxe/H8wU +gxkQs7gzzn8IW3ySp3d8SFmG3pqmgPiYUbLG7QIDAQABAoIBAC/bj2oia4MC+6AW +BK1qwLsNegFgfA1AlZ2DqZbRYKgPv9LzQ3mHfFIqSgaegv2FY/idncKMHugGSxOV +WHQS1AI+FDd53ac0WX+Mibg9Mc1VgDvkqR6KIbdCskpKIqosZdhL5LjeEhoM5eFs +BBmz1Cx3A5TGeh7Q+OjNCJOzTHAkDW/w+s9TRWzRQ3+YLba/V2LpuT/osSwnrX2P +k1DbrOewe7kbBQQqXTUu2w2lpS11m5cd4Xo6/ubBUKAVw1DprDd9Sen+8dCajEDW +zua0CPqEuLqLlbO+jMQ4phr0FaIz3D99WjX+/vTLPYlhsM4EGAEpwIZWyd2oVzMY +1M8JjcECgYEA+l5bwHIXPcbdOQUyPjexXDIIljy7y7hHsOoVEJhiDZa4ta82uK/F +QLz1ce10T5cizeMgXA1C0BlPbV0xpYcVSa8JKHdy9JD5funIYdT8M3x6biqss3Xq +aBf6UpgyEZjsM7yBUFWvQFAEgSF72yNjS4JjS+ByAkN7Qf/K4FP9NBUCgYEAzhQZ +Pg6EeexbaNZJx/waU1sAcnwuu0waK6Thwjken+hMeVEuDggd0PScolJ37HOj4pHD +L3CEB6zV8kHeJXBuw+GW5JTWiOndVbhD6SvPUIzGA3t7Vg3hf5xM8xLl8ouzvuvF +QqiB+vdjxt3ZhXapN8reiIMoS8d4JPuRinVtxXkCgYEA3OUvkoWW19yDBnH9OEOu +6icCyHrhPgZiykZdO30W1eJrKXFjmGMMZ+fPrirQ+f/gp0KDJHRWxH/wQg99ZAvg +zlfufpOVCw518nGVaCugMFTdOCHSqauZmym8o+7ADiKcE7F3bkeLDfULZFsEif2Y +9+Acd6+ZJ0Iel8Z1WqL/vVkCgYAHudcsbWzoCUVwC8CeX8Q45cuBf0hdO0Ar7LWO +C4grZJEhZzq7yfAcOl948nCOi9NUFjTkHWrFwuQOfguvCaUNcPKwRSg539KteQgK +wNq34V83GDUKh+CxYRG9dzLpwfUOLlap5hlhaE70ULLr/wPfFJr9MTWcibDmagwN +zdM8uQKBgDWuyzKkffPTScXGmTPek+tH2mdDt+9JhV+vF95pAp3qsOx8GJP9cvfj +F0oXhe9UX+230mZzm8Rhf/dwaJDJGKGFmJyt8sIDl/MWbrz8SuGjXF+vY7HwQHwP +s3dx7FQuW+X4X7jJ/CdkW9u483Eng5aH5W9X+Q1WlCQRWNty9gIy +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/loadbalancer.org-enterprise-va.key b/brute/badkeys/keys/loadbalancer.org-enterprise-va.key new file mode 100644 index 0000000..521e645 --- /dev/null +++ b/brute/badkeys/keys/loadbalancer.org-enterprise-va.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQCsCgcOw+DgNR/7g+IbXYdOEwSB3W0o3l1Ep1ibHHvAtLb6AdNW +Gq47/UxY/rX3g2FVrVCtQwNSZMqkrqALQwDScxeCOiLMndCj61t3RxU3IOl5c/Hd +yhGh6JGPdzTpgf8VhJIZnvG+0NFNomYntqYFm0y11dBQPpYbJE7Tx1t/lQIVANHJ +rJSVVkpcTB4XdtR7TfO317xVAoGABDytZN2OhKwGyJfenZ1Ap2Y7lkO8V8tOtqX+ +t0LkViOi2ErHJt39aRJJ1lDRa/3q0NNqZH4tnj/bh5dUyNapflJiV94N3637LCzW +cFlwFtJvD22Nx2UrPn+YXrzN7mt9qZyg5m0NlqbyjcsnCh4vNYUiNeMTHHW5SaJY +TeYmPP8CgYAjEe5+0m/TlBtVkqQbUit+s/g+eB+PFQ+raaQdL1uztW3etntXAPH1 +MjxsAC/vthWYSTYXORkDFMhrO5ssE2rfg9io0NDyTIZt+VRQMGdi++dH8ptU+ldl +2ZejLFdTJFwFgcfXz+iQ1mx6h9TPX1crE1KoMAVOj3yKVfKpLB1EkAIUCsG3dIJH +SzmJVCWFyVuuANR2Bnc= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/monroe-dasdec-cve-2013-0137.key b/brute/badkeys/keys/monroe-dasdec-cve-2013-0137.key new file mode 100644 index 0000000..16bb3a3 --- /dev/null +++ b/brute/badkeys/keys/monroe-dasdec-cve-2013-0137.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBuwIBAAKBgQDdwCE68iTEMjimYwJMvpkP/KThyJbuKvKc5kdKqLSmi5tssnuW +tD2NqzmkEQM4uxD4XgV26k2/GvE6x4RlyOT+xlB2iYaOR4RJ8PuU8ALz+9i+y3D8 +MTMY/6y3Ef41frizLFXiVVo8CXFL/N8sz16FYytIayJvkSy3rkzPoE8pRwIVAPmA +F1excCJPPVq3MyDfEMUXXOWjAoGAJS8ukwjJTgTNCHD7Lz//WxIw49DPGGWs3are +GpjtiGjVD2Lff7CLCzkH8SI/JsgytUzqfDckSXqe1eWiAhuH90Pl5LZZi83Vp97I +721riAF3taKYxtk+vWIcXx2a/Fp+z+LaQoMqjOLh5lCq35wc0EPb5FFFrGaFFzNm +e71F1X0CgYAU6eNlphQWDwx0KOBiiYhF9BM6kDbQlyw8333rAG3G4CcjI2G8eYGt +pBNliaD185UjCEsjPiudhGil/j4Zt/+VY3aGOLoi8kqXBBc8ZAML9bbkXpyhQhMg +wiywx3ciFmvSn2UAin8yurStYPQxtXauZN5PYbdwCHPS7ApIStdpMAIVAJ+eePIA +Azb0ux287wRfcfdbjlDM +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/moovbox_host_dsa.key b/brute/badkeys/keys/moovbox_host_dsa.key new file mode 100644 index 0000000..12db48e --- /dev/null +++ b/brute/badkeys/keys/moovbox_host_dsa.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBvQIBAAKBgQDfabHlEkqMi0FfL32dNB0lxp7vQJcPRJkZbH8HlIFTxHfUGM1K +LuK+4JOMM31J3yCyIyu/uBqGvmdZyjX6GS7aGY3Az4zlpIUunIW6m9hR3goGsOjF +zayEEvFs3ee7au0iKapxe4/kXekSDH0Inv4uTDJv3mshrT4B+IjyBFpVXQIVAOAT +b+thZZx73tGruGOPxUuw+QW5AoGBAMy7XfPec50b9X34SyS+PJV5GCOfXW7NqHkB +Oz4H2hAYi+9j4iGgVuznSCQaksbROwfSeyFcwiyaVoZkvyRXc8RaX1HPf6+cqWGR +ozISsTOto56JFOX2AwmjO4V09j0bhR1cpS2M49ku1g2uR94sfXCGvVdHMTjtAkqt +D+GwB6lGAoGBAJtctAVpawe5um4PdWHm3322Ys/VgUEYUas63FIKkFNAovtQK77R +YjsUMhZeOQxZx9d1UKmZlsDy6OEpiLTDaeBe0UMKR2NcaOTLUNYVezK5GmmJ69S1 +pgHHvfzxEBp8V4Eycm7PrrUlMmpQJtdNFt3GTLLgnmsqPk09tcdKWW2yAhUAyz9v +ERREcclKkAe7kBePJPZykxA= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/moovbox_host_rsa.key b/brute/badkeys/keys/moovbox_host_rsa.key new file mode 100644 index 0000000..630aa68 --- /dev/null +++ b/brute/badkeys/keys/moovbox_host_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWgIBAAKBgQDbOPIy1WriG0RPazArtW8lIwPx73aKIV+xLlbk0+/2amCV9HxU +3wAoB9wCuJG4KI2T2dDWC2Os5Ses18udZobIQzLMbgR1bOpz+VULNWnyO6TRB7VM +ls7FMUe4bhgG03be34EAmCX3F23H4+OQF4u5iYWjw0eqHOcBx3HzEr2IwQIBIwKB +gBLKXecZm3J3XaBoRfUeLhkgQiquIBp346F5ACI+DUEBzcO13sVyMzahloPj7zur +tF0hTGoeO75cyOLt7OGbEt3OhSWWLBBWeWvFjaT2NjjycZtnUGlVuZdY4CCZ8tS0 +QkXz3nliRBFSEf9k6VyBAkzf7t10Q77oRkr6FDrptVtfAkEA7POsn6VO8mmy7gvd +aoffeV3aZunWO5k0OY0LDp/oGTtkUz9Z8xjVb5RRSI/SqelKikhx4lcWhyBX+bYW +rh6QcwJBAOzYaUuyZ974LQp/U+e+z7XENut2qXMVq0AuhltnF9iLn8Rzc9VIV5UH +fayxMOHc7iAFGj8viv/n6b16gmiwePsCQFgCvHXPr5veHeNjfiBBGH2JQn3/FQ7S +gRywuvbNrfq+SdXHEsgB6OBNCD+F4IhAtUlOG6vXNEDRf8MmYDILWjkCQQCiaIK3 +kEc5zr/MrxT2rrpQwQ+3Z0+fX1DbjZ31iIVhScyj92VfDQjbOFYtRk1nrXAV9N7M +PduoKf9dW1Ib5rlbAkAUhW9SGduCeAqPv5wGXJOAuclxEBH/NzO0pxC07cS29MV/ +FNLfzXZ8NRiNs+QsmqkOydytBLwSsmMNlCW5VtOP +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/quantum-dxi-v1000.key b/brute/badkeys/keys/quantum-dxi-v1000.key new file mode 100644 index 0000000..788af40 --- /dev/null +++ b/brute/badkeys/keys/quantum-dxi-v1000.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQCEgBNwgF+IbMU8NHUXNIMfJ0ONa91ZI/TphuixnilkZqcuwur2 +hMbrqY8Yne+n3eGkuepQlBBKEZSd8xPd6qCvWnCOhBqhkBS7g2dH6jMkUl/opX/t +Rw6P00crq2oIMafR4/SzKWVW6RQEzJtPnfV7O3i5miY7jLKMDZTn/DRXRwIVALB2 ++o4CRHpCG6IBqlD/2JW5HRQBAoGAaSzKOHYUnlpAoX7+ufViz37cUa1/x0fGDA/4 +6mt0eD7FTNoOnUNdfdZx7oLXVe7mjHjqjif0EVnmDPlGME9GYMdi6r4FUozQ33Y5 +PmUWPMd0phMRYutpihaExkjgl33AH7mp42qBfrHqZ2oi1HfkqCUoRmB6KkdkFosr +E0apJ5cCgYBLEgYmr9XCSqjENFDVQPFELYKT7Zs9J87PjPS1AP0qF1OoRGZ5mefK +6X/6VivPAUWmmmev/BuAs8M1HtfGeGGzMzDIiU/WZQ3bScLB1Ykrcjk7TOFD6xrn +k/inYAp5l29hjidoAONcXoHmUAMYOKqn63Q2AsDpExVcmfj99/BlpQIUYS6Hs70u +B3Upsx556K/iZPPnJZE= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/tandberg-vcs.key b/brute/badkeys/keys/tandberg-vcs.key new file mode 100644 index 0000000..2b5afdb --- /dev/null +++ b/brute/badkeys/keys/tandberg-vcs.key @@ -0,0 +1,12 @@ +-----BEGIN DSA PRIVATE KEY----- +MIIBugIBAAKBgQD3tE1z0KZm+i79LIswVNiLCv28LONYItvwtIwXoLZoIddGPpxK +ZVzoLfK4312n0tJk2hKbyIuu4LZvVQKnoZmtv+JStZojj+n+EEnENr0xhdZ+QckV +QvEBRae1kwQ5beUj8+tySNNOcRE5rTALJCnUmd2MoWxUZqywbpRpd2984wIVANpt +9wmUtFFaluRzG22EYgz7KqOdAoGAIggS8dDGzfZirDQ5ASxcnqhVy4wPUj5eb8wK +5VCn9CpF3EoJ7fhm6zuZVKyR3AjcjQv1ZL5VyVgnOlTrpU93m3p671vTs1AU7YWu +9phryjwJbjSmE11awa8fiOClowSSVL0c8d6XU6YTm5FNGPcZDxWYgyyTpXb64fF8 +sQvN9IMCgYAjxxpAG9jphUvvRVYA+2uUnsN9oy6VHO4Zo4lsAAvkdAS8X/VN/i7+ +C8xFq6VY0o7qK1gOhibjOIt3nFC38gQYgBRiLQ0Sexm1whS8STsvhqMgQ1196ecB +03mVpaMDcteFbBrfRAbwAuOmW2nH9XMBivFuMSNjfs4tyyMimq3ZyQIUH+dinHtN +tpESE60mdCP2xIuMEAw= +-----END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/vagrant-default.key b/brute/badkeys/keys/vagrant-default.key new file mode 100644 index 0000000..7d6a083 --- /dev/null +++ b/brute/badkeys/keys/vagrant-default.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI +w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP +kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2 +hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO +Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW +yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd +ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1 +Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf +TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK +iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A +sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf +4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP +cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk +EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN +CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX +3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG +YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj +3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+ +dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz +6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC +P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF +llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ +kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH ++vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ +NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/zyxel-q100_rsa.key b/brute/badkeys/keys/zyxel-q100_rsa.key new file mode 100644 index 0000000..ed6cec0 --- /dev/null +++ b/brute/badkeys/keys/zyxel-q100_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZQIBAAKBgwC/2TX7O2hT4oCtqtdnNZXeeqXCBSFHkYAWkwB9zA0uDeVxZVdA +U25BefZUXExSqVeOw0DrCibrMD8uuPhxC7no2GYR6mKd8p9F0x9xi2UZVYFW5HNI +iT01Zj1JDORRwm8FsC3Jd9zpzzHnufBRoOpaaqFdRxS1DpD9Mzd8yCGsnyg1AgMB +AAECgYIHXVUKj7PbXIH1QlSogp19/+LmRgCKcpYIx891N/5D7iA7sFjSgFSZynVf +tN29L2dlp8f3/djGnubq0HD0R5X1NgbH14lk7oPsFcJiSf+kauKDjJT27CBJsmrm +3rygp6yuGoySUchL3aTpSOoUxeOYuhqH3c8cim5XlFtJIBlIu3IhAkIA05/xHzs3 +ibovPuhLw8qe3wLEIL1hRKGen6jJWtz94RIppetzfaFBBKT0SMC56J5oDBkYZRkR +XgI3QveYVpJIFakCQgDoE6x+f9zh34+ZoIMPq959NEl2G3AtOVPWPBHuQeEDKDZW +sPvx82599hXiFnN+1SyvKAWuIPiHvioLXdQmb819rQJCAJ09jxRslISSQX6VbY4p +5EfBr2bAMCClkc4BxLLt1vm/3BA7VRG4mi3QPu47vSbZZGfw0Y50xNG8BcGNZLSW +dlcZAkF3+XO7Ea7GtiQub2RRvbAPWfCANj8PogtNPVCnszb3wtoUhvo4YnhEdetq +LeEXOG0ZP79v/Wt+ATkLFz6NgE7jIQJBXYwvgBWfXK1qEX9bucjQRsxnSDM5Y5zI +hPZ+PK4yfKxNnlx7N+x4wLdExTrduT4kp4KpyUgcossdmJ66JK1/Y5I= +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/zyxel-vmg1312_rsa.key b/brute/badkeys/keys/zyxel-vmg1312_rsa.key new file mode 100644 index 0000000..f31f331 --- /dev/null +++ b/brute/badkeys/keys/zyxel-vmg1312_rsa.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICZgIBAAKBgwDHY3qOn4zaLIbtc8UpWZTQpCuUXoFTOF7kD5GNOmjb8/wPGuxt +vT+tAsUb/2JkJxyl7BnznC/V7pTi9LReZ4hDQPtL0zw+rDEX7c3ZfFlI7cHO/vKw +uty5hFj74XdXRjk8/2dKvMNeoK/vN4Jjq8R2GkuSvZWST6MKg65jjzkSkRYlAgMB +AAECgYIVpkuMaO6WUfdkmQlzb/KkpO7GcWyBaMGE6dvYBduU5m0cBsRdNgtP8dkN +LTG1xjlqGrTGqtLW28z2x7M9/OfixV5vWbtZFcb7CpKzNkwvnkqPQE5/UDuxTcu4 +W5p1Zk2f4x9cqZGUEeklxDHCujne24MMZp21v83PhYgnXRnZSSYRAkIA3BCNRUCx +W27GIflbyUHLbzI54HISzqBmyOnBbcu47OSq+DOPwWSpSmNZ7ic7JJDdTQXhCBjf +d8D/pcBkNllRgAMCQgDn8pjmDemQajbcvNXmTLFtCOFbXDOsxAQzCIITlQWidoWs +1PKlrUkWsz8EX7I40sTpizf/bk2mDx2Wfdo9D8TctwJCAIlHngkskTc7hEgfWT3U +WIb2axpzrv3NHDGLeQn4Q1UsVOdeqvf2xpDMJ4akXIW0BQmOeFHnykfXUuNggGNT +Fr9nAkFfkZK0P4k76reeLeRIVtNIJL+OACh+h+lfCaNm5CEFNqaPdtJft02FH+fY +KoHpbdaaj7VL9qvhvxqm1kYkoKmxcwJCAKH9PgEpSMqbcFTYVsnkIy1c3OKLCGwo +pLcuKQXC2UdqoDLrP3hSZS+3yukCcUJEMnsZwtVYXqsBuzlLmCTDlO42 +-----END RSA PRIVATE KEY----- diff --git a/brute/badkeys/metadata.yaml b/brute/badkeys/metadata.yaml new file mode 100644 index 0000000..0cebe83 --- /dev/null +++ b/brute/badkeys/metadata.yaml @@ -0,0 +1,375 @@ +# Rapid7 ssh-badkeys snapshot + Vagrant insecure key +# Snapshotted from https://github.com/rapid7/ssh-badkeys (authorized/ and host/) +# Keys in the "authorized" category are client private keys known to be deployed +# on live systems as authorized_keys entries. Keys in the "host" category are SSH +# host private keys found embedded in router/device firmware. + +# ── authorized keys (client keys with known default usernames) ───────────────── + +- file: vagrant-default.key + username: vagrant + vendor: HashiCorp Vagrant + cve: "" + description: Vagrant insecure default SSH key (any Vagrant VM using the default insecure keypair) + +- file: f5-bigip-cve-2012-1493.key + username: root + vendor: F5 BIG-IP + cve: CVE-2012-1493 + description: F5 BIG-IP 9.x-11.x hardcoded root SSH key used for device communication + +- file: exagrid-cve-2016-1561.key + username: root + vendor: ExaGrid + cve: CVE-2016-1561 + description: ExaGrid EX-series backup appliance hardcoded backdoor SSH key + +- file: ceragon-fibeair-cve-2015-0936.key + username: mateidu + vendor: Ceragon FibeAir + cve: CVE-2015-0936 + description: Ceragon FibeAir IP-10 microwave radio hardcoded support/admin SSH key + +- file: monroe-dasdec-cve-2013-0137.key + username: root + vendor: Monroe Electronics DASDEC + cve: CVE-2013-0137 + description: Monroe Electronics / Digital Alert Systems DASDEC EAS device hardcoded key + +- file: barracuda_load_balancer_vm.key + username: cluster + vendor: Barracuda Networks + cve: CVE-2014-8428 + description: Barracuda Load Balancer VM hardcoded cluster SSH key + +- file: array-networks-vapv-vxag.key + username: sync + vendor: Array Networks + cve: "" + description: Array Networks vAPV / vxAG SSL VPN hardcoded sync user SSH key + +- file: loadbalancer.org-enterprise-va.key + username: root + vendor: Loadbalancer.org + cve: "" + description: Loadbalancer.org Enterprise VA 7.5.2 static root SSH key + +- file: quantum-dxi-v1000.key + username: root + vendor: Quantum + cve: "" + description: Quantum DXi V1000 deduplication appliance hardcoded root SSH key + +# ── host keys (server-side keys found in device firmware) ───────────────────── + +- file: tandberg-vcs.key + username: root + vendor: Tandberg / Cisco TelePresence + cve: CVE-2009-4510 + description: Tandberg Video Communication Server (VCS) hardcoded SSH host key + +- file: kali-rpi2.key + username: root + vendor: Kali Linux (ARM) + cve: "" + description: Kali Linux 1.x Raspberry Pi 2 default SSH host key shipped in OS image + +- file: Actiontec_q2000_rsa.key + username: root + vendor: Actiontec + cve: "" + description: Actiontec Q2000 DSL gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: advantech_eki_rsa.key + username: root + vendor: Advantech + cve: "" + description: Advantech EKI serial device server hardcoded RSA SSH host key (ICSA-15-309-01) + +- file: Alice_1121_rsa.key + username: root + vendor: Alice / Telecom Italia + cve: "" + description: Alice 1121 DSL gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: Cisco_rtp300_dsa.key + username: root + vendor: Cisco + cve: "" + description: Cisco RTP300 VoIP router hardcoded DSA SSH host key (CERT VU#566724) + +- file: Cisco_rtp300_rsa.key + username: root + vendor: Cisco + cve: "" + description: Cisco RTP300 VoIP router hardcoded RSA SSH host key (CERT VU#566724) + +- file: Cisco_rv120w_dsa.key + username: root + vendor: Cisco + cve: "" + description: Cisco RV120W VPN firewall hardcoded DSA SSH host key (CERT VU#566724) + +- file: Cisco_rv120w_rsa.key + username: root + vendor: Cisco + cve: "" + description: Cisco RV120W VPN firewall hardcoded RSA SSH host key (CERT VU#566724) + +- file: Cisco_RV315W_dsa.key + username: root + vendor: Cisco + cve: "" + description: Cisco RV315W wireless router hardcoded DSA SSH host key (CERT VU#566724) + +- file: Cisco_RV315W_rsa.key + username: root + vendor: Cisco + cve: "" + description: Cisco RV315W wireless router hardcoded RSA SSH host key (CERT VU#566724) + +- file: Comtrend_AR5387UN_rsa.key + username: root + vendor: Comtrend + cve: "" + description: Comtrend AR5387UN ADSL router hardcoded RSA SSH host key (CERT VU#566724) + +- file: Edimax_AR-7167_dsa.key + username: root + vendor: Edimax + cve: "" + description: Edimax AR-7167 ADSL router hardcoded DSA SSH host key (CERT VU#566724) + +- file: Edimax_AR-7167_rsa.key + username: root + vendor: Edimax + cve: "" + description: Edimax AR-7167 ADSL router hardcoded RSA SSH host key (CERT VU#566724) + +- file: EVW3226_rsa.key + username: root + vendor: Rapid7 ssh-badkeys (origin unknown) + cve: "" + description: EVW3226 device hardcoded RSA SSH host key (CERT VU#566724) + +- file: Huawei_bm626_dsa.key + username: root + vendor: Huawei + cve: "" + description: Huawei BM626 WiMAX CPE hardcoded DSA SSH host key (CERT VU#566724) + +- file: Huawei_bm626_rsa.key + username: root + vendor: Huawei + cve: "" + description: Huawei BM626 WiMAX CPE hardcoded RSA SSH host key (CERT VU#566724) + +- file: Innacomm_w3400v_rsa.key + username: root + vendor: Innacomm + cve: "" + description: Innacomm W3400V ADSL gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: Linksys_X1000_rsa.key + username: root + vendor: Linksys / Belkin + cve: "" + description: Linksys X1000 ADSL router hardcoded RSA SSH host key (CERT VU#566724) + +- file: moovbox_host_dsa.key + username: root + vendor: Moovbox + cve: "" + description: Moovbox media server hardcoded DSA SSH host key (XiphosResearch MoovMisManage) + +- file: moovbox_host_rsa.key + username: root + vendor: Moovbox + cve: "" + description: Moovbox media server hardcoded RSA SSH host key (XiphosResearch MoovMisManage) + +- file: Moxa_6150_rsa.key + username: root + vendor: Moxa + cve: "" + description: Moxa 6150 serial device server hardcoded RSA SSH host key (CERT VU#566724) + +- file: Moxa_ia240_dsa.key + username: root + vendor: Moxa + cve: "" + description: Moxa IA240 industrial computer hardcoded DSA SSH host key (CERT VU#566724) + +- file: Moxa_ia240_rsa.key + username: root + vendor: Moxa + cve: "" + description: Moxa IA240 industrial computer hardcoded RSA SSH host key (CERT VU#566724) + +- file: Ont_g4020w_rsa.key + username: root + vendor: Rapid7 ssh-badkeys (origin unknown) + cve: "" + description: Ont G4020W device hardcoded RSA SSH host key (CERT VU#566724) + +- file: Pace_V5542_dsa.key + username: root + vendor: Pace + cve: "" + description: Pace V5542 DSL gateway hardcoded DSA SSH host key (CERT VU#566724) + +- file: Quanta_LTE.key + username: root + vendor: Quanta + cve: "" + description: Quanta LTE router hardcoded SSH host key (Pierre Kim advisory 2016-quanta-0x00) + +- file: Sagemcom_2740_rsa.key + username: root + vendor: Sagemcom + cve: "" + description: Sagemcom 2740 DSL gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: Sagemcom_sx682_dsa.key + username: root + vendor: Sagemcom + cve: "" + description: Sagemcom SX682 gateway hardcoded DSA SSH host key (CERT VU#566724) + +- file: Seagate_GoFlex_dsa.key + username: root + vendor: Seagate + cve: "" + description: Seagate GoFlex Home NAS hardcoded DSA SSH host key (CERT VU#566724) + +- file: Seagate_GoFlex_rsa.key + username: root + vendor: Seagate + cve: "" + description: Seagate GoFlex Home NAS hardcoded RSA SSH host key (CERT VU#566724) + +- file: Telefonica-de-Espana_rsa.key + username: root + vendor: Telefonica de Espana + cve: "" + description: Telefonica de Espana DSL gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: Tplink_tdw8960n-V1_rsa.key + username: root + vendor: TP-Link + cve: "" + description: TP-Link TDW8960N V1 ADSL modem hardcoded RSA SSH host key (CERT VU#566724) + +- file: Tplink_w8950nd_rsa.key + username: root + vendor: TP-Link + cve: "" + description: TP-Link W8950ND ADSL router hardcoded RSA SSH host key (CERT VU#566724) + +- file: Tplink_w8950n_rsa.key + username: root + vendor: TP-Link + cve: "" + description: TP-Link W8950N ADSL router hardcoded RSA SSH host key (CERT VU#566724) + +- file: Trendnet_tdmc500_rsa.key + username: root + vendor: TRENDnet + cve: "" + description: TRENDnet TDMC500 IP camera hardcoded RSA SSH host key (CERT VU#566724) + +- file: Trendnet_tew715apo_dsa.key + username: root + vendor: TRENDnet + cve: "" + description: TRENDnet TEW-715APO access point hardcoded DSA SSH host key (CERT VU#566724) + +- file: Trendnet_tew715apo_rsa.key + username: root + vendor: TRENDnet + cve: "" + description: TRENDnet TEW-715APO access point hardcoded RSA SSH host key (CERT VU#566724) + +- file: Trendnet_tew816drm_dsa.key + username: root + vendor: TRENDnet + cve: "" + description: TRENDnet TEW-816DRM router hardcoded DSA SSH host key (CERT VU#566724) + +- file: Trendnet_tew816drm_rsa.key + username: root + vendor: TRENDnet + cve: "" + description: TRENDnet TEW-816DRM router hardcoded RSA SSH host key (CERT VU#566724) + +- file: Trendnet_tvip310pi_dsa.key + username: root + vendor: TRENDnet + cve: "" + description: TRENDnet TVIP310PI IP camera hardcoded DSA SSH host key (CERT VU#566724) + +- file: Trendnet_tvip310pi_rsa.key + username: root + vendor: TRENDnet + cve: "" + description: TRENDnet TVIP310PI IP camera hardcoded RSA SSH host key (CERT VU#566724) + +- file: Westermo_MRD310_dsa.key + username: root + vendor: Westermo + cve: "" + description: Westermo MRD-310 industrial cellular router hardcoded DSA SSH host key + +- file: Westermo_MRD310_rsa.key + username: root + vendor: Westermo + cve: "" + description: Westermo MRD-310 industrial cellular router hardcoded RSA SSH host key + +- file: Zhone_6512a1_rsa.key + username: root + vendor: Zhone Technologies + cve: "" + description: Zhone 6512-A1 ADSL gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: Zyxel_fsg2200_rsa.key + username: root + vendor: Zyxel + cve: "" + description: Zyxel FSG2200 NAS hardcoded RSA SSH host key (CERT VU#566724) + +- file: Zyxel_p870h_rsa.key + username: root + vendor: Zyxel + cve: "" + description: Zyxel P-870H-51a DSL gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: Zyxel_pmg1006_dsa.key + username: root + vendor: Zyxel + cve: "" + description: Zyxel PMG1006 gateway hardcoded DSA SSH host key (CERT VU#566724) + +- file: Zyxel_pmg1006_rsa.key + username: root + vendor: Zyxel + cve: "" + description: Zyxel PMG1006 gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: zyxel-q100_rsa.key + username: root + vendor: Zyxel + cve: "" + description: Zyxel Q100 gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: Zyxel_sbg3300_rsa.key + username: root + vendor: Zyxel + cve: "" + description: Zyxel SBG3300 gateway hardcoded RSA SSH host key (CERT VU#566724) + +- file: zyxel-vmg1312_rsa.key + username: root + vendor: Zyxel + cve: "" + description: Zyxel VMG1312 VDSL gateway hardcoded RSA SSH host key (CERT VU#566724) diff --git a/brute/badkeys/registry.go b/brute/badkeys/registry.go new file mode 100644 index 0000000..9acc6dc --- /dev/null +++ b/brute/badkeys/registry.go @@ -0,0 +1,60 @@ +// Package badkeys provides a curated, embedded bundle of known-compromised +// SSH private keys (Rapid7 ssh-badkeys + Vagrant + vendor defaults). Each +// entry pairs a key with its default username and CVE metadata so brute +// modules can surface CVE-tagged findings without external files. +package badkeys + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + + "gopkg.in/yaml.v3" +) + +type Entry struct { + File string + Username string + Vendor string + CVE string + Description string + PEM []byte + Fingerprint string // sha256 hex of PEM bytes +} + +type metaEntry struct { + File string `yaml:"file"` + Username string `yaml:"username"` + Vendor string `yaml:"vendor"` + CVE string `yaml:"cve"` + Description string `yaml:"description"` +} + +func Load() ([]Entry, error) { + raw, err := assets.ReadFile("metadata.yaml") + if err != nil { + return nil, fmt.Errorf("read metadata.yaml: %w", err) + } + var metas []metaEntry + if err := yaml.Unmarshal(raw, &metas); err != nil { + return nil, fmt.Errorf("parse metadata.yaml: %w", err) + } + out := make([]Entry, 0, len(metas)) + for _, m := range metas { + pem, err := assets.ReadFile("keys/" + m.File) + if err != nil { + return nil, fmt.Errorf("read keys/%s: %w", m.File, err) + } + sum := sha256.Sum256(pem) + out = append(out, Entry{ + File: m.File, + Username: m.Username, + Vendor: m.Vendor, + CVE: m.CVE, + Description: m.Description, + PEM: pem, + Fingerprint: hex.EncodeToString(sum[:]), + }) + } + return out, nil +} diff --git a/brute/badkeys/registry_test.go b/brute/badkeys/registry_test.go new file mode 100644 index 0000000..20fbd5f --- /dev/null +++ b/brute/badkeys/registry_test.go @@ -0,0 +1,32 @@ +package badkeys + +import "testing" + +func TestLoadReturnsNonEmptyBundle(t *testing.T) { + bundle, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + if len(bundle) < 5 { + t.Fatalf("expected >=5 keys, got %d", len(bundle)) + } +} + +func TestLoadParsesVagrantEntry(t *testing.T) { + bundle, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + for _, e := range bundle { + if e.Vendor == "HashiCorp Vagrant" { + if e.Username != "vagrant" { + t.Fatalf("vagrant entry username = %q, want vagrant", e.Username) + } + if len(e.PEM) < 100 { + t.Fatalf("vagrant PEM too short: %d bytes", len(e.PEM)) + } + return + } + } + t.Fatal("no Vagrant entry found in bundle") +} From f4165762a73ab426d59afefa7b8489e4ddb4be27 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:45:33 -0500 Subject: [PATCH 08/49] fix(badkeys): drop SSH host keys (not usable as client auth) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the 52 keys from Rapid7's host/ directory (SSH server identity keys extracted from device firmware). These keys authenticate servers to clients and cannot be used as client identities in authorized_keys — every attempt would guarantee auth failure and waste connection slots. Retain only the 9 keys from Rapid7's authorized/ directory (plus the Vagrant key which was already among them) — these are actual client private keys confirmed present in real-world authorized_keys deployments. Prune metadata.yaml to match. Add SOURCES.md attributing the upstream repositories and explaining the host/ exclusion rationale. --- brute/badkeys/SOURCES.md | 13 + brute/badkeys/keys/Actiontec_q2000_rsa.key | 15 - brute/badkeys/keys/Alice_1121_rsa.key | 15 - brute/badkeys/keys/Cisco_RV315W_dsa.key | 12 - brute/badkeys/keys/Cisco_RV315W_rsa.key | 15 - brute/badkeys/keys/Cisco_rtp300_dsa.key | 12 - brute/badkeys/keys/Cisco_rtp300_rsa.key | 15 - brute/badkeys/keys/Cisco_rv120w_dsa.key | 12 - brute/badkeys/keys/Cisco_rv120w_rsa.key | 15 - brute/badkeys/keys/Comtrend_AR5387UN_rsa.key | 15 - brute/badkeys/keys/EVW3226_rsa.key | 15 - brute/badkeys/keys/Edimax_AR-7167_dsa.key | 12 - brute/badkeys/keys/Edimax_AR-7167_rsa.key | 15 - brute/badkeys/keys/Huawei_bm626_dsa.key | 12 - brute/badkeys/keys/Huawei_bm626_rsa.key | 15 - brute/badkeys/keys/Innacomm_w3400v_rsa.key | 15 - brute/badkeys/keys/Linksys_X1000_rsa.key | 15 - brute/badkeys/keys/Moxa_6150_rsa.key | 15 - brute/badkeys/keys/Moxa_ia240_dsa.key | 12 - brute/badkeys/keys/Moxa_ia240_rsa.key | 15 - brute/badkeys/keys/Ont_g4020w_rsa.key | 15 - brute/badkeys/keys/Pace_V5542_dsa.key | 12 - brute/badkeys/keys/Quanta_LTE.key | 15 - brute/badkeys/keys/Sagemcom_2740_rsa.key | 15 - brute/badkeys/keys/Sagemcom_sx682_dsa.key | 8 - brute/badkeys/keys/Seagate_GoFlex_dsa.key | 12 - brute/badkeys/keys/Seagate_GoFlex_rsa.key | 27 -- .../badkeys/keys/Telefonica-de-Espana_rsa.key | 15 - brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key | 15 - brute/badkeys/keys/Tplink_w8950n_rsa.key | 15 - brute/badkeys/keys/Tplink_w8950nd_rsa.key | 15 - brute/badkeys/keys/Trendnet_tdmc500_rsa.key | 15 - brute/badkeys/keys/Trendnet_tew715apo_dsa.key | 12 - brute/badkeys/keys/Trendnet_tew715apo_rsa.key | 15 - brute/badkeys/keys/Trendnet_tew816drm_dsa.key | 12 - brute/badkeys/keys/Trendnet_tew816drm_rsa.key | 15 - brute/badkeys/keys/Trendnet_tvip310pi_dsa.key | 12 - brute/badkeys/keys/Trendnet_tvip310pi_rsa.key | 15 - brute/badkeys/keys/Westermo_MRD310_dsa.key | 12 - brute/badkeys/keys/Westermo_MRD310_rsa.key | 27 -- brute/badkeys/keys/Zhone_6512a1_rsa.key | 15 - brute/badkeys/keys/Zyxel_fsg2200_rsa.key | 27 -- brute/badkeys/keys/Zyxel_p870h_rsa.key | 15 - brute/badkeys/keys/Zyxel_pmg1006_dsa.key | 12 - brute/badkeys/keys/Zyxel_pmg1006_rsa.key | 15 - brute/badkeys/keys/Zyxel_sbg3300_rsa.key | 15 - brute/badkeys/keys/advantech_eki_rsa.key | 27 -- brute/badkeys/keys/kali-rpi2.key | 27 -- brute/badkeys/keys/moovbox_host_dsa.key | 12 - brute/badkeys/keys/moovbox_host_rsa.key | 15 - brute/badkeys/keys/tandberg-vcs.key | 12 - brute/badkeys/keys/zyxel-q100_rsa.key | 15 - brute/badkeys/keys/zyxel-vmg1312_rsa.key | 15 - brute/badkeys/metadata.yaml | 324 +----------------- 54 files changed, 17 insertions(+), 1108 deletions(-) create mode 100644 brute/badkeys/SOURCES.md delete mode 100644 brute/badkeys/keys/Actiontec_q2000_rsa.key delete mode 100644 brute/badkeys/keys/Alice_1121_rsa.key delete mode 100644 brute/badkeys/keys/Cisco_RV315W_dsa.key delete mode 100644 brute/badkeys/keys/Cisco_RV315W_rsa.key delete mode 100644 brute/badkeys/keys/Cisco_rtp300_dsa.key delete mode 100644 brute/badkeys/keys/Cisco_rtp300_rsa.key delete mode 100644 brute/badkeys/keys/Cisco_rv120w_dsa.key delete mode 100644 brute/badkeys/keys/Cisco_rv120w_rsa.key delete mode 100644 brute/badkeys/keys/Comtrend_AR5387UN_rsa.key delete mode 100644 brute/badkeys/keys/EVW3226_rsa.key delete mode 100644 brute/badkeys/keys/Edimax_AR-7167_dsa.key delete mode 100644 brute/badkeys/keys/Edimax_AR-7167_rsa.key delete mode 100644 brute/badkeys/keys/Huawei_bm626_dsa.key delete mode 100644 brute/badkeys/keys/Huawei_bm626_rsa.key delete mode 100644 brute/badkeys/keys/Innacomm_w3400v_rsa.key delete mode 100644 brute/badkeys/keys/Linksys_X1000_rsa.key delete mode 100644 brute/badkeys/keys/Moxa_6150_rsa.key delete mode 100644 brute/badkeys/keys/Moxa_ia240_dsa.key delete mode 100644 brute/badkeys/keys/Moxa_ia240_rsa.key delete mode 100644 brute/badkeys/keys/Ont_g4020w_rsa.key delete mode 100644 brute/badkeys/keys/Pace_V5542_dsa.key delete mode 100644 brute/badkeys/keys/Quanta_LTE.key delete mode 100644 brute/badkeys/keys/Sagemcom_2740_rsa.key delete mode 100644 brute/badkeys/keys/Sagemcom_sx682_dsa.key delete mode 100644 brute/badkeys/keys/Seagate_GoFlex_dsa.key delete mode 100644 brute/badkeys/keys/Seagate_GoFlex_rsa.key delete mode 100644 brute/badkeys/keys/Telefonica-de-Espana_rsa.key delete mode 100644 brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key delete mode 100644 brute/badkeys/keys/Tplink_w8950n_rsa.key delete mode 100644 brute/badkeys/keys/Tplink_w8950nd_rsa.key delete mode 100644 brute/badkeys/keys/Trendnet_tdmc500_rsa.key delete mode 100644 brute/badkeys/keys/Trendnet_tew715apo_dsa.key delete mode 100644 brute/badkeys/keys/Trendnet_tew715apo_rsa.key delete mode 100644 brute/badkeys/keys/Trendnet_tew816drm_dsa.key delete mode 100644 brute/badkeys/keys/Trendnet_tew816drm_rsa.key delete mode 100644 brute/badkeys/keys/Trendnet_tvip310pi_dsa.key delete mode 100644 brute/badkeys/keys/Trendnet_tvip310pi_rsa.key delete mode 100644 brute/badkeys/keys/Westermo_MRD310_dsa.key delete mode 100644 brute/badkeys/keys/Westermo_MRD310_rsa.key delete mode 100644 brute/badkeys/keys/Zhone_6512a1_rsa.key delete mode 100644 brute/badkeys/keys/Zyxel_fsg2200_rsa.key delete mode 100644 brute/badkeys/keys/Zyxel_p870h_rsa.key delete mode 100644 brute/badkeys/keys/Zyxel_pmg1006_dsa.key delete mode 100644 brute/badkeys/keys/Zyxel_pmg1006_rsa.key delete mode 100644 brute/badkeys/keys/Zyxel_sbg3300_rsa.key delete mode 100644 brute/badkeys/keys/advantech_eki_rsa.key delete mode 100644 brute/badkeys/keys/kali-rpi2.key delete mode 100644 brute/badkeys/keys/moovbox_host_dsa.key delete mode 100644 brute/badkeys/keys/moovbox_host_rsa.key delete mode 100644 brute/badkeys/keys/tandberg-vcs.key delete mode 100644 brute/badkeys/keys/zyxel-q100_rsa.key delete mode 100644 brute/badkeys/keys/zyxel-vmg1312_rsa.key diff --git a/brute/badkeys/SOURCES.md b/brute/badkeys/SOURCES.md new file mode 100644 index 0000000..f81e6a3 --- /dev/null +++ b/brute/badkeys/SOURCES.md @@ -0,0 +1,13 @@ +# Bad-keys bundle sources + +This package vendors known-compromised SSH client private keys from: + +- [Rapid7/ssh-badkeys](https://github.com/rapid7/ssh-badkeys) (MIT) — vendor default keys for F5 BIG-IP, ExaGrid, Ceragon FibeAir, Monroe DASDEC, Barracuda, Array Networks, Loadbalancer.org, Quantum DXi +- [HashiCorp Vagrant insecure key](https://github.com/hashicorp/vagrant/tree/main/keys) (MIT) — default Vagrant VM identity + +Only Rapid7's `authorized/` directory (client identities found in real-world +`authorized_keys` files) is mirrored here. The `host/` directory (SSH server +identity keys extracted from firmware) is intentionally excluded — host keys +are not usable for client-side authentication. + +Refreshed via the same monthly cadence as `wordlist/` updates. diff --git a/brute/badkeys/keys/Actiontec_q2000_rsa.key b/brute/badkeys/keys/Actiontec_q2000_rsa.key deleted file mode 100644 index 46b4a5f..0000000 --- a/brute/badkeys/keys/Actiontec_q2000_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCK/N4qfs6LpD5wDnKh/oKCSTDWaaC5uUPrAdLq3uE1WqtmWphE -G0STxKhaGi8TxTxPJnIT5jBlpBGx7t1CVFCCDL+I119IO478eb+JE62pjmMxp2b3 -nzP4BdVIF0lYSvYeWc5TzbGxgG0NLqDb076lTu61ayflWotlcVDn2264fSTlAgMB -AAECgYIBQPsXs30IhMhLfp07XWf0HkFutmImbLCoLvDjiwOMGCv2bZlQ+O64tA/t -nmYXe94Aok+ngYq5jH12DklzOHl1sekWLNCwFhmmXDS7OkpAldUCnP22Bai/Z7qa -BPACDejTi5KJsvG4qKuwE7tAMVptq9hbpG680c3kwpOJjlS3/mXBAkIA7y5WeKsL -1Mj5mqysXKp7yyAuNkhkXmGJKBIIht2ZCuYUG8WExCWV70w4ByGQoYtqBWPuhQLD -qewxsPx3Gva6MuECQgCUwuHmoXPORDC3tCI5+KdVHXMgVB8gZnN+derTcG8Uh9Uv -ReT/1dMLqa083StMSbEckLfRtG4y4ozxXK3hHO12hQJBOUXCRQDIQ3qgck44s7PL -Etew2SS6i/MVEbhHvGuhsv9m/0NryEArx/JbVDHQXS5yA7prKgSAb9b0CypZJua/ -rWECQRVZJkwPrWvTc15ZlnPVUEYxtLzV9aWTrk1epLV1NCuxFpHzKNriF6hb0EWk -w7rt9GHSDt8kUpAw3OMiVr2whg91AkEMKqSqrFTKXjKilkRgql6XguWfjws+NFYH -BsgpJLXJvQ94WrWpTmCXcXjTvuTptTsfyrhLiuzLJD5T2ehqBE5IKw== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Alice_1121_rsa.key b/brute/badkeys/keys/Alice_1121_rsa.key deleted file mode 100644 index 8f8b958..0000000 --- a/brute/badkeys/keys/Alice_1121_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZgIBAAKBgwCGS+2tZHa5R22/6HBSLpN9yik+zUYms8pZ56poPd+e/Fiz9RuV -81zctq5zobQL/X/GUqum2eaDvg/M4Zh892jBlasN285c/aMche7tNK2NWhQShoOH -QLg6dpY9irX4k7GuJIZqkUlRfauOVms9GrAWHjZ7Ts3wLTlYImzdb+dIIekZAgMB -AAECgYIQbVw63/WXz1Fw7o1CuDjJY7+s02430Lk5Sxmwm5TtfR+aj6paqsSaqOvo -8Ag2iUEmcLlXdQL57XdAT/4Xlynkt+zikkUWYzEjOecOabNeyvkrt9pY/TNUM6GI -5gP2bTyPP5Jr6M05dr0XgTmTAME5EdVOsumGqvnG8+OjxgcqQpppAkIA0HyM4iFj -YlHSXDIIxig5c2gHGuufph/DR95iRIVoOc9kwbsWcYbWUVI2O7X8N83ANiZlzcHB -uGDY3rC+KyUEwY0CQgCk5wO6kfm4KgofIq2D9+LBocNQgLDyouaZzJg7qJ8FuNbN -qNitV7lUYhXWIwr7mfkb/FFeyJ0lqyaqFG9YhoAUvQJCAMQE9qj932VnrLMGT/2P -gESjgFqtQtN8dayyyA3IqLAB8Ke9cWKX9hVLiLYnatZE2v2OqJUGIU9rrzvhTUtf -aNNZAkIAgLMgMOOvH3IIFkbNX7r/ChrDQjg+YhCLo6uPgLhY7HFXjIlkGt3lchtf -aZJOBxIj3xitNSmjpHuQoJt0T4Yhvo0CQXG//++Zy7aqwcwlalxRkbDMq+pDCULR -4aO1pxRy7lTV1gTdyuypy5f5rTLEByZ8/K6+q+XD94/F5WrRUBBD+ewz ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_RV315W_dsa.key b/brute/badkeys/keys/Cisco_RV315W_dsa.key deleted file mode 100644 index 1e720f4..0000000 --- a/brute/badkeys/keys/Cisco_RV315W_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQDSXkD+i6T6MbU9DT+gk/jskwl5z3yxUQWhxNcoxdAL2FplMALR -FEaKXpQYA1L5JzhdXIJWiermPpwUjvnPrTgZTsGsj0ehOgtCQNRELCaJAEN2ZZGB -NMWClVq8KfMOj6GRog1WzObw1NA9t1dAPE+J22mSx2VWtIIfEa71qyXnfwIVAIjx -b6TBDZKeKXINEmiSxVKcKAoVAoGBAIBtRnFh5aZgakK7/uCcpkH0aXJn2XA88JyV -SzWQXkWZs3kJS0Ml9Z/ZWwW1D6XqgKgzK/yNp3girItoxtFGU/z97etwxyco/D8i -uEV6X1W/ZZeJBOgMu4KYtUqWS/Rm4JTgQeLdivAGIzXO8nmyC26ZSouJfuCLBcWB -DxN1hWckAoGBAIDCCmsH9HlIhwAwkDHWrq2jVsiQOPfKDNS3zzVW8NlRMtpzMrG0 -Qa7x1u8MEoICYhcQ8dsvWpaQEJgv+aKGbHdHxSFzu8Wj9n3iZdWWwdE/GjiE+iv1 -UzW1Cus0KAcy+kXVeSGnMXK2jqAB+bP6T5mBjpZUI+UGkYFiXIZ3W2+8AhRpt3Ke -dLIXnfBScDMdRepvTzJvzQ== ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_RV315W_rsa.key b/brute/badkeys/keys/Cisco_RV315W_rsa.key deleted file mode 100644 index 700b90c..0000000 --- a/brute/badkeys/keys/Cisco_RV315W_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCACRXscGPHm5ds/wtu5oCeYERXSanZfR4MD64KsEbPmRJV3f1n -6Qox1AxUhAr5j0XFtpGeyy0ARtLAifD6DhPRGaCJt8wMjeJMoSsolOL7D1wfFQU5 -TIBHFQ6ociGv/NQX2SJqp9kWts6UAWp1wVYtuC58eWelRv9CPPNZVQtfG88rAgMB -AAECgYIzMm7f0rkzchYE8Z9Cw00fB2OMe9o9K3i+Xe68O2y1SoI4b8IKsPbwodgk -W4B/9jhyLOTh2GCwQaG4d/AyLTrjW4GyW4zKhADRhB1CRf8aopNfXHi3mqeG25YN -1Hy0AoLZ1i9+8JUQ2UF0iCNiOdxzOt+/+oyR3rfztYoCSmZMkV5RAkIAg1Bmdkz4 -dveg0t79jZtMFq9ktScwvMw9HABujAC6e625IdegRGjKhO9+jiLYYh+fnI5VqZ6v -SpvovnnyTSKBrlUCQgD5m7xKpoJgb08r0PlM936ExwreTZY2soO7d/JJ7s2Frff5 -9ax6MSxAIUMcTj91dfvIHnP1PBLfMr//a201nvoHfwJBXjXRvTBN1rSkqoWXrf/s -IB5oB1v9qIZzlWJt7X4cTN1/hFs6PbpdCfD1gC5ZxlRf5CduKAqUQtgGrJm7EEIg -fh0CQQKez+s4tEWu5XXgS72zG3DpZgPPbLRGS3u8Vp7QEvhES0YkfhsKSRyjPMEf -USs6gdon96+rYcdWEQ3tYsgdUyKhAkFfIyu5hc7gsW5VdZ2VSqwSZD+AvMbnPJse -pGuOzilLgRBt6sQypoW/jC/6cx8ed3gvPZJMTmTrmKHNAv9fAgWRSA== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rtp300_dsa.key b/brute/badkeys/keys/Cisco_rtp300_dsa.key deleted file mode 100644 index 7a6dd76..0000000 --- a/brute/badkeys/keys/Cisco_rtp300_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBvQIBAAKBgQDFJj2bXyRskFtIYj9x1uwWFPDEYSrVz9nV2XIpflSZ1XvJ2Umw -4xREgFEdv/sKr9aazE7+Or+Odryk1DQy/oE9vaCcchVP3/6siRvjRuGwlQc3tNFl -gSKDGDpfHGsEpVlLcx6hsErFnr64/e/HwydZ1vP4OvmU/yeUTLFzcZv4PwIVANYH -nr0a8eLhdfgh5qZKNjiut2QRAoGBAIsn4griDXnu1F1MXoVm/DuPcMSBeGubZeqf -RXnVF39vDL30wKp7l0KabJzbRxBe2I9YhCzrhw8AEjtgMtVneqk3JUwFJPkh439G -wmrW/ZdVej4EDFhHYF7U4Kgw/ieexx/e0wMbuiqz2QzgFVFdod8OfQhBBj20ekB3 -fm+5YFedAoGBAIPtAfYWtowYMAKunj3x3SGTk7w00KBZisZ1O8F11Km1XhhQGYf3 -ONOpWnpzVFPIxrS6Neo0Z9UBwe7CZ+8EP8bi1eJc5sPDMAIp1bWddBJ3lRtryQ2k -DN3L8xzoaKPvuIHxhuh7TzQhVIn/6ffqBYzk53iZe/xJVfelyQNuDzEnAhUA/DI4 -Hn2XDwL5WIoiC8jEqb8afqE= ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rtp300_rsa.key b/brute/badkeys/keys/Cisco_rtp300_rsa.key deleted file mode 100644 index 52e2ac2..0000000 --- a/brute/badkeys/keys/Cisco_rtp300_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwCCe1FOkJuYLqOaeRmGSO1Ads2wOdyoePlLLnqFMIzImdza5Sjf -+QyEbNZdhMQDx0R+z0Wu714cnAQndaM3Y5I0URhpk8dfhxIIt/MNr99/B9glhHmx -d/twqKKNVvqc+f+Wztc3CbBUb+BbYvwnxce3JKZAEOWv9B+8R/TaPuZ4apmxAgMB -AAECgYIEIoC4f/JXwFr3fwsAy4FFLNL4Et+ByHs9hfrwOaOtLgqmwgV1EmhbLtZ5 -0HTqPNPj4QWA9YBuScBJOLv211PIVKzgNVISLRlY3HcJGZP0Q5zQ4mOo7PoGpPoM -Gv9NhUWvDj2GDM8oeJ9+SG+mO3lwr9wRELAT4RR2yN9xK5TkGVW/AkIA9+KuMxM4 -lOgDtfS+tmfAqTTTPOAKPnuHRLgC3IrxjXgEjxETMOJQw73ZKAoOYbXSaqAIiq+U -qUXcuO25TVexulMCQgCGwMZnIFROulBTYlhvjuPxTnaBi8yGrqScwGge4XTBqpGe -ftfRT80wCT6IAVaYSMeho4lte8GR5pzwceJIXWpDawJBEwWFAoxWCi8nob3PKKYb -haB1GTXD83l9LsvEBHJxCL8N8oCH7XdgZTTbRhRHeD4AkIgJP8MrcDXZMyOi1YmG -LNsCQSE7NTKFdIUdyWVm4WxRjsEZmnwEH+Iu+4V0pbjH3OVNzS+LFGoYBgAMp5Ee -014mxKSGEgQf+vKiLp3VV/qEnwoVAkIAtlL0mQyx+wfaHcYOZZ6T0JqFJMaO/Nmp -R1pDSwCwZRhvOCU0yXYpJo8F/Bc+syzGa0nbr8vta5jCWw9pHBzplMI= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rv120w_dsa.key b/brute/badkeys/keys/Cisco_rv120w_dsa.key deleted file mode 100644 index e1b52c2..0000000 --- a/brute/badkeys/keys/Cisco_rv120w_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBugIBAAKBgQCKdXLk8atJ5VPmLWf+TWifepkpHKNnRaOKFbhS2+/wovwbf74/ -x52deg6ixaARLiymE4enAMDXoxJeh5f1MnHAa0pyNI16k2YdtesVbeJYLp2+v48t -YWXYKgafUBZSsxH3Rzf77NB4rzqaNanvW3iS/44hZ+hh1/FapqXhlfM6uwIVALVs -ORLzNMG9rX5V/Weep8qyvTk7AoGAfEQzgwLT3EJBraxVLBnmjGTEXIB3XH9cn/gh -wmfICAQLV1aqI16P7N6mIoPRmN3BOT3LS1J6SQtBN3D7j+uk4WS6z8lGJCJXfB/m -i89sJssV4pTJiEBhGlAKlW4kpcoE2woqEmcEunJTZWbbK0YHaL251UlEWu7Q3ogb -8u38rSACgYAUAlMvXHESUBx46Bmbpe9u4zNdYwM5wf2KLnq+fRgp8GWQX53f4SNu -E1PxrjV52uGinIPNHNe/BE2lpeJ2I8uv/qnwjFArBFnS9a//JM+8CivOCjrJ25Ya -iU95GglOmkWrtUyYB2pZsXBQI5J4uvNQIuCmE5gbUP3BR4d9eSZ8DgIURWb7OxbY -GOCVPvqOxlsI7e1XbvU= ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Cisco_rv120w_rsa.key b/brute/badkeys/keys/Cisco_rv120w_rsa.key deleted file mode 100644 index 75e7dfe..0000000 --- a/brute/badkeys/keys/Cisco_rv120w_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICWwIBAAKBgQCdyRRI3+5n89wzquJvtX40jIibpxFiJLEf0+3ltVonBIRT3z5d -B0EQcQ7efzhH33S7ICgUacNZFsiNGytuyYdlQ4WG8a2D5mSypIFE11T3dLo8JBv9 -8h94jXqKzOtV3S+ypdRBHALYA1X63tWBOI3AP9/KWWzSyOy+LmXxz8n1nQIBIwKB -gQCZRv3DIq0Uhn4jkA8qsE62tGdyoktmp0z6WNh4vsya7nHrE2EnK55KfHTYI9BF -0cHaWcCI28UUtwv2ye+tc1BSN2NiZVN1zXyNIp7J6RrrlfDDKMrRu57RgzLKpN9A -28BSKu3IwHRzmemuqCfeXg8IXEXrLY7uAIFR4HpC5bIcCwJBAMzZxVnxbx9xjzcx -R6F1bE9FzVfqeNq8Ai9+mJcPUUR8otW7vtKd4AyWKYdB0UuJcwELCYT7El0HhEBJ -vEECg00CQQDFLtxNi9A3Lyi6nXiny1xldQ9PYLw/qWJ1C2zmfbpNErs6fhWDR3Hn -XlJBEEsEqYhnX5oHba1NQfw7vHg46nORAkARjwJJibkRUuBj0QYjyDx70sh1P/u6 -+iwSsxRkuCuJhwakmxBbMhqEvGn1po5ITZwkqSyzoH3qt4BdSCYUM2pXAkBDmwm0 -L++d5Eh6fyINpM84umumL8uDiogoISymzAVcQO+8SHxnhjWuaXtJge3Vtnfo7ZPl -SizKB/61ZTCIiuXLAkEAvufOOrbbeYIUUoP5331AHhqHNk2nNi+0YfEdw4gMq8i7 -7+PV84Ij7QE0URFdGnHqNwKAvxa0DllB+5sMXPBW5g== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Comtrend_AR5387UN_rsa.key b/brute/badkeys/keys/Comtrend_AR5387UN_rsa.key deleted file mode 100644 index cca2f7a..0000000 --- a/brute/badkeys/keys/Comtrend_AR5387UN_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCCuYhaXf8RgI8tXtKZMCoCTcuC0VCNPjnII/3gee48KhD0uAs3 -NpwYzvKf08G/PDllpml++kb8YxdnT4wqQEiP0y1hAhpDr/o/UR+GIBqm4hDVD5kd -FntVcxFIxObnwIbqcLHGdbpxHQAspzhzNvKCOfE2ttIoXJa1hspSQAevc0q1AgMB -AAECgYIWxrGhoQ/wYjhP/N3vj8s59Ij5RXPl8AoMO20f386oz+F9adWWbJ7c2a2/ -OBthMOKFOe3WkE+u6Krqtpriv6mE2VGkOsV+hTuTLxxL5fDmV/tuvmuS6a9SYGii -H3pSMM2tT2SN2b2K6IxlWdsCnymF5RcXDzgB+Bs0FhHeN8syrPU/AkIAikqgTuPq -DhhDK3jSTlRXzHt6TOGmyrZBvClVVS351j7aKKKXc9GOSm8bXIPozgjfnMrVDRcy -OGwx5TRLAyQ/2DcCQgDx/iAzTjl+graplaTywTTopedB3eUvxiztQzcbOOeSWFHJ -3ygEUEG5kLOAF78igtKZ1GJ66NRt9BulrIJuqDsmcwJBWH+xX0sTebmlIqjLTT1E -gqNyfMjbSFaicpuw9DXb8HOAgQisC35LpO69f9MapN+g2mIjPaFejiUPOTLh3jzQ -brkCQU6H/cMom5fmq4iVO5ZcBOOLE2VYsUuzsFi++18mNGBVsRUmCkJ8GRgzUU08 -Z5a3hcjOF7dzX5zfHynAtgNbLPbpAkEZas5pS0EJuZVO7VSgO+tdb0GLuCKP/l+K -FTraQJX2XpCT149AVee4powVSRDVABgWA88LEbyHdhC/4irUm9DBFg== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/EVW3226_rsa.key b/brute/badkeys/keys/EVW3226_rsa.key deleted file mode 100644 index cd43bd1..0000000 --- a/brute/badkeys/keys/EVW3226_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZgIBAAKBgwCDq1kGqqwdQVryCNcoyDbBpnL/okvM2d9NmR0OjprcToCZ2TZ5 -WUZt2BGwPE1QLJYskjhv7GwlfQ4qhEqHDg35wMrkO7j9LTQC7KW3xisOLuUil4Fm -MxPkol6s39945zBGpjw0l/BmJnUDlutxavkdd84fppFMwXNp2vbjxV1SYVc9AgMB -AAECgYIt+AYSWkbRxe36/1Qi9FeNn0+Z6S8em1gnTtQCr43oaW3jiJ7Imf8JPXzb -cwoo+hAKCpiylq+hHPpzpJEieqktbv1cSZhZ9omyP00iGJE73YSdeiwZZq6UtvxI -AIqDEql06a7OHhGT6BfK4q0vHm5n52SKT2oiOdX3WkJ+cPwPcxZPAkIApSaXXJj2 -GsgGR9Wxz8nB6Y9ZIYz0BE4QDiqMaY9wcZCQyrEPb0Qh1fR8EsZe5sjvBc545z8T -xPt9CT2AntcHRRsCQgDMGbl0lJtljLjNeTOEwVfev3Uoiu81UqDTe4rJs62vo7Gc -abprhy11Fdio3Jgsr808GYbmOVYpiWyGiI1L2xAShwJCAJjNjQx65nI/EjiSytND -jKwqGsDFJt140LgavBHLSrF4nc88ZdiABIJulAHXEuWbkjQgJpNnNEZ0nerXwdK1 -h59zAkIAxMNkDCUcLulcXBKlfS2cFc1EGILgm+p9y4RFx7BmGHbaSq2PIAzg4Qjf -p+OK/UG6vV7qs1bBXyfyLTzF40RcmU8CQV8gTmC0kZLGfXmqYksfPQfdRmYP9Zco -1QONXpmZmH79Y+QQURO2hpbC2kUGxbQemrBz+yORl7LS29tZkXkYquQ2 ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Edimax_AR-7167_dsa.key b/brute/badkeys/keys/Edimax_AR-7167_dsa.key deleted file mode 100644 index d8b33e3..0000000 --- a/brute/badkeys/keys/Edimax_AR-7167_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQDe6mJHhZhZOCouQP8MH20Jc3yD8QE6DX2EVeM5DAqrOOUcE2wV -lpzndsY3xk1vuWIBDuYc/+sUIjdm1lJa8yzW2/D9CFYCaEPYqV5Y+4zKQSt0ndWQ -Ic4Wj8KQ68tBtgYCCY5LHlT8qa5M1Wv+zt4WzcZDtucHER7xEefCJvXnJQIVAPHq -V268+AvcrlrfQdspoMObuxeJAoGAPjo83vFBdFKwB+HyFXF3/91hKNcLAqlblCXG -4fXo16lCFwQfUXgWU7cgCLsm1tquL6bQhXl9lCTZISnOdI4xLqrRWwRv8NUdjV8W -6MAbMTusmxLznI+Fxy7mz/aYpxBBGlMw1bBI9KdI53mq4Yt8G12CHyKq4JoIAeIb -FmkCgnYCgYBjjW5/w96z5w1skBQbC3j8RFNglupKS7iaXa04yw2dAz7P79OQe+tj -4KysKMjrHrfz5zFNo4ivpZCNiFezuMdVNwSNoQlARzNPy9ZW2dKXruPeya/XtpdW -ynBFRDtjrmPr9tpXaM2fRYlHnutPL7S7Cc0cCiFIGeS2prZQJ1MsPQIVAO13q/E7 -HoIn8eDK8LFtngs/6ZzG ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Edimax_AR-7167_rsa.key b/brute/badkeys/keys/Edimax_AR-7167_rsa.key deleted file mode 100644 index 61a9ab1..0000000 --- a/brute/badkeys/keys/Edimax_AR-7167_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCW6ToMbteHdLBddvV/CP3Hk1tF2Av180UFvyyK17lpOIKevtf+ -NWso/opmKKwePxAaGCz9cDWkJd2/Q1jcBJmh34V75CdUSGl5jhFso08FPXVKATeJ -jRUN9Q6ocVKOdpfpHP14FRdDLcLxi6wXsGtjcvcMcST4G9tV8Itgogu4Ox9NAgMB -AAECgYI8b2NRVlRXCUqV641uFeY39hG/20ES+pziK8jFQ5Ybhsd8llX+llr3XFSF -i/U92ahzosnlpvm8ZnOy5GAiIG98LN3m8RzrqhRZnVLxRgupKMqYYSWMzt7rcvs7 -OZPq3gpKyrDVD2iyRdRlzXEZhs5h0NAgNnLeaQLVjWtu+6QxSVKbAkIA5ArcK6GJ -j7Ihii29WVzDxCByjZO1GamCFVjNirJSxe+yFzFztfa+ETUgxrpMWV+ePw2z/R8k -v9FW7fQNzU4osR8CQgCpaZa0sh3KqIf5xF9zrHDbdYi77/80qQGmRxeak8enEV5r -AbD27IoBEb4iFEfCl2Xpy0zXgTgOrlcNrLsSrsjGEwJBbRbA4mJhQwtsvgRJe1FE -GTOIeS+6x67uGrYjhYDu1d9na27tspN5sxOePVLrqSlzsygj/SeD1fsXwbcpTxhf -oPMCQQuFc8tcJayHo5+33Cn4u0AhkSf+3WNObx9IzHElxbk19C7g0ZEpawVBmKWm -rW6tby/kNJifYBmXf7IdYieWHW9jAkEF0tpU7vobC/FHQ5II+bNPV7vHJASgx9C8 -hUsBFrCmNlFZ5SGJ3EyBtjgpj/4t4zmxb3HPbsltJQ9MShVf/3B9jA== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Huawei_bm626_dsa.key b/brute/badkeys/keys/Huawei_bm626_dsa.key deleted file mode 100644 index e9d4377..0000000 --- a/brute/badkeys/keys/Huawei_bm626_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQCCPuQYHOG31M5kc5H8M/GIA7jXeYdoi3dDpkRUeQBBh1LJSt0N -MmXMTMp7PoSvl7XtZOqn7tIbRqhrE57o2ulgc+0sv+APsLsgsJkjhXQdeVc0FUHz -BfR5/gbeQrjjz6zxd8QBn0XYS4TIYROk/xMwRq+uSsIxcpGrVWG+avrjxQIVALVc -6epdRIHJtE6j8F3XNIL2ZpwNAoGACIC6+qoj7GMhuGtqVVL3HvWcuEeUj63ECg4G -ohZrNGvTCxCTItBZj04j8W2Q9771tY8DH1NkDOSO0FDzNKcPeflwp6JGH6WMU8pu -UQos2HkOsZh+kTbpKePUonVgrCMvA/tAk/Bh5crvO6zmYlBwIpyXKJruFHEQ557D -ngeFxuQCgYB7ojEaYXgLraLwn0qeHJPm6DuSZy0hYiJDJJV/60Vzz4Pz2Q4wU/5O -0BGGMtAsaBRX5nltTwT2LXoe3MnRjdJRp2k0JJudUVdxd0Mu2BAsoWoh4oBCPJ9f -zKYiFrN1Yy9A69HQ9rzKBYcPvE5CVGcrfiv5MyBtBetRkMEHbNjnJwIVAKVv6Ocu -Q4T2F4f1grUvXjVWbCoE ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Huawei_bm626_rsa.key b/brute/badkeys/keys/Huawei_bm626_rsa.key deleted file mode 100644 index e8c2814..0000000 --- a/brute/badkeys/keys/Huawei_bm626_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZgIBAAKBgwDa1LyHnJ7NjTYkCQ+6EE+jva3Do4gN4i7lO/8Ti8J2SuoIWfwv -zj/5VqxY85DEUM8kEOLDLaAid9mbKq02+4Z1y+mD+dzmQbFSoxHEtyejSmPdgPFb -urCHV514lmcCABU3mFCx9bP0oFCUiftg3L7wDLnG5DXbtJ7abvN7N5YdnfbZAgMB -AAECgYIEiT1hOENocmFpbNCpJDZqZ/+mmwiydPpQ4SX/8zk0N4hTZP4fxZA47G2F -KINbNmuczUbOEe+MV1yo5UES7zLR1T7r5gHmxiVf28Z0Vz9UhezU424Zx8kN62g+ -GpaRFiCDgjVNijNgvwZLqIO9/rCJOS51q505I7iMDWJbJCQw/sLBAkIA/OVhv36H -HLkq2YRk+HpfCGX9XqYiRFveL5ZGIdotvozRPAIbkJJ+OvbZ3vHvlWnwidHQN6oO -SrvuXI6vks2T3rECQgDdhFHc/7MDx7RGLJc3Zn3+UR3Q2x33M8XoFlsSzu8mxuh4 -g9mWoFZ2uVSkBC+tQxPcgUQQ5W9cvG47zpG/0wQ0qQJCAPg5FVZmFKwGu55AqvKQ -+hI+ORDbtCqwmUbQEwpLIjcz9HMYBoJCp4+sl3CSu3xGPYio3dylF94W1AEZNA+9 -pHQhAkIA28htcNhCxX1fYEDdyBno6GuTKaY+FQdJVPzZX8/xeIfiH7CyMsDjCOjZ -EeMVatMi4aVf42PBGkHbHXvXIHw9sAECQR95LN+rY+GaB6kQFw93NQfgaRo5Eb15 -wxVuDldiVQgOjhOto3j66sXJCGb3S4viHMkDzK2W0nmTHlmPfgHNfI6q ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Innacomm_w3400v_rsa.key b/brute/badkeys/keys/Innacomm_w3400v_rsa.key deleted file mode 100644 index ca34347..0000000 --- a/brute/badkeys/keys/Innacomm_w3400v_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCzj2Ow4gPquTYzutWqUWX7ENRe1vBbqvqRIqG94Jd7lFyCrdf6 -jAjDf3WhcqX+4F7Aj30EtWQZ3pY1LdN15WklycJEhd6gQhGIreDkhlWZ3ThwsQDi -sEbT6lzvgJ5a8vMQyysuKNoQlN8SOWtLuJ1lpAJb+VUexSB4/RgHYbWeVUlLAgMB -AAECgYJJJB3oFdChoqsc/Dx/oaOIVu0i6qvCs01YpEI9DYi2+pKKWpTxBmylcbxx -NLnALhX6mxhCPD8XuI73/4A09GtVy/2z9Yq1+4OkkUSrK71U7s6VYaOipfXqdJgi -7TjSjcTuD6hUlRlM3LLD0pi371fEUHVhiIrBn8I3/AZVnRz06mAhAkIA/sHRBQB/ -t1GcSD4Ka0BRU277V9Rr5sVmh+JTCau582UIzdtQlB52LyDpunLzE2boIrlp2d2e -pkNRAsCV9E4orysCQgC0b6d/7CNxAQvg7FS8qcMsgBgRqpASAyV4KUvUMuC4tZQD -nD1ff2sWLqmLlgDUbaLEG6i5MAI5rITaieoMUDu+YQJBKn3DUjVCGB55lacbb8Yb -inIBZCXfFW+paVK7jOpiqpEYhdVlvD7UcYno0htoJb1NTLVmTVtkodCqi2351PTQ -Xl8CQTHrDej7GFcEFhg06cCEKsXd7kq86DzDeBNFQYBETx1qxrc0+1m3M5YgdLF0 -X2hlqHkeuc/58zOngd2/9+tOKykhAkFRcS+YUIrqfS9JhcowX3y+8YqXe1xCa90Y -EbqzOHIcqiO26AUYGg/Bp3y38g3xyswROaKF24+miNEm4PsqqzmXOQ== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Linksys_X1000_rsa.key b/brute/badkeys/keys/Linksys_X1000_rsa.key deleted file mode 100644 index f241e63..0000000 --- a/brute/badkeys/keys/Linksys_X1000_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwD4BGDq2pSt3leo50uK2JNyAKo7mxCJDtyjLunr737R33liUXCT -yzMnk54/7XPVm+slE2Fwtn9zsZEEucZObBOlWEEBsxmv9xjKBpnujxPtgOAxiDv8 -/7BlCXKfCJ/n0dcwR5RHKYqiY5pkpHJ5wa7HY+JPrRRfH1JUP7aVdBls9udjAgMB -AAECgYIfdp687IHUFNLWEJGayaLa4Y63BRLx8Dq5UOhIEBfslAcYG/DGLw8MREPd -JEh/nsoT6AOCgwRkLvCQwuiPZlKPtQSJlh/vWnDg5dMpQnQCEOrIY0QuQ8e8X0ru -PMf/vRoDVrArNwK45UAtdUj2znveDh/powhHjdaKttLlQYHAQzphAkIA/9gTgtvG -kHArIS24kGM14shq8nyIFoEmg2dxLSnd73m3YeNFvRpFioqTRaT3f4OMXvebVekK -+FOI/udj6zvutBsCQgD4KxS8Cn59SVTZhzVP+rb66DhN80JFNYOAoPgAvHDdSsWK -8wihoEI9VvlEmXDeY9180tYiZGbO3r1Ronkl8k9+WQJBNeJhgZ8ePA+T1eIWTDrI -/6GsfLPjybGb1fM0cAmwV56wE+rVJlXhLaOwDuwGwJGJP/sweTCjAdg+M0myzrmJ -td8CQgDr2aWfACouT58ADOux0whLdJ7uFh4JQE511IVdzblF1mXfJBIz6OaK/Tlw -5JLMCATbOFSh4uGCAVXTW6HfWmYKIQJBOZK8wpQRbXnybYsdctMvIzpeQ0hKNB1G -GkE7JG1YfcZgbPnBXlEUG+wBAme34Lexz8TVzUktGGDuW7sS2IdvXV0= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Moxa_6150_rsa.key b/brute/badkeys/keys/Moxa_6150_rsa.key deleted file mode 100644 index 1e84a9e..0000000 --- a/brute/badkeys/keys/Moxa_6150_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwCbYb0A2Hm6S9zEWopKGeno8ceXENbFD2eZLO0CVLZXBAUDubws -ipQDv7KxrSNW/5iUh24gYmIeLIBQ38YJVylAP/8bvuxSv7zc2rqgXsKDMeFwd2vZ -dcj/3ZldEMIOAZhUG5k5UKqw0YU9LmkGa48tPGmbY2iuTxd2iFBCUa2r81lDAgMB -AAECgYI/kUNNAsL+E8YsMGxN938J9Uw1wcZTlUNJV6dY4kY8oCDbDtPslv8J75WP -+eiw0V1fOm6z4fwJIySVsY6nyJhQN5sXXxj7ozezAQXD+DbqTFb1tof+P6jbD8iJ -+DjKGB5TnjnQwtMLBCvnFZfutZGfx+eCh6z8qeFHBCyYRj72lou1AkIAxPsWIIks -EypovXG4qNeg1HGusSq3lsTF+SlOOiacpyqmRdDK3h/9WJwerLOT32TqbpGItdc1 -kJ0jLtkfVZdGdCUCQgDJ7+edWkDLzkCKlxxHWmer4qfFl+vjShnDfCRQ0VCq1TWz -0OWWgXRJs2dKvGvMjDbjx6QUDQq2pZJDCHUUsd+BpwJBTNCxp0x24J54K/BuF4HH -GTGf8zz6TRQ9M9YUyH6INnReVPmAMzD8ZhKzr5tz/fjnpuigDHF5VQTCVKGdI46T -jfUCQTA/AAGSWEWPDMHE7tgsUznTR0Px16KRNqyb5qXrINQ+MY7QiGN9f485hkrA -J1MgwA+Q+jqmpnssBAXHIq/x/AvNAkIAl77E4ErEl1FwbvO4xfvCNmjyfH0l7V1u -87Uv9vPfwqAcrmhgTeywd+C7GcpNXgLptVOPb3XAk2gZzfXLewLrzeY= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Moxa_ia240_dsa.key b/brute/badkeys/keys/Moxa_ia240_dsa.key deleted file mode 100644 index 2eb54fd..0000000 --- a/brute/badkeys/keys/Moxa_ia240_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQCfew4NoNvEofEzw9LUjXIu6NCBUkgVdK8PQmn6HU/ixs1APKyp -MvwMIZvuYXbBgpnDHAqjkM4JzDDk0pyrTtCT6ryuOIillV4cwEbdjv2nzyiyTXms -iMBrHPfNtmUPDZB2sSoqXfSI5RTMWSMXM5Cd+WdGoa8g7WaHTZQNhKli6QIVANZq -/Gr/QLVELfQ89apfpPXGBUJ9AoGAaeeUGj1zZyS920KFhBg1gOkxH1YSqGtFHa3D -zNq+swm7dZPVnRuUGTt9ZIxlVVdrzzUyY1jvIJBAF8/XCVaiVB4HEAhcAZQTttkt -MTj3DfROPMpb/Cvi4JAF1zeLK7YwZvaEQb4j/0CfqZXJacS6ZEVQm6VpQfOm0z0m -vYgbjBICgYAaC8xgchB6PKuBkESHeBYt9zAK7MdlMfijl34wVVP9bJka00CEFKX6 -Qho4iMITQWDvDvJjYh9fCftoYvGSVDg7LyaYw/vvNgu9VKNj1sdru/Ts75qF0EMo -XQ3Kr7dXL15gh4MLTrm9oF5hRTBip6qEq2uPrkA8leDVe6YeigWdRAIVALHG4/bu -QwtnP0OPFQYghR4+o/lw ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Moxa_ia240_rsa.key b/brute/badkeys/keys/Moxa_ia240_rsa.key deleted file mode 100644 index dd8f958..0000000 --- a/brute/badkeys/keys/Moxa_ia240_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICWgIBAAKBgQC1bUUrwOmIqXXkFbaxa634g74qRqelhkK90Ki7BmjdJNi1rkzD -YD1dNMl+YpSVyzzMWtFevMcDagwuInC3ALffiKnJgwrmmHBnKNEjbktccjFw6/8V -azse8pzhEOYSYL8WQAZZ00Lrn5zK2jWfAHIazPjA7/eQ3N3SB3OYk29HSwIBIwKB -gHc5LXSGFc7Vwn/xAwbgXGF7JTG5Ztp8zMXg4+idz+HHwZv2MnHC5n8UD2GufuYX -13752gsOVuT8i6lJ1QqLcYMn8uICLsWixNJNAEZH81vL0J2Wfqrs1hYRP+B/2wLX -KgLLsIndYehEZng+099XMC0bHTOgnwai+Hfb9l+0w3UDAkEA5t/VnhWiB9aj8XEZ -ZG4aBUo6KfRQVcsLlHg06cwSZGPdqR9z+CDXEafB+sKkEBpbuaC9ij7yduQuoFlm -ouiD1QJBAMkr057anWFButh14d8oJgo6c3jatdmOd1onpG+rVLTuxQflYfmNF0dC -13S2IYHtCyG8RYeO+HkbXFAHc8cwzp8CQQCeUFf3X0qJCDXnY38gS3+OmUx0ikW3 -LCUym5H/Z10Ro5Cue/fAFoTY6A/8aDX8adh/SaaKrtIlo8gzcIDkvLIrAkBndaAI -jbAGIc3OaILKia0p1Ortsk7k6i7ApqxWr+Jrrf7uHjJjFVxtypqxDTXNyl1/EF5F -tz31JAOW30LbeC+/AkAOsL5BjSmiSeFx73m+fMnON44eoJo9kvmL3YlB4YeyWG7K -mN8axofazZHJlLUCUFb58WW5pZKM8wXHE37tXDs7 ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Ont_g4020w_rsa.key b/brute/badkeys/keys/Ont_g4020w_rsa.key deleted file mode 100644 index 73b2080..0000000 --- a/brute/badkeys/keys/Ont_g4020w_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZgIBAAKBgwCBKHkkEOzOs78wAqvrusZmUJUginRiukXA8YlJZjyOY3Po2wxr -Mjyt65b0diTz3fPuwaF2ug0giCYfQ+cAIF8ohzRRiLAVOa3ukZl5jF9soiWsEjOG -1NWv2lOS2Jb2bhtjoIJM3y+avsg8yQObqUGzHP7thI3EVheYta0RF+/iHBzfAgMB -AAECgYIJ4aTTA5B6h/n6nFm24UVIkqW2JaGd5A5uIoyUVwQPCMDXcdrTYLaPv9R+ -w4M0PSjqfVnzEWzrrj7saW+E7PUazpEdB1xLc2B7Edn7HToBFsbp7KB4oNlcpgSD -ZcbwTH3ieEMctnpnmPVy7prvWm274mH0gV/NoeWtrUlCzA8XGcRhAkIA1YyRwg8P -3hsdu7lt4LkkjYYY5Xd3YDHe/mRsXyattaXSB16DN4fmZS7VwZ5LwPprXA7hZ3vi -EMeah1TO0T7onfcCQgCa1UdT/p+nrB8o9ZC0FzdW9rfuhO7esf1Q0f6h53iaE61Q -gQQYCA29Og+M12lK5DBED/4eD//bIjzMEzgsq4feWQJCAMCva72MVab1FKkUMZ65 -r8+7Fa/HUgGMPkeQWXCpt8fVbWOU1hU/HJZj4iAoMvZXfpO8IYp8b4jwcfB2h36q -or4VAkFnsQHt3I4rinfrxFk+YnXrRZt0n44hke3l3Fy9LPl1pkvhqCWHuo0I9wNG -/VRElYFFc7hphamBpfI1cYGjWNTLMQJCAICRILA6fk1BJxAKjcQxv6i+f9mYolXR -D0Knq3lESOb2eJODEl0H/BXNAfu8qwFdrHZEM7VrBHeQ4eIpjUFXJZ/H ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Pace_V5542_dsa.key b/brute/badkeys/keys/Pace_V5542_dsa.key deleted file mode 100644 index c0686a6..0000000 --- a/brute/badkeys/keys/Pace_V5542_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQCVzNKLKMZH6q8xTaMR+JTRuvzcWom7LykjKrj72vtPiP1w0gHv -arsto6lrlT1up10QRqnzwbuX4VRYnz1Jubk1p/h5TmIYLnFXkQBxjWwMT+/bmImq -s8OHsIHhuhA+Qt4nkcwIzOzI/li3XOMiP7q987hRSeSiIv/W6/R6IhL0VQIVAOtt -A8w6CBdrqMfavOy6ytt2mST3AoGBAI4aXycOAC4aaA9JrQWpjuohsyOxUJZDZs// -WbHwVf70FM8quhxWlg3gPnpTIES6pGApru67GEYk1zlxtG6KSZX/TOuk3fiM0lHF -NvwBCxN5LCDMqQG+3RDciJGouo35qouZ0KyOHogJagDJPnNkJLmBXfol7G/WZzI6 -Twg/NRAtAoGADmYHtam0z6qQH2NChWIHAG7BCk0tej6E2O+iuWO0kTLCiAg+DpPk -dwkJQl3BvnxhlbdayfxC6+cBzee66yt9L7opXpfTT6fBJ8JTkHt7mq21xbGkcHQo -YjJhxkZ+xUQXGLhdZb3kQAOLHXDrVI+uhmGCjBPVnloeMzvNX+fq4GoCFQD3our6 -NlzbWw3qjEap9XBVH1AcTA== ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Quanta_LTE.key b/brute/badkeys/keys/Quanta_LTE.key deleted file mode 100644 index 68392fa..0000000 --- a/brute/badkeys/keys/Quanta_LTE.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwC9P88UlGdb6WIsIcyni8zh4zLdrSORieNeZNXtDHiHzgs80XQ8 -FOBBaNBTBAib+GX6V8Aixvmh315+H6xyb4fQSlicpJ1lq4k7pKsrXGgdYS2FTPrX -A8YG+1beVvWeK9/8LjXAdZCzwE7D8jOSPh9dw0HIuPQhBCfxE4o/WOvYwVMZAgMB -AAECgYIByX/YPQgGVkt86PNMrD1qrylwbjWBJvUQk8Fw5/6d4rBYuk4fkJ9nArk/ -5XB635M/9FLuSSR7lrdG19/6Iys8SGlEKZUtSrpnOKheuZLClowpibir7TudHaKG -TRJqvOgCip6wurfu6m/+ZKF/Xrv3VeMOD9oxExmQRFDi8BVC5/WhAkIA9SLigj01 -xEdne2+UReA3/sJ3YX9RuyG5bSRXW/iwlmMAGZWsnlNeiXw17wqhaKfEd8ZpKTL8 -glH3NKRNpQnLXXkCQgDFouA20kCjNeGgtabxuRP7lndn8/4jXR0/HvEVdZIki9Z5 -gifC5+gxn8rKcyTSZZmyMYGwLQsN3Z9LumQT/DdaoQJBGKCAkQUFOcrSopv7EWoN -NhLjW3AnDd29ezGDdUHuu60GfYuD5AQMI1PPN0yiGpmAK2hLeFAe/hit9SPsiQAK -5kECQTP03rbqzT1a8+b4+lt/yWYRp3B3r28Ckqa+bqiykOn0rTyiX+uYZe1t0bUp -UhvRw/cZlruHC+noQnF5Hcg3PSIhAkIA8SD8IMws3TTRDwDEtoLdcN+O4Lcq3uNv -1XuvwBtjZzqyy+YOB6Cc2DXYOsBi1iJRQeqf2lrlFrGnTCmj4Ey6IaA= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Sagemcom_2740_rsa.key b/brute/badkeys/keys/Sagemcom_2740_rsa.key deleted file mode 100644 index 8db64da..0000000 --- a/brute/badkeys/keys/Sagemcom_2740_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZwIBAAKBgwDh+jRrA8DSW4Euj9XNH4xVda+hp1E/vxxqkLF1k73SsUA9l+9z -3Fm8kvefX7H0f2Bcg3t9mqy33JtGuVqq89S9Hyn5tmSfSBVY/J0NM9WWSqbMw6GU -wCvFZq+Uoh2furY7dAQrfVyI97kufDnRyDbJQNnn8oCyFaWyd14d2O7wDrCzAgMB -AAECgYIKHd924wYg5MPVx860jjMEKG6ieBElH3MwOiYXs3OOGS1dFI49y6Gg1ZuM -YQZggctYmPJQXzpYSOISun2apavig4sWuPHq3PW7NAcv1Siypadd6rO+NGIIzCnb -AOosURncKKyqX2Ek0kFyL8bTDECTn3WBO21plzRS5bM7tYW2AnkxAkIA5DKadRxL -tqBCFqdkErMyRKBI8/FTga3ASPLxecPViaSRgVY+cPnbXpTSuG0P/ty3/vHBm8fd -+bRsRBIXq58M2P0CQgD9glnUTjVhrIjBveglU0WUS1xYDSn7e8yPiePXlGlCd5oE -7EbQqujZ820JyPdsHLHwtvBFU+oGM/asVVSYsMt3bwJCAJB4ZQctkdWeIhkbgqTA -JFwEKguexiJ8cRb+D9jqHb/Vm3UJt+BonvSDPeEa0xykeiyCu/M3FxZmnoB9/9Rc -jpK1AkIAmO66IknMClB7b+WQ1nOV1hBgdP38Bap5jV4yBSuTFHXyhGXFkryIHHOt -o+mI2b+12PGDoU8uLu/KL8yKbFK7bCECQgCc7A5lY6odxBm2EoZCmyaT4LaUzaoW -zOHvI6sKcC+1C8kzT7XT6CcHdjHBAeN+m4P4NHaDRAnW020i3UYCR2+pRQ== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Sagemcom_sx682_dsa.key b/brute/badkeys/keys/Sagemcom_sx682_dsa.key deleted file mode 100644 index 493ebc4..0000000 --- a/brute/badkeys/keys/Sagemcom_sx682_dsa.key +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIH5AgEAAkEA/UNXOgmhyXWRcOBZLun6hspiuS/Q/N/Ku0stYrD5kQr7vTcNrCNZ -DYDnCVFfrC6/xyBQGP0fhUBpRUc7eoJpMwIVAOLUu3B5ClDwieTcsYqmu517ABBX -AkEAgTfPLQcVxAiqkTowSvA/tVyXwCPovs3nBBK8S9nFW+ZzGm9oR2Rccrbfuln0 -AkaoUVmacGBxvD+z9qhhHgiNjQJAeBvdcIgYEly7EUl0bb1KxTGe97hxdM31vhJn -zGkgrzlyEhf2+d1V756A0mArKvnkq1MKlaFj4vxnqqGVypRfngIVAN/UwBqwgIP2 -IOnHijeFIH2NQF7G ------END DSA PRIVATE KEY----- \ No newline at end of file diff --git a/brute/badkeys/keys/Seagate_GoFlex_dsa.key b/brute/badkeys/keys/Seagate_GoFlex_dsa.key deleted file mode 100644 index 3871c8c..0000000 --- a/brute/badkeys/keys/Seagate_GoFlex_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQDkoYOnXcJmvAShYB5LmN99VBBa5PuTNmeLQkM1z0ErN/MiZdlH -p0NDDssPXetMHV3tyiAfx6n2iD7E6wD1NkaVqRJqnMIUhXYsOsEMHN4Dq89R4rRP -gXUZKv5SxD8nu+ZpiCR0wrjYKlamogc6yY7OqIXozP5thVJ8iSn8XAVr4QIVANnS -7+avMkcsNNkYxnH5SzvsUh9pAoGALi2ywyhDtjiWJJCKTfPnreGJzJmuwtoR2p2L -JntZl8/rPm3Dqbi4ZWs08leTewpW8PArPrqXT1ThXRdBo2daECU8EDSx5Z4AWKBZ -zRwtDaYOp/zA+k+INoc8HcAutEuzS6fI+wjNroJ6Oo6HbLFr+wKmNXnW1a5nqFw7 -cgniomACgYEAk5ZKDyvACL+aB9yWFtiTu5pP9bi64Mj4kjLf2yg1zM+DKevxHaEE -3ZYOJBoyj+Y8HH3DZBPWBIb2KgrEFoitHco+QBm4QaQj3Gfl774zV+wmKAnf2iv4 -N6Ke/7+v9Ymb2hVAw2G1+bIZlmnuXy7kkWZwmh/3vtvt1VswYvK7p3ICFAaImJuy -4gGChIcehXfwjkqTD1M6 ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Seagate_GoFlex_rsa.key b/brute/badkeys/keys/Seagate_GoFlex_rsa.key deleted file mode 100644 index 3c49b02..0000000 --- a/brute/badkeys/keys/Seagate_GoFlex_rsa.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAr9h6zIROyA+A+JgIA+SAWReL9l9HaFSBhJNSW8or4EMzIZz3 -GrbJVGD5/mJfMRipUfH3Gt5MTPN682n4e/VkqPdsQWwb0RC5bFiMU4KwEZyuYrxB -WoQv0UMS7V/EWh4rypLtag99x3Oid5u0uVS/5y06YLV8XpHO9slKRg5qpEgbXAqP -o0C/vIImgHUxOOTNg6yOIuRHPqLhUeN03BjLwcgliuMWvUDkNxwf7BhqudQnhmHA -6gJAbH9PJn6ECxzTXpHRVtLadYkCwOzq3V4HLAO8FmpzNfvXFBzX3nHeNtCvgqyG -gnr9YDosBwYjzubmRKAbePweCDzrii+aRsKlXwIBIwKCAQEAm7+9OMz1UhxcSeW9 -9NGzghwrgnGlpYytOujii9BSv1F2bjqRuJNEmzFSc5j1Oh0oQUQGwAa4m+2YyPd1 -r57Vf7aTFV/A3b5bFtIV45ECWL35iqa9iq+YER4mtP0M/1yNL8P22kg8NFB59Oj/ -J864mY51iNtCRSIPFRivf+DiHHjoVz0Zzdd+bSZsqQs+rWdDidraRvEcsVit1jIK -tRc4K1GM/m8+QzolV7JF2nu6JNC3eeF1HvZt8WCpiBl0nx6oe8kHt0zwe5D62Kbm -Jb2I39stqP602V217r3p8jTZf//yYC9PLUP1ktVHFxY/PXDzrCS8fxuRzz+KQnik -ZVZ4SwKBgQDlu/CpaTxVUxJ9h79DfuxdkArehEOpPIGN4EufwbXyVAksB8UM97om -nWOotRegW6jY7BtPU5AYUitLHK140zA/TXubCvtd69dR7zyQA4xR7fczFBj8Y2SQ -Hh+r+wzepheiZAMM2i03NvhMdRoNTebSnX+5ZtDEDQMkTgCJOBuafwKBgQDD80iH -B0rsMERHEJB1ac4r9ORmmNq3a3LhwdLyTe3Lj2soxvMFiiQPT3uTs/D4NJp/c8PE -EGQwGlI8X6PJnQwHZEwES1dUQD+NWPPrpvN1i6F2fVLss+Mm+PU0RYxtGPstBKWc -Ip3y9xdE+/xCt4CM7QdsOFaMETxcrxI2arDFIQKBgQDYm0H+0O++KcD6A6W0qt7b -35U4M5BAeuCNFU6d8Sfkex6XOoaXMrbT83s4qr6BQHqgpBm/0nHrC6UpkBFGCPL5 -21dJClq3o9mlBiMosuNjMNMhh/pNDUGPMlhgUxrDTCTpkX84AOjNqNt7Sda8FkAI -aJz/Q67ku8/DJPkw68JBNQKBgQC4wM9avburffc7sIg0MI8wwlOxMQi7kTHNitzV -1HJ+GYJLBLk/vMLp3ToASpK+IvgRxOvHfStDTARkzzQHPE0rinOPBTUU7B6p14bl -gCdgMzHWHmQhAWEkvtiQXtTb5FqJiAncWyc1ieK9Yp1jedD55sx7+pq+k+h0pREr -/jGj6wKBgCWGAnfrVS2EkcZt46cqSkKMO+NlqiIjmnZroJwFLx1oY2dae4UJjsYV -95bGhcCOSPc3Jy+job6LD7iYoFUFLnRs0y6EilRQoG6OYFHg18YrP8ix9RdY6Roz -MzHb/5e3XVMcPBJbu0HHcOZar7RmHtfMl/XRHTk+dQXj3hqfNzQW ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Telefonica-de-Espana_rsa.key b/brute/badkeys/keys/Telefonica-de-Espana_rsa.key deleted file mode 100644 index b4e8d77..0000000 --- a/brute/badkeys/keys/Telefonica-de-Espana_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwCKj10BLi11/oSbukFArKJZTXvBvw+AUGfie6fdE7psCNwCLM5b -YnJgjQZMP/VOhJkxkA539e2mM4fW9U4ECAUwgvlF9AZGhcmn0kF0jIjMUDgCV8kF -IS85OuBU/ayyswdYp6bxp3zn0tGAh0Ty8ikf7CgWU5c+PCbpygbBxMDfZM9PAgMB -AAECgYIFSRq/JMfPLHpahmxezzcSOQZziZpJxsdvuE/a+xmtbVkXLCZjDgpW7IpE -9luhNyWw4lHq+ZKrOGQCKzFirfuksgvsGeHTTGSey6t2KI0Xn3amADLGRvbF+lGK -73UpytVNVrFWjSE5rRFYjj4GP6tq3tPQhvnhjBCobQ6XUPWpoFThAkIArMhjsFGL -EOQmvEPP9Q06C1/GB8L+tYMHMlSgRQBNaTGevoa0urYpZdxWUAseWEn2f3PzjL1C -fZXJ/H9+asa36u0CQgDNS2hoRJ/MghqzSbiO0Duf/gc7EM39galIs6rPMpojqht5 -sskAk1JGnHN1D2KYdeIh8SRnBROmaR+uD4fFpwMPqwJBM0fqZntdNe8xG/FYeFer -oZKUWNtj84VnDmYVh0U2tID4p32diEjmcof5yhnysKuLEHrejyfg4xsg7uL8Jz7B -eWUCQVsbB+6d5bzAUFEYeksGpi9OcK8JuiCylgmpkjf5YOZK636KlSEoP+8OJz7f -4QV/6ybc+Sau3hWPuXtpcPuKLpWZAkIApF/sZxeLHZeijSCLHRVkbtq4K5T+BqsI -6d5XuMiu6Y9Br4zuCchnmJayb59YPIkiKAXzAAeVSLXFgnfduhp4EA0= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key b/brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key deleted file mode 100644 index 81b74ad..0000000 --- a/brute/badkeys/keys/Tplink_tdw8960n-V1_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwC0frfsMNpuhz4RDjRpzZthkGs1V8X/xjVhvmN4sySasHL9n//s -xqTyMdrAWQTVMCauJDJTweWjYRUoHifxifC0P55B3wvqCvr1BPpekNfvjRVZn5/N -m/xVU4i4SFZkHwmufyyzJgSuO9YgMsOYCCe/wve8ulttUrHxHlSfeexnG/A1AgMB -AAECgYFSqrcSNk3U0Zxbs2XOD51a0gpIJCtLOjy4x7pHTcU2FBqWjvFoQJLLWooW -Dq9n9OXCXFGwit3iNlS6SxWASu0zoCitDgfzd6gW3mEvjxbGr8YVq9p9Qet2zA87 -K50HtVuj6kAzCbWXY5zw3REalN1TOjh6Mi2qpJxcLHAn4qHFneECQgC6PzhfR6j/ -c6nXu9LmnFCjj9Pj7jMBjEJMtz4e3LspdOv9MInbi+vvafqH9vsgMqV7Oh8U4pOT -CzKV3KiGodsw4QJCAPgYBKcpwUUR8vJ7k1AdsA3aSoEd8IwKpAO4fKMq/GkECDvQ -lLfcxnLRkBjENPHr5BK3MH8QUnA40FkPnxkCY+XVAkIArPSgqPqnGfKTOuAVTkrD -J+Ec6IH/o+RYfV19trNMq4cEz68Plm4tv7svCKx3MMNXoUOsMXznhpnTdA/iAIS2 -RIECQgDcp/gTeYbFWOZ9Fr36Jr18RKe5WRimZZxlFsP8F/JxsL1l/ekX8suqOYtx -C6mPdd/faYE/shOwbkeYvtUhWEfjlQJBYMtzWoXbgPtf9QRbzd5LZ6ZUbvHtZk5q -LZ14Sw7Vj/mNXLJ7+SISx5QddvbL4PDRvca0pr7PwpVc6R+TxJZAVl4= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Tplink_w8950n_rsa.key b/brute/badkeys/keys/Tplink_w8950n_rsa.key deleted file mode 100644 index af03e53..0000000 --- a/brute/badkeys/keys/Tplink_w8950n_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZgIBAAKBgwCPP9CoRpVtXwWvZXhqhWNqREIdEcCrtqI+XdVAuMH43MLizZUm -IrQHRSTCyEjwTnFJ4FudjL//nkrdPuZVg5dDP4njXb8nxAk6JC3ULDCjLWsCG2vp -zaPi3sxGr2H6AFTFNB3H/dyVlLEX6t5tFSboa45SnQF0vz2OE8dugN5qw1+NAgMB -AAECgYIJfk3ufQEFpsy6Z0Pf8VeZkQz9nrHRX3Dop7DMkYH/Y2gB/mRiSt359l3d -j8bvsA8neXgw3IVT5DJrb7H1nFsvsGHN/yhHFD4tM33qOY2NKRdZyMRb5jqEokNP -/Dr4z1s/rsg48waftgriLII2EbukakJBnfEN+Xy6gpXGJkdbYt3pAkIAk72QKxxF -MAAE5sTx5OPeZ7OKbGUV6x7NRM0x9vdvkKv4qdGMDa7exxNgHae3z14mnZ/oUPXT -mRaEAFOoqhV3UckCQgD4N8DNGgI+p6QrmC5ybh9QO80Yh45z05kOChJ8JSFgeC+g -PnUEJ/bgnlQArJy8g4p/GDjIfXlTvv7hqL6QcyrhpQJCAISWR87YpSLpsXxk3O1c -rpkEYMLciSojz3XibOfFsaL1IslMXFNfT2D7e3PBs8zvItqsynH127r0oOwmKCTT -9Ow5AkIA9By4072BeacbUPhwrX9Z8mltsnMWiRkPzHvhZBCMO7jXdewW3wiCxrNP -f/5Cgy+G1Km4poyn9tG/D6UKDTwGbzUCQWhNqaQetH8xFuOJePeZpGbGZ+ToD5Ip -bauSp6NW+/7H1UCYrmzbkUKey+0mBHjvMyGabdSd96zNRvESbi4PPW86 ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Tplink_w8950nd_rsa.key b/brute/badkeys/keys/Tplink_w8950nd_rsa.key deleted file mode 100644 index f5dcfec..0000000 --- a/brute/badkeys/keys/Tplink_w8950nd_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCAnQcDza1fw/2rHrXlxvU7TPhjBTQ2Dxdy3R9jVPfmAf5l1iGq -30oSSUeKj67+gExVY0S/3gvrYmR0luAtGC3VYhA686OH7BijWBFbo7fzXZt2L0pM -yVnE17+XQj8uEOvuLhmFunfha8jwTD5ei1UVcVt0cBBMAFqO7VUCk5vuA/IdAgMB -AAECgYIAkvLXkQDvlvvYWiw8qM0qZRcMtha8EQgtQpBjzJrIo4SjpreCwDlf6ITR -uP9yclck4GsLoi2ScN9657t7aAq4U5SYCt8IojzGloQPka+363Xsyfudg518L6ii -V7DUaL4yTXKeP77aGQyca+xY2NaJpZpmo6QOeRXwb9GZueIl8V8xAkIAp8ZjETOa -xBx+y7E9+1W0biU8y7U5ONxIvum7gMC6ZIbab27LrG8Xn1CsFoOiY341/U+N/CJJ -KhQMJ267WLThLaUCQgDEPsUEilf5OEMK87LxCSfIArJABFNNR9S9SQev5g3LMV/8 -oAXmRxeXDP9RoPvcmUv0d/3qDZWSC/B2LlWHNE35GQJBPjaUmdZW943Rftr0rvRy -+b0ZNDi5RVp6J0Eo5G+TLp/K9DNTl1eCnPMfPSIG33n3rz0G244jL1VLxHuNQTGh -pnkCQRpguYivs71684AAW7gMW/2FZ0I1kqDbm9vJejAJgychvt4tAD4ApkoWZCcr -tU7kiBIXItkX9FfKJQstlS90N1rZAkFR+gJ8Hmi3VgDjkYvVWzqrSHyetIlk+uUf -CXZ6XlnU7exvgiNCtUTQoG4yfi/XsCPSekxTohvzn6JQgMl4NsuKxQ== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tdmc500_rsa.key b/brute/badkeys/keys/Trendnet_tdmc500_rsa.key deleted file mode 100644 index 81791be..0000000 --- a/brute/badkeys/keys/Trendnet_tdmc500_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZgIBAAKBgwCDK/l+dBrBXBwyndkS9Rqs4kekkuz+aRdBqMXOqs1gkroAUsst -T6sUrdnRZqUU6jnA3iMTBateRmcfeX2WIMZ24wlbwQK9P20RUomOfMcVEETh/E2A -N31vhXHMPiX0WdoawRKR7vAjTuSzPorYj6wvrVROQAoXad2sTKiVZ63JU7GtAgMB -AAECgYIS9gzbMCpb8BYtZO7nlCG2gwErM4fpoMRZW+24Ga2Ve7BCcrVnB+CpnRXi -9K0BUHhnvlSgn82tU4z9mkDCrlsx/RsdM64Jc8lyb/JfU/2nrnInGNUBtFtXkA27 -d1xu4VosMysowx8I11wegXi6d2GU7j5DzShev4k4Udgy1FjoekHrAkIA1QUzphsk -lUtkBG/vxprixABBV4sW/t/2UlAHKV4ZYCMDY9I4bdU54Wr3sxGlWJ75uu67ef11 -chj3+ARecl5z9mMCQgCdoy5Gnp+pklyWuxH7NAKtR2rV5OBePmYpe6v7/9lbL/rF -dp9QwoMvha73/WPmhSCFxFQJ9ofNmru6GdLv3dnsrwJCAIlcdVWjIw/yMWh7Fc6n -iZqB36Cn3Ag2OwwQ9s1CFHLdoQ35PNH2MQCejWM2+bwPp1FXKCUdv2H3n86aYpy1 -M2mpAkF7yZClVBr9Bjo/A1fzc7xGZja3EKxAVa/UE5HSqe79dIfwWF5zBMwPnWLw -ysKQWypW4P09daLfCgsw8OZ8BjMDcQJCAIsaP0rmzVvuL+EzHLozTgFTJbQ9HJHQ -X7d0ur5i71u5KHRdzi9IR6a0zxTAvMuP7b/bUc4CkvbiTcqTUG5H50zp ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew715apo_dsa.key b/brute/badkeys/keys/Trendnet_tew715apo_dsa.key deleted file mode 100644 index f2ec640..0000000 --- a/brute/badkeys/keys/Trendnet_tew715apo_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQDApyB0PjfCD6gSmDKWJaf92segd+0vqNSi9vHlFQUvRrVf/qFg -cG15t0cwpwZt90NFSqRZGwhsxUKKnF+X4SFyH644N5jEvuMjUqhZu7/t5Jn8o38H -HNqwScxVkQBaiW6Dx9rSrW1F4EyOh6XjrJf97bY9dCO+nF3Hv2Pul30GLwIVAPyL -nHSQF9TxP3YAfmvS8f1rc427AoGAGPxbXP7qPgyw9Z3bHmkLpUcXHcbQWDesX3Zx -xqZZw43OCuglLSn/Zq8AmcQrUdaiZl7QFZGrPxeL5rzOp5iABX54dlDpPjr9dLnN -clgUvbzkHY7G3Mf8KyGcx8AgU+4lzeCd621R2MhaM4wt07T+inASWaJQTPZEXxqM -o0ng99MCgYEAgRZVh7lLwDbGv8sJifQSKwxBpX8r7at6pcbH10BiT23E7CmkH8q0 -k4oTxilS5cJ1idlml/7mTGz7p6ialNF3QK6tIGQ4BNfh4EOluYHHuw41Twm5/58e -VIryd3mhv1Fdf4JkSQQUxrR2oe1HDdJrwmRSFnR6t1ldZvjmV4LtMPQCFFi2jrPV -Nici5GGOwFmbZ+cr6Qrf ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew715apo_rsa.key b/brute/badkeys/keys/Trendnet_tew715apo_rsa.key deleted file mode 100644 index 65239bb..0000000 --- a/brute/badkeys/keys/Trendnet_tew715apo_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCaX+klFFjH2SREDCqGtja+6IwCYlFK+jF9XxWPEfW5w/fS4D/x -Qp9uZ/4GayFSDCx2hS7PwJJIJKpm3blG4Nq1hnu8TR7LyMZAFqVF9ezSRNLjEDSi -Ckucq25xlmX2xgVxw3rvxDkQRIhl38uu4QLy0Vl/f3tEi0+dfEBJEKSLlrR/AgMB -AAECgYIKFh4rmAq89AkUkR7uMlWdX6BWP4pENdip8l58PJYrjwxQMOq8nrfABPdp -//HrZHQ7QjRuyoxEPnELy4zhfquLlKVTEqzqt2bEFZNMJcKM52bqnj0v0vBB4V8s -/COueWJKbQKH0lMMu5KfZo9w/cOkAjUkHmV3ODhqYFV+Ai20J+zxAkIAoY3kF7B5 -rMlqvoRqqU8W3sjQ/mX8rc/0ziqu2bo2nPAbZklksbMNwIBHoxXpuNgOrnYIOeHu -YpILe7O6JWU6KWsCQgD0n4YVfdDdOxrmS1hnlTzKfakLEOF9d3tgKnzZ66q+fSea -4KwfCR5tUu4y6Enf8I+plSbPN/H2VeNS+JnU+zgCPQJBU5VoxETevuG6o3U5Bf3Q -VFVLo8M6Vub3vk7hBe7M4KdtVZ91RGbiH41/AsaMlMDb37Fbki7tOfxbipWzIjPc -Qp8CQQ/izDJZGVdEn1qVSghwCKKdxnyRfBNJzxlPqQv94fi85/WG4aaiUPeIiy+D -JkkEtk+s//g1CeGVck3RFyxdpCWBAkExKQanlhQXWoZhpntiXa7wpOEFxrJvNDgH -Nbi6Dl8mpXPpR6vL9YfqTQHXD+clNjijn0nS8lnrR8H1U58fivfmFw== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew816drm_dsa.key b/brute/badkeys/keys/Trendnet_tew816drm_dsa.key deleted file mode 100644 index 65a8638..0000000 --- a/brute/badkeys/keys/Trendnet_tew816drm_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBvAIBAAKBgQDgiv9aWKPNJYUJSsxrqSVi9eyxhilR4FYDFryUF5xLPXxZHISo -PkJXD9S5kp7PYnd173aTDUECIFaOdVsnTpFKmgd9oQti4KEpYGvXyyE5W+Npe3J6 -k3Ei7qlkQbpoKx2Wl0VlXXmsm770Wl7V+p/kC+b9F0a7p2p15ddhGYyK4QIVALph -fzcSINDga1pc4X4ycjdhsKDbAoGBANZ71maYZt5WJBO0JSuAiFEhbGfbW1aqrpVy -mKhznl1vRh7FFok3mnHmzc5Q/rZt48UfRct9y1DfzK48Mp3hUM9hidOOdxKhLQyF -9f4+nz2yjrsQoQZkid7BpEk5kybciMjHBUiKDHc5X9K7nwYl9cu2i7WOjIERqsak -XJ9pKqhiAoGAOjZFfMY+Gd0mUrsdEIkTw3lL6xAf9V7oJ5MJ1eFnS+ekNOGTAFdc -tfEUYNyKh7WmWwWzfLMg69qNFwtKF0XNVKhgbU6b8RaEC3djkLa0IxmKWDW473xc -bP1QprJvRP/kbNl/639xHnpnI3bw3pa7sIJeFCYR3WmEyBmvhawlJy8CFQCCy/J7 -FMEe5+zliGLk7Xd604Mzgw== ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tew816drm_rsa.key b/brute/badkeys/keys/Trendnet_tew816drm_rsa.key deleted file mode 100644 index 8438179..0000000 --- a/brute/badkeys/keys/Trendnet_tew816drm_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwCAmPVBs6DX2/2G6NcLwFI6jP055kbQzxGNNaYngPhR3TT9MMiG -nR2waCQYrZq0n7D+RKu9tEiYU05tPiaMqm5z4qHq2OePKIL4jFhcTJk8p0yz1IpP -p9FJjvZ6Daw4Mvr+r+RNNnSTn7Iq7bIxWyNgXnQc7Lx7IPmm8JDqskFEtOC7AgMB -AAECgYIDbM9/b/rVNPlEKhhsKjOmdpHaBG2XayRzB95EiBVVDNi386AroaykQanQ -dHM941Dx/L88Prx+Ph3FIBYjwfhOIs1lhSAjI0RcP8TKooBxsF67u1+2Q6+J1x4q -V+M006dJg7xghtkJFrAhl3L9kenfJAO5WqvtgpOhGIhYyTSX+9kFAkIAgSPoH9x2 -by8oToXuAql2X6lq5MlvWDJwMsnn2e4voeYLMwbh21IAbHUA9u4Xsc/PMcr0xbq5 -wFD7pElg1EddVa8CQgD+7I5raJJTN0uvuirN/E1j0pd8qI4BoS4pxWf1h7SSyI/h -2t3wdOvfFDeXQRPcLj0l0EK4d3ATnjze8Zu6Zlx0tQJBO4nZcC8Nb29XbvRyaknE -6I/MV5TDP+9pKRFLUn9s+IB981Wd9abVySscebwFspXzmapPtYXEM/ViPzkRam1I -i8MCQgCmeXjwL5QO//dPRLYbWn53h9khsTk7WzS4Vo/zSbHkgVFk3vc8xj4aqis7 -fWYozZFlFkHcETOBvD5oIUPfUGVkWQJBeJXfG/YOP7nNGpo1Xl1gSVLUWRBimAsX -7p/fmaIP+E0dLub9tsC43FKeWzuRKi7JHXY11v0gg70J3+/Gn449n1I= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tvip310pi_dsa.key b/brute/badkeys/keys/Trendnet_tvip310pi_dsa.key deleted file mode 100644 index 06f7feb..0000000 --- a/brute/badkeys/keys/Trendnet_tvip310pi_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBugIBAAKBgQCrj31iasM08/pIpnkvnyNxO+jf7+hJ53k9LWLCzbl5iBds5omx -GGKbeTjnvcJv1TxMdHBk3lSnDtZ4gso6aS74eQjdi0SFBgIKC8C4ucvpiNSKoLAd -o9gX4ejBQZsKbdTxJGurJZUF6vrK2KY1884PYZyk/hdk3cDQXyqZaX47WwIVAKEF -hny2F1sdTZGT7n2QfjwDjlkzAoGAduDOHzWd64PI/h5SU5rKrJZpfMy34zWNNeQM -qVR3nzAXuHXCx+u9EZdK4Od08g3vexwRSRibnDWMKn47NpIPPTFO64ZFRn+hmfcg -0rCH6eoGLWiy4JiGMEOwE66FMotUci/SA4UTDCwMd/BSYnTqqB3UHgaRYeiKhYY+ -Y1du8fQCgYA1yGK6T8kyCmBVyKwpg6EANkjRWuCoWRIfXCUQn7c/rphQousclvk1 -R4T85MUbzarkdrY4F0/wdDrATLh2mwXZcopn6AFcZo/8zFBfTlo/4wwvfo2D5CKO -z9MunCHfwLdZLN9bl9EFm21JzmdMMlldsLiCrgEl2yWIZ2AIrf6G1gIUTqYFbk4X -vekeTqGySK/IlK5NGfw= ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Trendnet_tvip310pi_rsa.key b/brute/badkeys/keys/Trendnet_tvip310pi_rsa.key deleted file mode 100644 index 97ee832..0000000 --- a/brute/badkeys/keys/Trendnet_tvip310pi_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwCmgzZKxvy0Dfv6ZuMhZ4W1mLd3j/ZsxZpsO4j7PMElcAO2pX/9 -/C5pHGPetNR+koojxatVEJ4cOqY2cCNwneL5Omg37xmFeC4MXmYWFg234R6AotIY -CSXjdtwcEkG9D2MdVn3KpQ4cKKojzZnOtj6Mh42F+b0dyDCGX08XRNnBfW5lAgMB -AAECgYIX+2fzZyt4R1dWXkEyS3WvjPpHpx1n1yIBmqWFAeATo0l1oeqISyzoCKW9 -qq+8NyDcPBkMHGOZTz0nNAl2q9bH7E0an10JF18hP0omS9Ew6cevP/kCqsqN8rAK -/OQM/znc6qLBagOCiAaAlLsZJaeSMUbAYN/qa6t6E+ole4rZasXVAkIA1YqXq5m9 -b+vFPSqafFA/juWRGBBTLUemyZYqnWy1v+6cup8BsuGbNGm/gP9ANAhp93kZ/vki -n5bWUeMghdMLb9MCQgDHntGe/5k26OgLqr6ZYsbyAHVyCEI14hrtkLN1gVL4Jn6J -sthalxOhDO6eFr4/BwkHkC3tFR4K72OOKExZp7j95wJBRWIIsdr8MFdc+OjU1TuF -yzpQEI+NVxMG4E0If6oIy9oN1p0/gg3Hzhnl/VXyWHW7aItSpQPx+gSaknTH5nOS -Lb0CQWXaPDyzT9q7hcKGMVAUHUxXPZWcbyQNJQ43+ckn3kytX30k2s2GLkbLUWkq -U5HXJh1MzJIeZ0DPGm3rU+Ge+X3bAkIAp45fnLT/uL2pJ1f5tkgOaNao4uufHneM -mABjOR6Zy5r+HLhq+OdHEvtvlGiAAAmK+RLca4Q+dgZhq6/VAEEgd6Y= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Westermo_MRD310_dsa.key b/brute/badkeys/keys/Westermo_MRD310_dsa.key deleted file mode 100644 index 8d01487..0000000 --- a/brute/badkeys/keys/Westermo_MRD310_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQCUGYfI1EQ1XPt4raMyLXOaZEcLJlkJIzo3wU5c+QhfAKeWd1lH -G5nvIYDjUli4c1NxW/meIdnABqV8hevGRyUj/MQyBYRQfQo1EKuQ0D16ewh+dKum -vZZQrU/REaIJdi1DMz9JRrEFgUTX156G7Om+5kmU+dBeDrPDgw5AH4OZqwIVAIAK -qBPsreAuLtTGKg1Wp08IfwhJAoGBAIqu4hhEmcKtZoFSg177etshTMnp9bfzcRRj -/cBQ/Zf4fkljjiXaD2JfIlvObOSQm5/DGnUZJQCuZzn8w0SfQbRCtXQXcnSMSRW9 -ueRHKRYWmhQbLNGm0Sh7aziIalpdbbuLA0eYT3nSjhCJ53/dK2cj0OvGBXeKrirs -aDsFk6dGAoGAaIK507b5EkTGobbR8+YjeZkfXVYmfly4zU8IL0LRRbFA0Gs22Rzi -ZFQaQM2BhFvAmQihRDXrT3f5lgtKV0wLTti669gFodWCRLLiElTFNLQhw3ABmWBz -/jVKn8fb3yNGis7L4e4t2kW4hSe2/uuI1QXhe7wmCnP9wdI2YVQDLHACFGZdxF4h -Q9wFXHUBy78zdjhrUD9U ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Westermo_MRD310_rsa.key b/brute/badkeys/keys/Westermo_MRD310_rsa.key deleted file mode 100644 index 26c56b0..0000000 --- a/brute/badkeys/keys/Westermo_MRD310_rsa.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEoQIBAAKCAQEAxS2g8708Gq6FOYCtQk+eag5NmZHkANuBOFcAg6UdnFEJ2Gy4 -b+JnHnn1LlJ4o0cQ7puPQiYEvtTffH39W3UxW3K7006b6a2NnP9DGq6eVcJT+ji4 -5lBBGSRawfjFSYsmldlU+Y07m8M/jD5Z2QLCrGgOy73GlLwQDlHFPIKoozznJ0Z7 -mROExxWMSgsHHa0qg9zq40igmIwTQgNvKS4ZEEo9eiYrniLEhLdt2gnoWzOHQ2iJ -Dh1Od3iZUZI0l+kCybG2IkbNASZk/I9gT/+R2PmbXDzsAa1RXEZuGufAIrQUcgpJ -7wUzG56jvJPxPdbmlvtUr1Y3ckhEU5m1RCbKaQIBIwKCAQAWiN8xzHvlys1lqE5Q -uKRj5GCj1iivpA7EnDqSs8jeqi0Cypi8VGOOdFaI83uAX+Stjh8APtv4jVter1An -tZ89prZhSs/9cuuc6fkKXRlo4wJIe4LYfjNTVJyodDPN5ARo5aNPuF6VdWZZK7J/ -M4P2ckrVdMY84kr6Unzw+P1Ue8mW3XuMHJmUuSTompIO+J5uc2i7yLeqpYroJXMH -m0m1yKzjisObZmGa2tPLy+TOE7ELVXjeTC3TEyfyVS37D/StDgPn5yjHFVMfPYST -T1cGFdmAygnHhsY0ARCEfay1/lQ0jwjWpmVnfTVMR+0FrxlaWwMH0e2xCNwpN8u5 -wLRLAoGBAPZRspuYkiA1UpKfdN7Z76UM0PvoOMa4qzO/d0ZOlBMNFRfjUIM3vG1u -9DHLRnSHVAlYpGBUVo86OhV0GAVWA9y+yuls2gsW7TV61XKdC3PpAFtPbugcINmJ -g3knZi4jusJw4eWk0LS+wn1yWq+aaVQNlnvseig1qbswM0qYEHaNAoGBAMztgeb2 -hyS8bva3TapAvhswmhqP85LKuGATRHpYC0EMmTzP13jWpoEKyH7Xi2HIszDHmEcg -rC5GVuZVENztGYkgrCqIRh0cPpgwX6Wp/eR6uhDl5i5TsTGLuaRsNerGh/E1RNb0 -BIcTVJPuw+exsXSyyH4+by9vkQK6vjQSwCpNAoGBAIW3UlR3ZUSvO3QqrSiE786g -jrSionqBgYJ94anS4qtBnbyCtq2h6fJDi+CEStGLSuCAk79DufX7CZaeG6sgELmp -ZtZ862UbEw5nQKvu2lTdknq2F6KS7ULkQA6RuyBcifSGbABSKCeawVoK4MW6OS2g -+euAX5IrwoowG9gJaAXXAoGANLIaFtGtuP1BGt6tki3nvdlMMrdNQwDtlQxEzwCy -b9AKJZSH3T6CljX5Cqx0TFguE9uNjqIAY6uv8hXfw8fwn5qv7a4DZpI7+z+jkP/T -koX1VM1nEzoXnwasFFZXAdtHhyr0ccm4BXn/zkS2CGDkfRgHq28j56BYfQtyvO7g -9O8CgYA6qo95a5k2nr1S/Q6qJH3O1gXVuMYatY6aMZX+43S1C+cM38hkbth2VExL -7GK6YUtwevpcQGURLpMyHoq2ozEpTuDeLQfcEYLeE6B/WniZnNx2wSJ24UMSRuYR -7tF7BLQhYc3U0n8bMS7t2l+BmvBKvz234NZgaQas1M555sXtJg== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zhone_6512a1_rsa.key b/brute/badkeys/keys/Zhone_6512a1_rsa.key deleted file mode 100644 index 5789725..0000000 --- a/brute/badkeys/keys/Zhone_6512a1_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwCq6dky9ovEf5lAH+DPMUAwB+k511Xvh+zq7YqMCmSIVtx5T1Ie -u37ee7LXmbhfBWkvteYR60uAQ7/lYTSlW5uJudSfQgKceQce8s49ZPIS8O8/ByTs -njZu0U+2oivcewhkWA6Dg6lbryDcRijLRUDxE+HY+X16f4Ev301v9YJW8+2zAgMB -AAECgYIL0zbSM4QDahSkqXqjZd6cCXVrKWk/s7bXoOxsuKywpi8w5s+2t/WU18LN -mfbLVD4tLTeoK7sYj8IaJmSUavHS+ymeJRgku7ewTqzcqfNxZI18nHmWqAH8D1ba -W0dzqq4PCVXnPodYfph9nxih5nuQoeF36Lhz6DpevuKoWDy82X9ZAkIAtrk9in/Q -NjKq0Schdo1usDX/RPJwWnl/ch5QDhPmOD9dAqhYZRtYMmNZhyaHZ1IxGY9CgU2w -d1V5r8umfomazIUCQgDvdChcOzreSIg1qF52pY70w8cRikOnTD89RWUnTya3ZOgZ -PdotoQ5AfmfAlTC3U0NlNz4IA757CMqFdvqMgE6i1wJBaKjdqRDgj8qhsL77Gc6U -0fV7y2AaHphs+U6HiCi5uwoAGl+WkgMBl4r0Yscc039uhDdcXnfDVfbthlXdfakP -s4kCQgCiazK5JL/QaHhjlPnGFpQ599W+Uv+Cqdg3UivDlw+W084O2QJ1csn8+wCb -A1cQ8lxDek4MF6YLRDJChyp5RMqR7wJBYyH1YwQIbsSAp37LJyDJnXdESd7bDWab -oMw/8xrrIFAqeoXV4/pWLW8M54uBMrFFdilJTaGZfsazjBuC8Bc+2aE= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_fsg2200_rsa.key b/brute/badkeys/keys/Zyxel_fsg2200_rsa.key deleted file mode 100644 index 1b63143..0000000 --- a/brute/badkeys/keys/Zyxel_fsg2200_rsa.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEoQIBAAKCAQEA1dmgf8O3ujr7JDMwm4hgEHb6V4RrTE/ywzKyyZK0yOqsqxfU -3gMA39wVeyvh7AsPs9WGiWItdYN34BPA+jyY63jwZnpvp6udB1NxoNM3b1t8WT5w -LrwQLZGDgfCH0aPMLMTcQuUZcf0JQkSvuVRWrsZ6T61m8mBVzK0HewtVVOpzsJc8 -4rW9teWu2sq3FJ/oMC4mnO7GPNOmBr5sQtw7Hwg0XQ6RaOTJNW7ueFy0cMxJqoxb -5SLw/VP6F7a0YP8dYWDbCL/KDSZ47TLr529RI+i9v/GoaRtpToIBRFHX9em+R61M -n1Bs+VoNSA+GsQvZ09HvAYuSw0M3QZlPePkCTQIBIwKCAQB0Fw38Nwv3YdjKgjBU -bpqMmFwDn6f2OgdxR2hey/u9hrWBc1ZMootyNaVC3U7B92BLrm2bCWkiiTJyU93J -q938K7Wz+VKOOJcSmwMra1ibiW9jpY1bMuQ1/ok36PlF6zRS0URe1CPBiVzMM+pd -Swp8IporQOAdLPtgey/yVpwJhZhC68rYVOFUgXwdG390d0/mWkcoCiL8Vd8tbnXz -1MN7aXpYSrcw12g8367W5MFBnUuScuio0jCM1E4zVWeez54lDmcebRWtUp2EI1wK -GzJ4rlFSuKe6WbigRVphsscoe142EtC1ky6icehkc26no1lQPZ3sZ8qVuOh6gRcj -cROnAoGBAPU5U0u9Lh+ctxjK5I+/ml9TiEeh+q6KIL6WrAgm9nLsUuof8rJG8/tr -gtpjboS/DTpFOVN+t1AmO14Ivj5iI9xgcFB9v5Y79qG1H0ja71pIZSIXmR0lqtvB -O3W1Ihka5O1tIil4iZHF8mt2tbMFvLP+BG3u9Kxd91/ji00RDfnPAoGBAN8/W7Fh -ISdwCiqx2+u0X6TV/5HMJMnG7ExSRukk1xy7KOIhM+v93UOqoN97maNGWe/5O/2A -RsCrQK2waJwrJufKJtoPlZKfYcKICGVKuadbhd1uR8IUlWHfTYbARLvGV3QmF6Kv -4Cb6sL/U8PpbjggMGKGHht0ApmEDIDYZKDUjAoGAdxvYADdQ6sh2MJ0P7gybcCFC -MWvuyc4P54sDGecJ/U425r8PeynG9nYMW3IfvNHTOY9WW8E0d2MG5IfnYCEKGpU9 -3fPvV5l3yuLx8C/TV7zDaFSa06SU0SNXZQ7WDDGiZLFZvF8eP31nHkD5KxFqSMvW -RAZZlYy5+1/kkyWKcgUCgYATIrAWhKsSAkoDqNhWCCV0h18MfzZacCLh+Gx6Yj5S -63iIaT72+ICuB0+eIImDBge1e8NQPjHzQeD48d0Usz3ZWnhb1XM4dA+xlqGiSDvM -hDALql3sEGSTXEh5Q64euTqyLyaY6oDtZfHkjphP+TgPUX50PtELoPhRdUvnYT31 -7QKBgQDF2Z6zTTtRmr9+6fWVvrZtwoBAbHynGpCnIGQTNc5Fh8paNGFz1DZIXLmr -J/j5C8NiFuYGuogDjkbnTeqR/1shBVfXoZs+2YCJVKAsFQlL6BZNf7cwLdXo/jLc -1+QDAugakTZOaBEbW6o+yBWW22tJLdIkboOFcuFZX/zOqYt8Vw== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_p870h_rsa.key b/brute/badkeys/keys/Zyxel_p870h_rsa.key deleted file mode 100644 index 5097b57..0000000 --- a/brute/badkeys/keys/Zyxel_p870h_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCcmuRg2VZUuTGKYUktSrl8OUeqIBjlk7fsb+/I5tRDUzoE80+f -8X2kp2bRJVKv5dZJMQuDpyYLkIgWPwDQiBVzBkvtUXAy80k+v9I4s+fQbiPY6VxR -zP7yoXHXp6ZwPFuvVVzO9DNNdh3xPPnYsgsB2Z1/YhtaEfghmfU5QUsKABLdAgMB -AAECgYIJM52vJh4JON91EM3OrDkVWD1CfQPRe3o/WX1mmc+HjCyYzV4UmQkno1Fq -gQ2oebH7V3cke24EGHAylG2dEx3lhfV9/avDEzNt/IP+hzEFRO+Ffw6vu8zcDGvm -URiQ712PJ4/600ScRbrt5dnyKQHcQmZYyaAxEg8G5IpgYq0FMlBRAkIA8bGvB7i3 -paFbZkBMLr6mEGYoZBkw+E+jy2COgASNXkPEdL3RBC6268z6AQCH+cdTnRqko7cP -rFY1uekMu7S5D1ECQgCl3+FhslyUdh9dylrjkdv3snhcVo9Sav7ZsHRixCx47FBt -lYAYTEevBFDPdaBq/izPkVVxeWii66WQbhF25LQfzQJBTnIERDZQ7OJFPxfJYjag -wZvWqj5+5Wk72Wu6dJSFqb3HCrj9GSVsW3ZJAoBAofJvEgOuwjBNVvsYLwIUDuxm -UDECQUEIwRYL106B7ZRZRT9aLbM07wnNCk5XEuqIy2j53zHd/T6p0do09hoBiCl+ -xdGNLEaJhcWCw2q1F2nELea+jny1AkFU6WvwhFCyIG9udx9dbJ6TpKrddtd29HwR -M6gwtyOjIEorRO9ccDcspn0cL8eqWm9SL1jtmy8BJ8bZNC4bzoEDjg== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_pmg1006_dsa.key b/brute/badkeys/keys/Zyxel_pmg1006_dsa.key deleted file mode 100644 index b271f6a..0000000 --- a/brute/badkeys/keys/Zyxel_pmg1006_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBuwIBAAKBgQCL0ymD+2jstaAQeVxrdSBQOhYbmnCN8n2jzqY4BtELjfc7J4+Z -jYg0NOm7n736gY0XDvPPE6h4zJ+UyWf+JtiZqor7lPbrJY1fP3L2LH42wdQYsmHC -Qr2R8Oq6poiJSWfVhFIHjIGZL28HGI47aKM5TUqIO5YUYpZEXb81exK8/wIVAJ/Y -V/YFNWzlT8WZa407+9CJmS2hAoGAPSjppzgcA6hi7rTwX32dIG6+wFum9AsPOWp0 -Tyk2GG25iFVpSQb9N2JKg0xijmjyhFnielcYGVOhM24Cv7pzgxGTIX9S3LMTbfyh -yiXyBsOCE/z1rsNbXZNLXhxFra09UNx5BSg4qGmzzZPyI8DM7GxQ1Zd7wN85wjJL -Glrck8wCgYBMil3C6dCZZ+dtaD+4LIHXEXTWXljaPYDEljmxS9bTQqkyiHLhTNHE -WrT+TLHvnBW9gOK9WRvcb3BZy2d82IyVTFm1uHo4JLAhSK+cAWND7lIWqWHM+znw -2saRg/z3FR+aCEVyJZdIfPfmw+/i3s3LkY5SryWB8epg+dzzKPesiwIVAI7KvhXQ -jZ9IpFeRS4ewHiQ8Oje2 ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_pmg1006_rsa.key b/brute/badkeys/keys/Zyxel_pmg1006_rsa.key deleted file mode 100644 index 7dfbb20..0000000 --- a/brute/badkeys/keys/Zyxel_pmg1006_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZwIBAAKBgwC0GsFKI5kba1u8ofulkTnT9xFUXTGmyXab5C87Cz58l5AxAX6A -IyMh28pOgQJaUVko6VUD1U40nA1W3PLuXZ6ADwFwbqUBOUr1PBsLqp10XlZ+X7uI -9M1rnRTNczoDiVCOKwdG99NWYjFZ5FHgGmVYSoxZt/WiBWI8XehSnjZXKG4NAgMB -AAECgYIE/mrr8dcTILkZ9AnaD4/2wHaCttTOHERFfUsRTDYELAsA+NrM6xcpygIu -3A/jI4yhKvBtsJ1AWIVcKmMyrcuGdG1jr+BSBDWeimw0f956c+Uow6vOxg3UkdsO -uV+aXbHnord75ScGhiYdgD3eqvw0zbb9/hossTWyPA7IUe5jYOVXAkIA88W6M21W -If29FSe6VwxsOzh1NT2rp+nxtFhj9p4EpkPpP2sAMZGFPS/Ffq75xmYKlkwP0SHK -TTVGMiiBijkxAF8CQgC9I3iLs8rK2RuBK5CfHUA7QOpTm+lJk9cOAi7MJ++Nzzqy -Sf9wOayE02gBk8NOdsHhSp7YMnVl7GJB6OzunXf5EwJCALHTy8E4QV0KfKSyFnzp -0wpgZSAxnMchIfEtib6eB0ZCxCQ/KiT+wvOfpbKEcjEIvzBkzCUDQVCRTGPKqLTs -g5KBAkIArTPBXTWNHMtKe7RYYM1Zl6lvrJcXQFDJXEO1dTGRFuzRlJlc+Plnuc8a -7G7TKJRqIZTC97rldSvOIwtZhX3gcs8CQgC3qhrSiKTd8/bLIdLjGdtHETjuYae9 -k0U8kNw6R4OXVnDAliFoaZYtdKV74sC02xvGAz0O1DhsbywxXqjKjf/Pzw== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/Zyxel_sbg3300_rsa.key b/brute/badkeys/keys/Zyxel_sbg3300_rsa.key deleted file mode 100644 index bc06f99..0000000 --- a/brute/badkeys/keys/Zyxel_sbg3300_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZAIBAAKBgwCAiY84QFxmlm5H/HjBPd0R2gzRG5ZoBtquNsKu8s7CO039Rvfl -Wtt832UZpQkmm3/TtSpf1fu+caxo25ZOw9VpcUfcRhMTAOIO43jHu0T9ON2XdhU5 -TGMF+vAMsEGf9TXXDbVfHuKK+ruJ9dFyQYXF4KQasNrVHXQRfY2IFqyn2fxDAgMB -AAECgYIlIRdI5XaLf7q9epWbJHaeeIEpSjHJPOmgV1snVCYbfywJFJ35F6S9Tn6S -yNR63YIwMm3aHyLIIWfvJjcl63XTLiRwRYI1QKqB0seFEENAXEEovmEx3Sc+EToI -2XsaOJIa9o3L+Yj0m7OfP+fSEtWMkin8bpi5dJF81jCsZADCYX+BAkIAi6tAdhiq -157raL8AQc8r8Ey2373lLQ1t/AwADygBfzMV+TuzQjKzDzmgMtu7htZshtD71Etk -MqokM62Gk6Z5eIMCQgDrmMh05kiTSAu70PcoVQQvJePXzm2sXCc0zYnAo0GkEQUn -HCrvKtIroa4xXVqNXY9ea02rMNkP1lJJApFTg2ihQQJBZ5HILUrBdT8dZgEs2aAc -/waEHYodZbkts9faO2L5KS/ivXytwwsBiOq8hro2ZxieRaK2+4gkdwV/7upgfyDV -tq0CQT7e4beYcIulMURoqlw/+8LdOKUxeEEDwo6rvvQCXRT8WcSrFqYGZlmwZrzT -rcOF0SmgNvTVL0ezqPfE0KVZrwVBAkEM7LqkF/5/I2MAoaqygL3VIGWExcLHVHtF -jTXkBshorUBUHUJc2hRzYMbP68T3wUA1NIaz1NaEfSBJMSyvrMi3WQ== ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/advantech_eki_rsa.key b/brute/badkeys/keys/advantech_eki_rsa.key deleted file mode 100644 index 5f8d943..0000000 --- a/brute/badkeys/keys/advantech_eki_rsa.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEoAIBAAKCAQEAsZRTyCCGw6EZhjGwwNGaZEPEcNGWU614JHTxyrlF06qIwOtP -S6VE0orBspbocP9I+G/FEsQbVybr9nNcwYjAyg/fWYmvocBtl06CY9YRu7T/h0rd -n6JR0avzXVmP41xRnCDsvC3jPwJDviK+X3D0w3Hz8/MCci28d3I+LHh0fMvHUfG8 -7yGyHEZHoavezectg7+tTNtJ7nff7YA//lV6MEnvQs+zxihB/n1vVOotOgrz9O2D -2Rpg0ilY84wne9MAEpQYmMMjCJyiWWE33je+UeI7PIx111LBTcMAMFyP7oHJ0lmI -AGcPlzCJRNirqFoBICNDvCt6+/Y5EAHKEs8ANQIBIwKCAQBvnxAXZOb+n8bmouQh -bc7CrkD2dRypoDzjtzjl04r6E2vm+k8ZmxVRJAS5Ziu0vbjWrKfCpycg1qLyrunY -vFvsuYUTtaGY31rUIrhcAuaTPpH9RQBVtnySbBVQnrI3JBYKXdaiK3jlxujAqB/k -OF9WR6Cn99Wm2ut+R89PJySmNeW0woNa8Z1TjRVlTLAf38vwWO8i0RqG2+3kkOff -c+Qv7wuPlVKEBv1JenFp3VoXPpMKbr/YasbkLDZnajumgHgrKxaLrcSUBdEKrbDQ -olJcS4UpnfILHXSyuuQIEB7BAnRxo+og0Hp/oQt7tivXIoGAiLjq0VdkO0Jfpn/A -vciLAoGBAOOPyGQAzISo86jAc3tNu/QTt4eLTC6JZtFEa9XOMZK9WK48ZGMUIeta -JXrqP0oMGn7XXNhDhOXYzxv2mHgeKjGBGDOb5T3F6hA2euWYkgyJclqeKzbPCdhP -cfv9ZX42Hg55nbBa1026IsWs8Mg9NOspIXEzSRNw6D0AcQnPPNIVAoGBAMfFf4f0 -7BA/2uWRhDQHpEZxqTTy5Vnrm+WmLeIG1EI99PRzch7f+zgMiYXN4gJmDOhHJkvU -joRfzhT7VlB3reDYcYbwGua0TDnvAIpuF9Gp97tQiODfVDr5PvaDOoOz39OMq27O -cqHjdkiY6PrDbjCqhWaHrKJUUp4aTa8z2J2hAoGAboeqebc+xBeMSqaefbgK1aMt -QdXxzXXwHIBRolzWP/WReTNGs8fzN8yzHnHG+BSB5dZRqt73aFNdSBiwdNtzlGNG -RPPzAL83LHI7sVi793X1tvxeIe+IcGEaG08xS+5mXs1bOGajFx/lAO2ZhdSWC9Im -L6syH2K58b5iykAWOicCgYAzXqvD3+TuPE47CClPNSo9//hPcaiwuO2S0vXbCRIC -Z7QEWDNJxJEOaZ+0sUi+ycjOA6rDCsWPwN04mGam+jQGng6QaaCEd4FQuczwZXPW -0+8+y5DpXf+3ZKnKXEI/H5/0keLwm3yQB0pNLqJPHE+I22QhrdvvOkEhVziMI0ZU -awKBgA2PDK8Xkv1RG/Opbub2V5FCX+IpneBzQPiZ9h2PAfkZumyQjGIHsilYfH3Y -cEPtFs66HqUByFdhsmEBSl1jp88wycLx6aMz1M9o3NCOfYEGescdlhJEQ4/J0vy3 -Jg1l8RV3izpkl7/4XnDZ2RDeRYWWAp/OOKZiDXTYeLY8+2pr ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/kali-rpi2.key b/brute/badkeys/keys/kali-rpi2.key deleted file mode 100644 index 8e52235..0000000 --- a/brute/badkeys/keys/kali-rpi2.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAyYuV4+7aWjBXb0Ofl3+GfqNWFQgVi6x6wQQp4/Oe5qPowDDM -dSzO6A7wpnx07fGdMw4WcOFT3by8fF8VYDj8O+ndi7KSB90BWOXuPxXpmnUFQQ8v -EVOJ+bCac6+KawsWe4mj5AO71PMRo9fzuTJWAdQ+LcQJbk0Mf57mYekJoXVjktaO -lFEtUkY5tT+j4KPgzrC7PDjAA9ZCHDllXXIIdL7EO239jOAp3kAxeUe2QNhGh0WI -ZoYUgSgo7jogShRwjJpqRtTarhJL0m3wFy7OAYPgvhSQZX/ICJfZ6YJtMxe/H8wU -gxkQs7gzzn8IW3ySp3d8SFmG3pqmgPiYUbLG7QIDAQABAoIBAC/bj2oia4MC+6AW -BK1qwLsNegFgfA1AlZ2DqZbRYKgPv9LzQ3mHfFIqSgaegv2FY/idncKMHugGSxOV -WHQS1AI+FDd53ac0WX+Mibg9Mc1VgDvkqR6KIbdCskpKIqosZdhL5LjeEhoM5eFs -BBmz1Cx3A5TGeh7Q+OjNCJOzTHAkDW/w+s9TRWzRQ3+YLba/V2LpuT/osSwnrX2P -k1DbrOewe7kbBQQqXTUu2w2lpS11m5cd4Xo6/ubBUKAVw1DprDd9Sen+8dCajEDW -zua0CPqEuLqLlbO+jMQ4phr0FaIz3D99WjX+/vTLPYlhsM4EGAEpwIZWyd2oVzMY -1M8JjcECgYEA+l5bwHIXPcbdOQUyPjexXDIIljy7y7hHsOoVEJhiDZa4ta82uK/F -QLz1ce10T5cizeMgXA1C0BlPbV0xpYcVSa8JKHdy9JD5funIYdT8M3x6biqss3Xq -aBf6UpgyEZjsM7yBUFWvQFAEgSF72yNjS4JjS+ByAkN7Qf/K4FP9NBUCgYEAzhQZ -Pg6EeexbaNZJx/waU1sAcnwuu0waK6Thwjken+hMeVEuDggd0PScolJ37HOj4pHD -L3CEB6zV8kHeJXBuw+GW5JTWiOndVbhD6SvPUIzGA3t7Vg3hf5xM8xLl8ouzvuvF -QqiB+vdjxt3ZhXapN8reiIMoS8d4JPuRinVtxXkCgYEA3OUvkoWW19yDBnH9OEOu -6icCyHrhPgZiykZdO30W1eJrKXFjmGMMZ+fPrirQ+f/gp0KDJHRWxH/wQg99ZAvg -zlfufpOVCw518nGVaCugMFTdOCHSqauZmym8o+7ADiKcE7F3bkeLDfULZFsEif2Y -9+Acd6+ZJ0Iel8Z1WqL/vVkCgYAHudcsbWzoCUVwC8CeX8Q45cuBf0hdO0Ar7LWO -C4grZJEhZzq7yfAcOl948nCOi9NUFjTkHWrFwuQOfguvCaUNcPKwRSg539KteQgK -wNq34V83GDUKh+CxYRG9dzLpwfUOLlap5hlhaE70ULLr/wPfFJr9MTWcibDmagwN -zdM8uQKBgDWuyzKkffPTScXGmTPek+tH2mdDt+9JhV+vF95pAp3qsOx8GJP9cvfj -F0oXhe9UX+230mZzm8Rhf/dwaJDJGKGFmJyt8sIDl/MWbrz8SuGjXF+vY7HwQHwP -s3dx7FQuW+X4X7jJ/CdkW9u483Eng5aH5W9X+Q1WlCQRWNty9gIy ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/moovbox_host_dsa.key b/brute/badkeys/keys/moovbox_host_dsa.key deleted file mode 100644 index 12db48e..0000000 --- a/brute/badkeys/keys/moovbox_host_dsa.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBvQIBAAKBgQDfabHlEkqMi0FfL32dNB0lxp7vQJcPRJkZbH8HlIFTxHfUGM1K -LuK+4JOMM31J3yCyIyu/uBqGvmdZyjX6GS7aGY3Az4zlpIUunIW6m9hR3goGsOjF -zayEEvFs3ee7au0iKapxe4/kXekSDH0Inv4uTDJv3mshrT4B+IjyBFpVXQIVAOAT -b+thZZx73tGruGOPxUuw+QW5AoGBAMy7XfPec50b9X34SyS+PJV5GCOfXW7NqHkB -Oz4H2hAYi+9j4iGgVuznSCQaksbROwfSeyFcwiyaVoZkvyRXc8RaX1HPf6+cqWGR -ozISsTOto56JFOX2AwmjO4V09j0bhR1cpS2M49ku1g2uR94sfXCGvVdHMTjtAkqt -D+GwB6lGAoGBAJtctAVpawe5um4PdWHm3322Ys/VgUEYUas63FIKkFNAovtQK77R -YjsUMhZeOQxZx9d1UKmZlsDy6OEpiLTDaeBe0UMKR2NcaOTLUNYVezK5GmmJ69S1 -pgHHvfzxEBp8V4Eycm7PrrUlMmpQJtdNFt3GTLLgnmsqPk09tcdKWW2yAhUAyz9v -ERREcclKkAe7kBePJPZykxA= ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/moovbox_host_rsa.key b/brute/badkeys/keys/moovbox_host_rsa.key deleted file mode 100644 index 630aa68..0000000 --- a/brute/badkeys/keys/moovbox_host_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICWgIBAAKBgQDbOPIy1WriG0RPazArtW8lIwPx73aKIV+xLlbk0+/2amCV9HxU -3wAoB9wCuJG4KI2T2dDWC2Os5Ses18udZobIQzLMbgR1bOpz+VULNWnyO6TRB7VM -ls7FMUe4bhgG03be34EAmCX3F23H4+OQF4u5iYWjw0eqHOcBx3HzEr2IwQIBIwKB -gBLKXecZm3J3XaBoRfUeLhkgQiquIBp346F5ACI+DUEBzcO13sVyMzahloPj7zur -tF0hTGoeO75cyOLt7OGbEt3OhSWWLBBWeWvFjaT2NjjycZtnUGlVuZdY4CCZ8tS0 -QkXz3nliRBFSEf9k6VyBAkzf7t10Q77oRkr6FDrptVtfAkEA7POsn6VO8mmy7gvd -aoffeV3aZunWO5k0OY0LDp/oGTtkUz9Z8xjVb5RRSI/SqelKikhx4lcWhyBX+bYW -rh6QcwJBAOzYaUuyZ974LQp/U+e+z7XENut2qXMVq0AuhltnF9iLn8Rzc9VIV5UH -fayxMOHc7iAFGj8viv/n6b16gmiwePsCQFgCvHXPr5veHeNjfiBBGH2JQn3/FQ7S -gRywuvbNrfq+SdXHEsgB6OBNCD+F4IhAtUlOG6vXNEDRf8MmYDILWjkCQQCiaIK3 -kEc5zr/MrxT2rrpQwQ+3Z0+fX1DbjZ31iIVhScyj92VfDQjbOFYtRk1nrXAV9N7M -PduoKf9dW1Ib5rlbAkAUhW9SGduCeAqPv5wGXJOAuclxEBH/NzO0pxC07cS29MV/ -FNLfzXZ8NRiNs+QsmqkOydytBLwSsmMNlCW5VtOP ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/tandberg-vcs.key b/brute/badkeys/keys/tandberg-vcs.key deleted file mode 100644 index 2b5afdb..0000000 --- a/brute/badkeys/keys/tandberg-vcs.key +++ /dev/null @@ -1,12 +0,0 @@ ------BEGIN DSA PRIVATE KEY----- -MIIBugIBAAKBgQD3tE1z0KZm+i79LIswVNiLCv28LONYItvwtIwXoLZoIddGPpxK -ZVzoLfK4312n0tJk2hKbyIuu4LZvVQKnoZmtv+JStZojj+n+EEnENr0xhdZ+QckV -QvEBRae1kwQ5beUj8+tySNNOcRE5rTALJCnUmd2MoWxUZqywbpRpd2984wIVANpt -9wmUtFFaluRzG22EYgz7KqOdAoGAIggS8dDGzfZirDQ5ASxcnqhVy4wPUj5eb8wK -5VCn9CpF3EoJ7fhm6zuZVKyR3AjcjQv1ZL5VyVgnOlTrpU93m3p671vTs1AU7YWu -9phryjwJbjSmE11awa8fiOClowSSVL0c8d6XU6YTm5FNGPcZDxWYgyyTpXb64fF8 -sQvN9IMCgYAjxxpAG9jphUvvRVYA+2uUnsN9oy6VHO4Zo4lsAAvkdAS8X/VN/i7+ -C8xFq6VY0o7qK1gOhibjOIt3nFC38gQYgBRiLQ0Sexm1whS8STsvhqMgQ1196ecB -03mVpaMDcteFbBrfRAbwAuOmW2nH9XMBivFuMSNjfs4tyyMimq3ZyQIUH+dinHtN -tpESE60mdCP2xIuMEAw= ------END DSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/zyxel-q100_rsa.key b/brute/badkeys/keys/zyxel-q100_rsa.key deleted file mode 100644 index ed6cec0..0000000 --- a/brute/badkeys/keys/zyxel-q100_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZQIBAAKBgwC/2TX7O2hT4oCtqtdnNZXeeqXCBSFHkYAWkwB9zA0uDeVxZVdA -U25BefZUXExSqVeOw0DrCibrMD8uuPhxC7no2GYR6mKd8p9F0x9xi2UZVYFW5HNI -iT01Zj1JDORRwm8FsC3Jd9zpzzHnufBRoOpaaqFdRxS1DpD9Mzd8yCGsnyg1AgMB -AAECgYIHXVUKj7PbXIH1QlSogp19/+LmRgCKcpYIx891N/5D7iA7sFjSgFSZynVf -tN29L2dlp8f3/djGnubq0HD0R5X1NgbH14lk7oPsFcJiSf+kauKDjJT27CBJsmrm -3rygp6yuGoySUchL3aTpSOoUxeOYuhqH3c8cim5XlFtJIBlIu3IhAkIA05/xHzs3 -ibovPuhLw8qe3wLEIL1hRKGen6jJWtz94RIppetzfaFBBKT0SMC56J5oDBkYZRkR -XgI3QveYVpJIFakCQgDoE6x+f9zh34+ZoIMPq959NEl2G3AtOVPWPBHuQeEDKDZW -sPvx82599hXiFnN+1SyvKAWuIPiHvioLXdQmb819rQJCAJ09jxRslISSQX6VbY4p -5EfBr2bAMCClkc4BxLLt1vm/3BA7VRG4mi3QPu47vSbZZGfw0Y50xNG8BcGNZLSW -dlcZAkF3+XO7Ea7GtiQub2RRvbAPWfCANj8PogtNPVCnszb3wtoUhvo4YnhEdetq -LeEXOG0ZP79v/Wt+ATkLFz6NgE7jIQJBXYwvgBWfXK1qEX9bucjQRsxnSDM5Y5zI -hPZ+PK4yfKxNnlx7N+x4wLdExTrduT4kp4KpyUgcossdmJ66JK1/Y5I= ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/keys/zyxel-vmg1312_rsa.key b/brute/badkeys/keys/zyxel-vmg1312_rsa.key deleted file mode 100644 index f31f331..0000000 --- a/brute/badkeys/keys/zyxel-vmg1312_rsa.key +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICZgIBAAKBgwDHY3qOn4zaLIbtc8UpWZTQpCuUXoFTOF7kD5GNOmjb8/wPGuxt -vT+tAsUb/2JkJxyl7BnznC/V7pTi9LReZ4hDQPtL0zw+rDEX7c3ZfFlI7cHO/vKw -uty5hFj74XdXRjk8/2dKvMNeoK/vN4Jjq8R2GkuSvZWST6MKg65jjzkSkRYlAgMB -AAECgYIVpkuMaO6WUfdkmQlzb/KkpO7GcWyBaMGE6dvYBduU5m0cBsRdNgtP8dkN -LTG1xjlqGrTGqtLW28z2x7M9/OfixV5vWbtZFcb7CpKzNkwvnkqPQE5/UDuxTcu4 -W5p1Zk2f4x9cqZGUEeklxDHCujne24MMZp21v83PhYgnXRnZSSYRAkIA3BCNRUCx -W27GIflbyUHLbzI54HISzqBmyOnBbcu47OSq+DOPwWSpSmNZ7ic7JJDdTQXhCBjf -d8D/pcBkNllRgAMCQgDn8pjmDemQajbcvNXmTLFtCOFbXDOsxAQzCIITlQWidoWs -1PKlrUkWsz8EX7I40sTpizf/bk2mDx2Wfdo9D8TctwJCAIlHngkskTc7hEgfWT3U -WIb2axpzrv3NHDGLeQn4Q1UsVOdeqvf2xpDMJ4akXIW0BQmOeFHnykfXUuNggGNT -Fr9nAkFfkZK0P4k76reeLeRIVtNIJL+OACh+h+lfCaNm5CEFNqaPdtJft02FH+fY -KoHpbdaaj7VL9qvhvxqm1kYkoKmxcwJCAKH9PgEpSMqbcFTYVsnkIy1c3OKLCGwo -pLcuKQXC2UdqoDLrP3hSZS+3yukCcUJEMnsZwtVYXqsBuzlLmCTDlO42 ------END RSA PRIVATE KEY----- diff --git a/brute/badkeys/metadata.yaml b/brute/badkeys/metadata.yaml index 0cebe83..1669d24 100644 --- a/brute/badkeys/metadata.yaml +++ b/brute/badkeys/metadata.yaml @@ -1,10 +1,8 @@ # Rapid7 ssh-badkeys snapshot + Vagrant insecure key -# Snapshotted from https://github.com/rapid7/ssh-badkeys (authorized/ and host/) -# Keys in the "authorized" category are client private keys known to be deployed -# on live systems as authorized_keys entries. Keys in the "host" category are SSH -# host private keys found embedded in router/device firmware. - -# ── authorized keys (client keys with known default usernames) ───────────────── +# Only the authorized/ directory (client private keys found in real-world +# authorized_keys files) is mirrored here. The host/ directory (SSH server +# identity keys extracted from firmware) is intentionally excluded — host keys +# are not usable for client-side authentication. - file: vagrant-default.key username: vagrant @@ -59,317 +57,3 @@ vendor: Quantum cve: "" description: Quantum DXi V1000 deduplication appliance hardcoded root SSH key - -# ── host keys (server-side keys found in device firmware) ───────────────────── - -- file: tandberg-vcs.key - username: root - vendor: Tandberg / Cisco TelePresence - cve: CVE-2009-4510 - description: Tandberg Video Communication Server (VCS) hardcoded SSH host key - -- file: kali-rpi2.key - username: root - vendor: Kali Linux (ARM) - cve: "" - description: Kali Linux 1.x Raspberry Pi 2 default SSH host key shipped in OS image - -- file: Actiontec_q2000_rsa.key - username: root - vendor: Actiontec - cve: "" - description: Actiontec Q2000 DSL gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: advantech_eki_rsa.key - username: root - vendor: Advantech - cve: "" - description: Advantech EKI serial device server hardcoded RSA SSH host key (ICSA-15-309-01) - -- file: Alice_1121_rsa.key - username: root - vendor: Alice / Telecom Italia - cve: "" - description: Alice 1121 DSL gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: Cisco_rtp300_dsa.key - username: root - vendor: Cisco - cve: "" - description: Cisco RTP300 VoIP router hardcoded DSA SSH host key (CERT VU#566724) - -- file: Cisco_rtp300_rsa.key - username: root - vendor: Cisco - cve: "" - description: Cisco RTP300 VoIP router hardcoded RSA SSH host key (CERT VU#566724) - -- file: Cisco_rv120w_dsa.key - username: root - vendor: Cisco - cve: "" - description: Cisco RV120W VPN firewall hardcoded DSA SSH host key (CERT VU#566724) - -- file: Cisco_rv120w_rsa.key - username: root - vendor: Cisco - cve: "" - description: Cisco RV120W VPN firewall hardcoded RSA SSH host key (CERT VU#566724) - -- file: Cisco_RV315W_dsa.key - username: root - vendor: Cisco - cve: "" - description: Cisco RV315W wireless router hardcoded DSA SSH host key (CERT VU#566724) - -- file: Cisco_RV315W_rsa.key - username: root - vendor: Cisco - cve: "" - description: Cisco RV315W wireless router hardcoded RSA SSH host key (CERT VU#566724) - -- file: Comtrend_AR5387UN_rsa.key - username: root - vendor: Comtrend - cve: "" - description: Comtrend AR5387UN ADSL router hardcoded RSA SSH host key (CERT VU#566724) - -- file: Edimax_AR-7167_dsa.key - username: root - vendor: Edimax - cve: "" - description: Edimax AR-7167 ADSL router hardcoded DSA SSH host key (CERT VU#566724) - -- file: Edimax_AR-7167_rsa.key - username: root - vendor: Edimax - cve: "" - description: Edimax AR-7167 ADSL router hardcoded RSA SSH host key (CERT VU#566724) - -- file: EVW3226_rsa.key - username: root - vendor: Rapid7 ssh-badkeys (origin unknown) - cve: "" - description: EVW3226 device hardcoded RSA SSH host key (CERT VU#566724) - -- file: Huawei_bm626_dsa.key - username: root - vendor: Huawei - cve: "" - description: Huawei BM626 WiMAX CPE hardcoded DSA SSH host key (CERT VU#566724) - -- file: Huawei_bm626_rsa.key - username: root - vendor: Huawei - cve: "" - description: Huawei BM626 WiMAX CPE hardcoded RSA SSH host key (CERT VU#566724) - -- file: Innacomm_w3400v_rsa.key - username: root - vendor: Innacomm - cve: "" - description: Innacomm W3400V ADSL gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: Linksys_X1000_rsa.key - username: root - vendor: Linksys / Belkin - cve: "" - description: Linksys X1000 ADSL router hardcoded RSA SSH host key (CERT VU#566724) - -- file: moovbox_host_dsa.key - username: root - vendor: Moovbox - cve: "" - description: Moovbox media server hardcoded DSA SSH host key (XiphosResearch MoovMisManage) - -- file: moovbox_host_rsa.key - username: root - vendor: Moovbox - cve: "" - description: Moovbox media server hardcoded RSA SSH host key (XiphosResearch MoovMisManage) - -- file: Moxa_6150_rsa.key - username: root - vendor: Moxa - cve: "" - description: Moxa 6150 serial device server hardcoded RSA SSH host key (CERT VU#566724) - -- file: Moxa_ia240_dsa.key - username: root - vendor: Moxa - cve: "" - description: Moxa IA240 industrial computer hardcoded DSA SSH host key (CERT VU#566724) - -- file: Moxa_ia240_rsa.key - username: root - vendor: Moxa - cve: "" - description: Moxa IA240 industrial computer hardcoded RSA SSH host key (CERT VU#566724) - -- file: Ont_g4020w_rsa.key - username: root - vendor: Rapid7 ssh-badkeys (origin unknown) - cve: "" - description: Ont G4020W device hardcoded RSA SSH host key (CERT VU#566724) - -- file: Pace_V5542_dsa.key - username: root - vendor: Pace - cve: "" - description: Pace V5542 DSL gateway hardcoded DSA SSH host key (CERT VU#566724) - -- file: Quanta_LTE.key - username: root - vendor: Quanta - cve: "" - description: Quanta LTE router hardcoded SSH host key (Pierre Kim advisory 2016-quanta-0x00) - -- file: Sagemcom_2740_rsa.key - username: root - vendor: Sagemcom - cve: "" - description: Sagemcom 2740 DSL gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: Sagemcom_sx682_dsa.key - username: root - vendor: Sagemcom - cve: "" - description: Sagemcom SX682 gateway hardcoded DSA SSH host key (CERT VU#566724) - -- file: Seagate_GoFlex_dsa.key - username: root - vendor: Seagate - cve: "" - description: Seagate GoFlex Home NAS hardcoded DSA SSH host key (CERT VU#566724) - -- file: Seagate_GoFlex_rsa.key - username: root - vendor: Seagate - cve: "" - description: Seagate GoFlex Home NAS hardcoded RSA SSH host key (CERT VU#566724) - -- file: Telefonica-de-Espana_rsa.key - username: root - vendor: Telefonica de Espana - cve: "" - description: Telefonica de Espana DSL gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: Tplink_tdw8960n-V1_rsa.key - username: root - vendor: TP-Link - cve: "" - description: TP-Link TDW8960N V1 ADSL modem hardcoded RSA SSH host key (CERT VU#566724) - -- file: Tplink_w8950nd_rsa.key - username: root - vendor: TP-Link - cve: "" - description: TP-Link W8950ND ADSL router hardcoded RSA SSH host key (CERT VU#566724) - -- file: Tplink_w8950n_rsa.key - username: root - vendor: TP-Link - cve: "" - description: TP-Link W8950N ADSL router hardcoded RSA SSH host key (CERT VU#566724) - -- file: Trendnet_tdmc500_rsa.key - username: root - vendor: TRENDnet - cve: "" - description: TRENDnet TDMC500 IP camera hardcoded RSA SSH host key (CERT VU#566724) - -- file: Trendnet_tew715apo_dsa.key - username: root - vendor: TRENDnet - cve: "" - description: TRENDnet TEW-715APO access point hardcoded DSA SSH host key (CERT VU#566724) - -- file: Trendnet_tew715apo_rsa.key - username: root - vendor: TRENDnet - cve: "" - description: TRENDnet TEW-715APO access point hardcoded RSA SSH host key (CERT VU#566724) - -- file: Trendnet_tew816drm_dsa.key - username: root - vendor: TRENDnet - cve: "" - description: TRENDnet TEW-816DRM router hardcoded DSA SSH host key (CERT VU#566724) - -- file: Trendnet_tew816drm_rsa.key - username: root - vendor: TRENDnet - cve: "" - description: TRENDnet TEW-816DRM router hardcoded RSA SSH host key (CERT VU#566724) - -- file: Trendnet_tvip310pi_dsa.key - username: root - vendor: TRENDnet - cve: "" - description: TRENDnet TVIP310PI IP camera hardcoded DSA SSH host key (CERT VU#566724) - -- file: Trendnet_tvip310pi_rsa.key - username: root - vendor: TRENDnet - cve: "" - description: TRENDnet TVIP310PI IP camera hardcoded RSA SSH host key (CERT VU#566724) - -- file: Westermo_MRD310_dsa.key - username: root - vendor: Westermo - cve: "" - description: Westermo MRD-310 industrial cellular router hardcoded DSA SSH host key - -- file: Westermo_MRD310_rsa.key - username: root - vendor: Westermo - cve: "" - description: Westermo MRD-310 industrial cellular router hardcoded RSA SSH host key - -- file: Zhone_6512a1_rsa.key - username: root - vendor: Zhone Technologies - cve: "" - description: Zhone 6512-A1 ADSL gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: Zyxel_fsg2200_rsa.key - username: root - vendor: Zyxel - cve: "" - description: Zyxel FSG2200 NAS hardcoded RSA SSH host key (CERT VU#566724) - -- file: Zyxel_p870h_rsa.key - username: root - vendor: Zyxel - cve: "" - description: Zyxel P-870H-51a DSL gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: Zyxel_pmg1006_dsa.key - username: root - vendor: Zyxel - cve: "" - description: Zyxel PMG1006 gateway hardcoded DSA SSH host key (CERT VU#566724) - -- file: Zyxel_pmg1006_rsa.key - username: root - vendor: Zyxel - cve: "" - description: Zyxel PMG1006 gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: zyxel-q100_rsa.key - username: root - vendor: Zyxel - cve: "" - description: Zyxel Q100 gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: Zyxel_sbg3300_rsa.key - username: root - vendor: Zyxel - cve: "" - description: Zyxel SBG3300 gateway hardcoded RSA SSH host key (CERT VU#566724) - -- file: zyxel-vmg1312_rsa.key - username: root - vendor: Zyxel - cve: "" - description: Zyxel VMG1312 VDSL gateway hardcoded RSA SSH host key (CERT VU#566724) From e8f17eae761055bbb7844e43614888073ce221fc Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:46:34 -0500 Subject: [PATCH 09/49] =?UTF-8?q?fix(badkeys):=20empty-PEM=20guard,=20rena?= =?UTF-8?q?me=20Fingerprint=E2=86=92PEMHash,=20tighten=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a guard in Load() that returns an error if a key file embeds as zero bytes, catching corrupted or accidentally truncated vendored keys at startup rather than silently passing an empty slice to SSH auth. Rename Entry.Fingerprint to Entry.PEMHash with a clarifying comment: the field holds SHA-256 of the raw PEM file bytes, which differs from the OpenSSH-format fingerprint produced by `ssh-keygen -l -E sha256`. The old name implied the standard format, which would mislead callers. Tighten registry_test.go: exact-count assertion (9), new TestPEMHashIsHexSHA256 validating format of every entry's hash, and TestLoadIsDeterministic confirming stable ordering across two calls. --- brute/badkeys/registry.go | 7 +++-- brute/badkeys/registry_test.go | 53 ++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/brute/badkeys/registry.go b/brute/badkeys/registry.go index 9acc6dc..63e93be 100644 --- a/brute/badkeys/registry.go +++ b/brute/badkeys/registry.go @@ -19,7 +19,7 @@ type Entry struct { CVE string Description string PEM []byte - Fingerprint string // sha256 hex of PEM bytes + PEMHash string // SHA-256 of the raw PEM file bytes (NOT an OpenSSH-format fingerprint); used for change-detection across vendor updates } type metaEntry struct { @@ -45,6 +45,9 @@ func Load() ([]Entry, error) { if err != nil { return nil, fmt.Errorf("read keys/%s: %w", m.File, err) } + if len(pem) == 0 { + return nil, fmt.Errorf("keys/%s: file is empty", m.File) + } sum := sha256.Sum256(pem) out = append(out, Entry{ File: m.File, @@ -53,7 +56,7 @@ func Load() ([]Entry, error) { CVE: m.CVE, Description: m.Description, PEM: pem, - Fingerprint: hex.EncodeToString(sum[:]), + PEMHash: hex.EncodeToString(sum[:]), }) } return out, nil diff --git a/brute/badkeys/registry_test.go b/brute/badkeys/registry_test.go index 20fbd5f..99930c2 100644 --- a/brute/badkeys/registry_test.go +++ b/brute/badkeys/registry_test.go @@ -1,14 +1,17 @@ package badkeys -import "testing" +import ( + "encoding/hex" + "testing" +) func TestLoadReturnsNonEmptyBundle(t *testing.T) { bundle, err := Load() if err != nil { t.Fatalf("Load: %v", err) } - if len(bundle) < 5 { - t.Fatalf("expected >=5 keys, got %d", len(bundle)) + if len(bundle) != 9 { + t.Fatalf("expected 9 keys, got %d", len(bundle)) } } @@ -30,3 +33,47 @@ func TestLoadParsesVagrantEntry(t *testing.T) { } t.Fatal("no Vagrant entry found in bundle") } + +func TestPEMHashIsHexSHA256(t *testing.T) { + bundle, err := Load() + if err != nil { + t.Fatalf("Load: %v", err) + } + for _, e := range bundle { + if e.PEMHash == "" { + t.Errorf("entry %q has empty PEMHash", e.File) + continue + } + if len(e.PEMHash) != 64 { + t.Errorf("entry %q PEMHash length = %d, want 64", e.File, len(e.PEMHash)) + continue + } + b, err := hex.DecodeString(e.PEMHash) + if err != nil || len(b) != 32 { + t.Errorf("entry %q PEMHash %q is not valid lowercase hex SHA-256", e.File, e.PEMHash) + } + } +} + +func TestLoadIsDeterministic(t *testing.T) { + first, err := Load() + if err != nil { + t.Fatalf("Load (first): %v", err) + } + second, err := Load() + if err != nil { + t.Fatalf("Load (second): %v", err) + } + if len(first) != len(second) { + t.Fatalf("non-deterministic length: first=%d second=%d", len(first), len(second)) + } + for i := range first { + if first[i].PEMHash != second[i].PEMHash { + t.Errorf("entry %d (%s): PEMHash differs between calls: %q vs %q", + i, first[i].File, first[i].PEMHash, second[i].PEMHash) + } + if first[i].File != second[i].File { + t.Errorf("entry %d: File differs: %q vs %q", i, first[i].File, second[i].File) + } + } +} From 6ff17e0a869f8a824ec956109510d2d649b8a7d3 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:49:20 -0500 Subject: [PATCH 10/49] feat(ssh): execute embedded bad-key attempts via ::badkey:: marker --- brute/ssh.go | 84 +++++++++++++++++++++++++++++++++++++++ brute/ssh_badkeys_test.go | 36 +++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 brute/ssh_badkeys_test.go diff --git a/brute/ssh.go b/brute/ssh.go index 10d0358..4b3d2f9 100644 --- a/brute/ssh.go +++ b/brute/ssh.go @@ -4,18 +4,63 @@ import ( "fmt" "net" "os" + "strconv" "strings" "sync" "time" + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" "github.com/x90skysn3k/brutespray/v2/modules" "golang.org/x/crypto/ssh" ) +// BadKeyAttempt is one user+key pair to try during the bad-keys pass. +type BadKeyAttempt struct { + Username string + Entry badkeys.Entry +} + +// PlanBadKeyAttempts produces the ordered list of SSH bad-key attempts for a +// host. When userOverride is non-empty (operator passed -u explicitly), every +// attempt uses that username; otherwise the entry's metadata-suggested user +// is used (root for F5, vagrant for Vagrant, etc.). +func PlanBadKeyAttempts(bundle []badkeys.Entry, userOverride string) []BadKeyAttempt { + out := make([]BadKeyAttempt, 0, len(bundle)) + for _, e := range bundle { + u := e.Username + if userOverride != "" { + u = userOverride + } + out = append(out, BadKeyAttempt{Username: u, Entry: e}) + } + return out +} + // sshKeyCache caches key file contents to avoid re-reading on every attempt. var sshKeyCache sync.Map func BruteSSH(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + // Bad-keys pre-pass: when the magic password marker "::badkey::" is in play, + // the caller is asking us to attempt a single embedded bad-key. The dispatcher + // (Task A4) emits these as synthetic credential pairs before regular passwords. + if strings.HasPrefix(password, "::badkey::") { + idx, err := strconv.Atoi(strings.TrimPrefix(password, "::badkey::")) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, + Error: fmt.Errorf("invalid badkey index: %w", err)} + } + bundle, err := badkeys.Load() + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, + Error: fmt.Errorf("loading badkeys bundle: %w", err)} + } + if idx < 0 || idx >= len(bundle) { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, + Error: fmt.Errorf("badkey index out of range: %d", idx)} + } + return attemptBadKey(host, port, user, bundle[idx], timeout, cm) + } + var authMethod ssh.AuthMethod keyParam := params["key"] @@ -153,4 +198,43 @@ func BruteSSH(host string, port int, user, password string, timeout time.Duratio } } +func attemptBadKey(host string, port int, user string, e badkeys.Entry, + timeout time.Duration, cm *modules.ConnectionManager) *BruteResult { + signer, err := ssh.ParsePrivateKey(e.PEM) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("parsing badkey %s: %w", e.File, err)} + } + cfg := &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: timeout, + } + conn, err := cm.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port))) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + c, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, strconv.Itoa(port)), cfg) + if err != nil { + conn.Close() + if strings.Contains(err.Error(), "unable to authenticate") { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} + } + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + client := ssh.NewClient(c, chans, reqs) + defer client.Close() + return &BruteResult{ + AuthSuccess: true, + ConnectionSuccess: true, + KeyMatch: &KeyMatch{ + Fingerprint: e.PEMHash, + Vendor: e.Vendor, + CVE: e.CVE, + Description: e.Description, + }, + } +} + func init() { Register("ssh", BruteSSH) } diff --git a/brute/ssh_badkeys_test.go b/brute/ssh_badkeys_test.go new file mode 100644 index 0000000..41a8630 --- /dev/null +++ b/brute/ssh_badkeys_test.go @@ -0,0 +1,36 @@ +package brute + +import ( + "testing" + + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" +) + +func TestBadKeysPlanCoversBundle(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + plan := PlanBadKeyAttempts(bundle, "") + if len(plan) != len(bundle) { + t.Fatalf("plan size = %d, bundle = %d", len(plan), len(bundle)) + } + for _, a := range plan { + if a.Username == "" { + t.Fatalf("attempt missing username: %+v", a) + } + } +} + +func TestBadKeysPlanRespectsExplicitUser(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + plan := PlanBadKeyAttempts(bundle, "admin") + for _, a := range plan { + if a.Username != "admin" { + t.Fatalf("explicit username override failed: %s", a.Username) + } + } +} From a9658d829dc7f62b68e0bbab02b1a9695adeb82c Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 08:58:29 -0500 Subject: [PATCH 11/49] fix(ssh): handshake deadline, correct ConnectionSuccess on PEM error, banner capture --- brute/ssh.go | 22 ++++++++++++++++++---- brute/ssh_badkeys_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/brute/ssh.go b/brute/ssh.go index 4b3d2f9..e3d5a9f 100644 --- a/brute/ssh.go +++ b/brute/ssh.go @@ -36,6 +36,12 @@ func PlanBadKeyAttempts(bundle []badkeys.Entry, userOverride string) []BadKeyAtt return out } +// badKeyMarker is the synthetic password prefix used by the dispatcher to +// signal a bad-keys bundle attempt. The dispatcher emits passwords of the form +// "::badkey::" before the user's actual password list, and BruteSSH +// dispatches them to attemptBadKey. INTERNAL — not part of any public contract. +const badKeyMarker = "::badkey::" + // sshKeyCache caches key file contents to avoid re-reading on every attempt. var sshKeyCache sync.Map @@ -43,8 +49,8 @@ func BruteSSH(host string, port int, user, password string, timeout time.Duratio // Bad-keys pre-pass: when the magic password marker "::badkey::" is in play, // the caller is asking us to attempt a single embedded bad-key. The dispatcher // (Task A4) emits these as synthetic credential pairs before regular passwords. - if strings.HasPrefix(password, "::badkey::") { - idx, err := strconv.Atoi(strings.TrimPrefix(password, "::badkey::")) + if idxStr, ok := strings.CutPrefix(password, badKeyMarker); ok { + idx, err := strconv.Atoi(idxStr) if err != nil { return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: fmt.Errorf("invalid badkey index: %w", err)} @@ -200,21 +206,27 @@ func BruteSSH(host string, port int, user, password string, timeout time.Duratio func attemptBadKey(host string, port int, user string, e badkeys.Entry, timeout time.Duration, cm *modules.ConnectionManager) *BruteResult { + // Fix 2: PEM parsing happens before any network I/O; a parse failure must + // not set ConnectionSuccess=true (no network was touched, circuit-breaker + // must not be credited with a success counter). signer, err := ssh.ParsePrivateKey(e.PEM) if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: fmt.Errorf("parsing badkey %s: %w", e.File, err)} } cfg := &ssh.ClientConfig{ User: user, Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), - Timeout: timeout, } conn, err := cm.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port))) if err != nil { return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} } + // Fix 1: ssh.ClientConfig.Timeout is only honoured by ssh.Dial, not by + // ssh.NewClientConn. Set a deadline on the raw connection so a slow + // responder cannot stall this worker goroutine for the OS socket timeout. + _ = conn.SetDeadline(time.Now().Add(timeout)) c, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, strconv.Itoa(port)), cfg) if err != nil { conn.Close() @@ -225,9 +237,11 @@ func attemptBadKey(host string, port int, user string, e badkeys.Entry, } client := ssh.NewClient(c, chans, reqs) defer client.Close() + // Fix 3: capture server banner (high-value for a bad-key hit). return &BruteResult{ AuthSuccess: true, ConnectionSuccess: true, + Banner: string(c.ServerVersion()), KeyMatch: &KeyMatch{ Fingerprint: e.PEMHash, Vendor: e.Vendor, diff --git a/brute/ssh_badkeys_test.go b/brute/ssh_badkeys_test.go index 41a8630..3ae4736 100644 --- a/brute/ssh_badkeys_test.go +++ b/brute/ssh_badkeys_test.go @@ -2,8 +2,10 @@ package brute import ( "testing" + "time" "github.com/x90skysn3k/brutespray/v2/brute/badkeys" + "github.com/x90skysn3k/brutespray/v2/modules" ) func TestBadKeysPlanCoversBundle(t *testing.T) { @@ -34,3 +36,35 @@ func TestBadKeysPlanRespectsExplicitUser(t *testing.T) { } } } + +// TestAttemptBadKeyReturnsConnectionFailureOnBadPEM verifies that a corrupt +// PEM blob (which triggers a parse error before any network I/O) causes +// attemptBadKey to return ConnectionSuccess=false. Prior to the fix the +// function returned ConnectionSuccess=true, which incorrectly credited the +// circuit-breaker and broke the retry logic. +func TestAttemptBadKeyReturnsConnectionFailureOnBadPEM(t *testing.T) { + cm, err := modules.NewConnectionManager("", 5*time.Second) + if err != nil { + t.Fatalf("NewConnectionManager: %v", err) + } + + badEntry := badkeys.Entry{ + File: "test-invalid.pem", + Username: "root", + PEM: []byte("not a valid PEM key"), + } + + r := attemptBadKey("127.0.0.1", 22, "root", badEntry, 5*time.Second, cm) + if r == nil { + t.Fatal("attemptBadKey returned nil") + } + if r.ConnectionSuccess { + t.Error("expected ConnectionSuccess=false for bad PEM, got true") + } + if r.Error == nil { + t.Error("expected non-nil Error for bad PEM, got nil") + } + if r.AuthSuccess { + t.Error("expected AuthSuccess=false for bad PEM, got true") + } +} From 6cd42a6fdb753c77ac4000f16ccaeda5ead968f6 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:03:24 -0500 Subject: [PATCH 12/49] feat(dispatch): emit bad-key attempts for SSH targets with opt-out flags Adds --no-badkeys (skip the pre-pass) and --badkeys-only (pre-pass only, skip password list) flags, wires them through Config and WorkerPool, and injects BuildBadKeyCreds into ProcessHost before the regular credential loop. --- brutespray/brutespray.go | 4 +++ brutespray/config.go | 8 +++++ brutespray/dispatch.go | 44 +++++++++++++++++++++++ brutespray/dispatch_badkeys_test.go | 56 +++++++++++++++++++++++++++++ brutespray/pool.go | 3 ++ 5 files changed, 115 insertions(+) create mode 100644 brutespray/dispatch_badkeys_test.go diff --git a/brutespray/brutespray.go b/brutespray/brutespray.go index 2bc7ae3..29ce039 100644 --- a/brutespray/brutespray.go +++ b/brutespray/brutespray.go @@ -67,6 +67,8 @@ func executeTUI(cfg *Config, cm *modules.ConnectionManager, totalHosts int) { workerPool.sprayDelay = cfg.SprayDelay workerPool.useReversedPass = cfg.UseReversedPass workerPool.passwordGen = cfg.PasswordGen + workerPool.noBadKeys = cfg.NoBadKeys + workerPool.badKeysOnly = cfg.BadKeysOnly // Initialize checkpoint var replayEntries []modules.SessionEntry @@ -167,6 +169,8 @@ func executeLegacy(cfg *Config, cm *modules.ConnectionManager, totalHosts int) { workerPool.sprayDelay = cfg.SprayDelay workerPool.useReversedPass = cfg.UseReversedPass workerPool.passwordGen = cfg.PasswordGen + workerPool.noBadKeys = cfg.NoBadKeys + workerPool.badKeysOnly = cfg.BadKeysOnly // Initialize checkpoint for resume capability if cfg.ResumeFile != "" { diff --git a/brutespray/config.go b/brutespray/config.go index eee1271..a93b4a0 100644 --- a/brutespray/config.go +++ b/brutespray/config.go @@ -134,6 +134,8 @@ var helpGroups = []flagGroup{ {"-checkpoint", "file", "Checkpoint file (default: brutespray-checkpoint.json)"}, {"-resume", "file", "Resume from checkpoint"}, {"-allow-wrapper", "", "Allow wrapper module (executes commands)"}, + {"-no-badkeys", "", "Skip SSH bad-keys pre-pass for SSH targets"}, + {"-badkeys-only", "", "Run SSH bad-keys pre-pass only; skip password attempts"}, }, }, } @@ -235,6 +237,8 @@ type Config struct { UseUsernameAsPass bool UseReversedPass bool AllowWrapper bool + BadKeysOnly bool + NoBadKeys bool PasswordGenSpec string PasswordGen *modules.PasswordGenerator OutputFormat string @@ -295,6 +299,8 @@ func ParseConfig() *Config { domain := flag.String("d", "", "Domain to use for RDP authentication (optional)") noColor := flag.Bool("nc", false, "Disable colored output") stopOnSuccess := flag.Bool("stop-on-success", false, "Stop testing a host after finding valid credentials") + noBadKeys := flag.Bool("no-badkeys", false, "Skip SSH bad-keys pre-pass for SSH targets") + badKeysOnly := flag.Bool("badkeys-only", false, "Run SSH bad-keys pre-pass only; skip password attempts") rateLimit := flag.Float64("rate", 0, "Per-host rate limit in attempts/second; fractional values supported (e.g. 0.1 = 1 attempt every 10s; 0 = unlimited)") attemptDelay := flag.Duration("delay", 0, "Per-host delay between attempts (e.g. 10s); alias for -rate, mutually exclusive") sprayMode := flag.Bool("spray", false, "Spray mode: try each password across all users before next password (avoids lockouts)") @@ -428,6 +434,8 @@ func ParseConfig() *Config { cfg.Domain = *domain cfg.NoColor = *noColor cfg.StopOnSuccess = *stopOnSuccess + cfg.NoBadKeys = *noBadKeys + cfg.BadKeysOnly = *badKeysOnly cfg.RateLimit = *rateLimit if *attemptDelay > 0 { if cfg.RateLimit > 0 { diff --git a/brutespray/dispatch.go b/brutespray/dispatch.go index d24db27..3ac2370 100644 --- a/brutespray/dispatch.go +++ b/brutespray/dispatch.go @@ -1,14 +1,43 @@ package brutespray import ( + "fmt" "time" "github.com/pterm/pterm" "github.com/x90skysn3k/brutespray/v2/brute" + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" "github.com/x90skysn3k/brutespray/v2/modules" "github.com/x90skysn3k/brutespray/v2/tui" ) +// BadKeyCred is a synthetic user/password pair for the SSH bad-keys pre-pass. +// Password carries the marker "::badkey::N" where N indexes into the bundle; +// BruteSSH unpacks this marker (see brute/ssh.go:badKeyMarker). +type BadKeyCred struct { + User string + Password string +} + +// BuildBadKeyCreds turns the embedded bad-keys bundle into a list of synthetic +// credential pairs. When userOverride is set (operator passed -u explicitly), +// every pair uses that username; otherwise each entry's metadata-suggested +// user is used (root for F5, vagrant for Vagrant, etc.). +func BuildBadKeyCreds(bundle []badkeys.Entry, userOverride string) []BadKeyCred { + out := make([]BadKeyCred, 0, len(bundle)) + for i, e := range bundle { + u := e.Username + if userOverride != "" { + u = userOverride + } + out = append(out, BadKeyCred{ + User: u, + Password: fmt.Sprintf("::badkey::%d", i), + }) + } + return out +} + // reverseString returns the reversed version of a string. func reverseString(s string) string { runes := []rune(s) @@ -185,6 +214,21 @@ func (wp *WorkerPool) ProcessHost(host modules.Host, service string, combo strin } } + // SSH bad-keys pre-pass: try the embedded bundle before any password list. + // Opt-out via --no-badkeys; --badkeys-only short-circuits the regular loop. + if service == "ssh" && !wp.noBadKeys { + if bundle, err := badkeys.Load(); err == nil { + for _, pair := range BuildBadKeyCreds(bundle, user) { + if !queueCred(pair.User, pair.Password) { + break + } + } + } + } + if service == "ssh" && wp.badKeysOnly { + return + } + if wp.sprayMode { // Spray: try each password across all users before next password diff --git a/brutespray/dispatch_badkeys_test.go b/brutespray/dispatch_badkeys_test.go new file mode 100644 index 0000000..23ea464 --- /dev/null +++ b/brutespray/dispatch_badkeys_test.go @@ -0,0 +1,56 @@ +package brutespray + +import ( + "fmt" + "testing" + + "github.com/x90skysn3k/brutespray/v2/brute/badkeys" +) + +func TestBuildBadKeyCredsProducesMarkers(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + pairs := BuildBadKeyCreds(bundle, "") + if len(pairs) != len(bundle) { + t.Fatalf("got %d pairs, want %d", len(pairs), len(bundle)) + } + for i, p := range pairs { + wantPass := fmt.Sprintf("::badkey::%d", i) + if p.Password != wantPass { + t.Fatalf("pair[%d].Password = %q, want %q", i, p.Password, wantPass) + } + } +} + +func TestBuildBadKeyCredsRespectsExplicitUser(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + pairs := BuildBadKeyCreds(bundle, "admin") + for _, p := range pairs { + if p.User != "admin" { + t.Fatalf("user override failed: %q", p.User) + } + } +} + +func TestBuildBadKeyCredsUsesMetadataUserByDefault(t *testing.T) { + bundle, err := badkeys.Load() + if err != nil { + t.Fatalf("badkeys.Load: %v", err) + } + pairs := BuildBadKeyCreds(bundle, "") + // Find the Vagrant entry and confirm its pair uses "vagrant" as user + for i, e := range bundle { + if e.Vendor == "HashiCorp Vagrant" { + if pairs[i].User != "vagrant" { + t.Fatalf("vagrant default user not preserved: got %q", pairs[i].User) + } + return + } + } + t.Fatal("no Vagrant entry in bundle") +} diff --git a/brutespray/pool.go b/brutespray/pool.go index e13c549..6a2345b 100644 --- a/brutespray/pool.go +++ b/brutespray/pool.go @@ -91,6 +91,9 @@ type WorkerPool struct { // Extra credential options useReversedPass bool passwordGen *modules.PasswordGenerator + // SSH bad-keys pre-pass control + noBadKeys bool + badKeysOnly bool } // NewHostWorkerPool creates a new host-specific worker pool From 21c5628e58ab443dc35458127b859c0de54a629d Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:06:23 -0500 Subject: [PATCH 13/49] style: gofmt brutespray/dispatch.go after A4 insertion --- brutespray/dispatch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brutespray/dispatch.go b/brutespray/dispatch.go index 3ac2370..c3cba45 100644 --- a/brutespray/dispatch.go +++ b/brutespray/dispatch.go @@ -229,7 +229,7 @@ func (wp *WorkerPool) ProcessHost(host modules.Host, service string, combo strin return } - if wp.sprayMode { + if wp.sprayMode { // Spray: try each password across all users before next password // Prepend username-as-password round if -e s From fcb3263db0b210a00eae37ddea44792fa5e911f1 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:10:38 -0500 Subject: [PATCH 14/49] fix(dispatch): handle -u wordlist path in bad-keys pre-pass + log load errors When -u is a file path, os.Stat detects it so the bad-keys pre-pass uses each entry's metadata-suggested username instead of the literal path string. Bundle load failures now emit a warning to stderr rather than silently skipping. Adds NOTE comment on early return bypassing jobQueue close for future cleanup. --- brutespray/dispatch.go | 19 ++++++++++++++++++- brutespray/dispatch_badkeys_test.go | 7 +++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/brutespray/dispatch.go b/brutespray/dispatch.go index c3cba45..8f26bd9 100644 --- a/brutespray/dispatch.go +++ b/brutespray/dispatch.go @@ -2,6 +2,7 @@ package brutespray import ( "fmt" + "os" "time" "github.com/pterm/pterm" @@ -217,15 +218,31 @@ func (wp *WorkerPool) ProcessHost(host modules.Host, service string, combo strin // SSH bad-keys pre-pass: try the embedded bundle before any password list. // Opt-out via --no-badkeys; --badkeys-only short-circuits the regular loop. if service == "ssh" && !wp.noBadKeys { + // effectiveBadKeyUser is the username to apply across the bad-keys pre-pass. + // When -u is a file path (wordlist), we cannot use a single value — fall back + // to each entry's metadata-suggested user. When -u is a bare username, use it + // as the override. + effectiveBadKeyUser := "" + if user != "" { + if _, statErr := os.Stat(user); statErr != nil { + // Not a file → treat as a bare username + effectiveBadKeyUser = user + } + } if bundle, err := badkeys.Load(); err == nil { - for _, pair := range BuildBadKeyCreds(bundle, user) { + for _, pair := range BuildBadKeyCreds(bundle, effectiveBadKeyUser) { if !queueCred(pair.User, pair.Password) { break } } + } else { + fmt.Fprintf(os.Stderr, "warning: bad-keys bundle load failed (skipping pre-pass): %v\n", err) } } if service == "ssh" && wp.badKeysOnly { + // NOTE: --badkeys-only returns before the regular cred loop, which means + // hostPool.jobQueue is not closed here. Global wp.Stop() handles eventual + // cleanup; tighten if profiling shows the premature exit matters. return } diff --git a/brutespray/dispatch_badkeys_test.go b/brutespray/dispatch_badkeys_test.go index 23ea464..77dbdcf 100644 --- a/brutespray/dispatch_badkeys_test.go +++ b/brutespray/dispatch_badkeys_test.go @@ -7,6 +7,13 @@ import ( "github.com/x90skysn3k/brutespray/v2/brute/badkeys" ) +func TestValidateRejectsContradictoryBadKeyFlags(t *testing.T) { + cfg := &Config{NoBadKeys: true, BadKeysOnly: true, ServiceType: "all"} + if err := cfg.Validate(); err == nil { + t.Fatal("expected error for --no-badkeys + --badkeys-only, got nil") + } +} + func TestBuildBadKeyCredsProducesMarkers(t *testing.T) { bundle, err := badkeys.Load() if err != nil { From 9c87f8a7b82cf1abfb71027e2649435b820a99ad Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:10:44 -0500 Subject: [PATCH 15/49] fix(config): reject --no-badkeys with --badkeys-only Both flags together silently produce no SSH attempts. Validate() now returns an error when both are set. Adds TestValidateRejectsContradictoryBadKeyFlags. Also applies gofmt alignment normalization to the Config struct. --- brutespray/config.go | 87 +++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/brutespray/config.go b/brutespray/config.go index a93b4a0..9980a55 100644 --- a/brutespray/config.go +++ b/brutespray/config.go @@ -200,48 +200,48 @@ func customUsage() { // Config holds all parsed configuration for a brutespray run type Config struct { - User string - Password string - Combo string - Output string - Summary bool - NoStats bool - Silent bool - LogEvery int - Threads int - HostParallelism int - SocksProxy string - ProxyList string - NetInterface string - ServiceType string - File string - HostArgs hostListFlag - Quiet bool - Timeout time.Duration - Retry int - PrintHosts bool - Domain string - NoColor bool - StopOnSuccess bool - RateLimit float64 - SprayMode bool - SprayDelay time.Duration - ResumeFile string - CheckpointFile string - ConfigFile string - TUI bool - Hosts []modules.Host - SupportedServices []string - TotalCombinations int - ModuleParams map[string]string - UseUsernameAsPass bool - UseReversedPass bool - AllowWrapper bool - BadKeysOnly bool - NoBadKeys bool - PasswordGenSpec string - PasswordGen *modules.PasswordGenerator - OutputFormat string + User string + Password string + Combo string + Output string + Summary bool + NoStats bool + Silent bool + LogEvery int + Threads int + HostParallelism int + SocksProxy string + ProxyList string + NetInterface string + ServiceType string + File string + HostArgs hostListFlag + Quiet bool + Timeout time.Duration + Retry int + PrintHosts bool + Domain string + NoColor bool + StopOnSuccess bool + RateLimit float64 + SprayMode bool + SprayDelay time.Duration + ResumeFile string + CheckpointFile string + ConfigFile string + TUI bool + Hosts []modules.Host + SupportedServices []string + TotalCombinations int + ModuleParams map[string]string + UseUsernameAsPass bool + UseReversedPass bool + AllowWrapper bool + BadKeysOnly bool + NoBadKeys bool + PasswordGenSpec string + PasswordGen *modules.PasswordGenerator + OutputFormat string } // Validate checks for mutually exclusive flags, contradictory options, @@ -252,6 +252,9 @@ func (cfg *Config) Validate() error { if cfg.User != "" && cfg.Combo != "" { return fmt.Errorf("-u and -C are mutually exclusive") } + if cfg.NoBadKeys && cfg.BadKeysOnly { + return fmt.Errorf("--no-badkeys and --badkeys-only are mutually exclusive") + } // Contradictory flags (warn, don't error) if cfg.SprayMode && cfg.StopOnSuccess { From 7f51e321c0c29959a61f2c65f58848dbf4dc8636 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:15:58 -0500 Subject: [PATCH 16/49] feat(rdp): pre-auth NLA fingerprint scan Add FingerprintNLA to grdp client, wire local sibling via replace directive, and implement nlaFinding/ScanRDPRecon in brute/rdp.go. --- brute/rdp.go | 39 +++++++++++++++++++++++++++++++++++++++ brute/rdp_nla_test.go | 28 ++++++++++++++++++++++++++++ go.mod | 4 ++++ go.sum | 6 ++++-- 4 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 brute/rdp_nla_test.go diff --git a/brute/rdp.go b/brute/rdp.go index ccbfc4f..65518e7 100644 --- a/brute/rdp.go +++ b/brute/rdp.go @@ -52,3 +52,42 @@ func BruteRDP(host string, port int, user, password string, timeout time.Duratio } func init() { Register("rdp", BruteRDP) } + +func nlaFinding(status string) *Finding { + switch status { + case "required": + return &Finding{Severity: "INFO", Code: "rdp-nla-required", + Message: "NLA (CredSSP) enforced"} + case "not-enforced": + return &Finding{Severity: "WARN", Code: "rdp-nla-missing", + Message: "NLA not enforced — server accepts standard RDP without pre-auth"} + case "hybrid-ex": + return &Finding{Severity: "INFO", Code: "rdp-nla-hybridex", + Message: "HybridEx (NLA + CredSSP early-user auth) enforced"} + } + return nil +} + +// ScanRDPRecon runs pre-auth RDP recon (NLA fingerprint, sticky-keys probe) +// against a single target. Returns a slice of findings to emit. Called once +// per host by the dispatcher before any brute attempts. +func ScanRDPRecon(host string, port int, timeout time.Duration) []*Finding { + target := fmt.Sprintf("%s:%d", host, port) + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + status, err := client.FingerprintNLA(ctx, target, timeout) + if err != nil { + return nil + } + var out []*Finding + switch status { + case client.NLARequired: + out = append(out, nlaFinding("required")) + case client.NLANotEnforced: + out = append(out, nlaFinding("not-enforced")) + case client.NLAHybridEx: + out = append(out, nlaFinding("hybrid-ex")) + } + // Sticky-keys probe slots in here in Task A6. + return out +} diff --git a/brute/rdp_nla_test.go b/brute/rdp_nla_test.go new file mode 100644 index 0000000..067bbd2 --- /dev/null +++ b/brute/rdp_nla_test.go @@ -0,0 +1,28 @@ +package brute + +import "testing" + +func TestNLAFindingFromStatus(t *testing.T) { + cases := []struct { + status string + wantSev string + wantCode string + }{ + {"required", "INFO", "rdp-nla-required"}, + {"not-enforced", "WARN", "rdp-nla-missing"}, + {"hybrid-ex", "INFO", "rdp-nla-hybridex"}, + {"unknown", "", ""}, + } + for _, c := range cases { + f := nlaFinding(c.status) + if c.wantCode == "" { + if f != nil { + t.Fatalf("status %q: expected nil finding, got %+v", c.status, f) + } + continue + } + if f == nil || f.Severity != c.wantSev || f.Code != c.wantCode { + t.Fatalf("status %q: got %+v want sev=%s code=%s", c.status, f, c.wantSev, c.wantCode) + } + } +} diff --git a/go.mod b/go.mod index e67ad45..6f6a94c 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,8 @@ module github.com/x90skysn3k/brutespray/v2 go 1.26.1 +replace github.com/x90skysn3k/grdp => ../grdp + require ( github.com/Azure/go-ntlmssp v0.1.1 github.com/charmbracelet/bubbles v1.0.0 @@ -63,6 +65,8 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358 // indirect + github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect diff --git a/go.sum b/go.sum index 7cac90a..6142359 100644 --- a/go.sum +++ b/go.sum @@ -151,6 +151,10 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358 h1:hVXNJ57IHkOA8FBq80UG263MEBwNUMfS9c82J2QE5UQ= +github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358/go.mod h1:qBE210J2T9uLXRB3GNc73SvZACDEFAmDCOlDkV47zbY= +github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5 h1:ZcsPFW8UgACapqjcrBJx0PuyT4ppArO5VFn0vgnkvmc= +github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5/go.mod h1:VJNHW2GxCtQP/IQtXykBIPBV8maPJ/dHWirVTwm9GwY= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -272,8 +276,6 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= -github.com/x90skysn3k/grdp v1.0.2 h1:HkhPK1rDAbSyZY6OEqxOLA/FPly15IBVnVgzAnS7fCY= -github.com/x90skysn3k/grdp v1.0.2/go.mod h1:brKUl7Rx/wJ9hA3ZlHa9+zC14ye/GrCQKbGW3si7/yE= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= From a1a9a63e75b23df163328fba65547cdd692e0090 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:25:50 -0500 Subject: [PATCH 17/49] feat(rdp): sticky-keys backdoor pre-auth probe with framebuffer heuristic ScanRDPRecon now calls CaptureLogonScreen when NLA is not enforced, captures before/after PNG framebuffer snapshots around 5x Shift presses, and emits a CRITICAL finding when the after-snapshot looks like a cmd.exe console (>65% black, 2-15% white pixels in top-left 400x200 region) or an INFO/inconclusive finding when the screen changed but the console heuristic did not fire. Adds looksLikeCmdConsole, framebuffersDiffer, stickyKeysVerdict helpers and a unit-test file covering all verdict paths plus edge cases. --- brute/rdp.go | 97 +++++++++++++++++++++++++++++++++++- brute/rdp_stickykeys_test.go | 54 ++++++++++++++++++++ 2 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 brute/rdp_stickykeys_test.go diff --git a/brute/rdp.go b/brute/rdp.go index 65518e7..f20c70c 100644 --- a/brute/rdp.go +++ b/brute/rdp.go @@ -1,9 +1,12 @@ package brute import ( + "bytes" "context" "errors" "fmt" + "image" + _ "image/png" "io" "log" "time" @@ -88,6 +91,98 @@ func ScanRDPRecon(host string, port int, timeout time.Duration) []*Finding { case client.NLAHybridEx: out = append(out, nlaFinding("hybrid-ex")) } - // Sticky-keys probe slots in here in Task A6. + // Sticky-keys probe: only meaningful when the server accepts standard RDP + // (no NLA), because that's the only mode where the GINA/logon screen is + // reachable without credentials. + if status == client.NLANotEnforced { + c := &client.RdpClient{} + before, after, stickyErr := c.CaptureLogonScreen(ctx, target, client.TriggerShift5x, timeout) + c.Close() + if stickyErr == nil { + if f := stickyKeysVerdict( + looksLikeCmdConsole(before), + looksLikeCmdConsole(after), + framebuffersDiffer(before, after), + ); f != nil { + out = append(out, f) + } + } + } return out } + +// stickyKeysVerdict produces a Finding (or nil) from the before/after analysis. +func stickyKeysVerdict(beforeCmd, afterCmd, differ bool) *Finding { + if !differ { + return nil + } + if afterCmd && !beforeCmd { + return &Finding{ + Severity: "CRITICAL", + Code: "rdp-stickykeys", + Message: "sticky-keys backdoor detected (cmd.exe shell at logon screen)", + } + } + return &Finding{ + Severity: "INFO", + Code: "rdp-stickykeys-inconclusive", + Message: "logon screen reacted to sticky-keys trigger but no console signature detected; manual verification recommended", + } +} + +// looksLikeCmdConsole applies a pixel-ratio heuristic to a PNG-encoded +// framebuffer snapshot: a cmd.exe window in its default colour scheme +// covers the top-left region of the screen with predominantly black +// pixels (background) and a small proportion of white pixels (text). +func looksLikeCmdConsole(pngBytes []byte) bool { + if len(pngBytes) == 0 { + return false + } + img, _, err := image.Decode(bytes.NewReader(pngBytes)) + if err != nil { + return false + } + b := img.Bounds() + maxX := b.Min.X + 400 + if maxX > b.Max.X { + maxX = b.Max.X + } + maxY := b.Min.Y + 200 + if maxY > b.Max.Y { + maxY = b.Max.Y + } + var total, black, white int + for y := b.Min.Y; y < maxY; y++ { + for x := b.Min.X; x < maxX; x++ { + r, g, bl, _ := img.At(x, y).RGBA() + r >>= 8 + g >>= 8 + bl >>= 8 + total++ + switch { + case r < 32 && g < 32 && bl < 32: + black++ + case r > 200 && g > 200 && bl > 200: + white++ + } + } + } + if total == 0 { + return false + } + blackPct := float64(black) / float64(total) + whitePct := float64(white) / float64(total) + return blackPct > 0.65 && whitePct > 0.02 && whitePct < 0.15 +} + +// framebuffersDiffer reports whether two PNG-encoded framebuffer snapshots +// differ at the byte level. A length difference also counts as a difference. +func framebuffersDiffer(a, b []byte) bool { + if len(a) == 0 || len(b) == 0 { + return false + } + if len(a) != len(b) { + return true + } + return !bytes.Equal(a, b) +} diff --git a/brute/rdp_stickykeys_test.go b/brute/rdp_stickykeys_test.go new file mode 100644 index 0000000..dff659f --- /dev/null +++ b/brute/rdp_stickykeys_test.go @@ -0,0 +1,54 @@ +package brute + +import "testing" + +func TestStickyKeysVerdictFromFlags(t *testing.T) { + cases := []struct { + name string + beforeIsCmdLike bool + afterIsCmdLike bool + differ bool + wantSev string + wantCode string + }{ + {"identical-no-change", false, false, false, "", ""}, + {"change-but-not-cmd", false, false, true, "INFO", "rdp-stickykeys-inconclusive"}, + {"change-to-cmd", false, true, true, "CRITICAL", "rdp-stickykeys"}, + } + for _, c := range cases { + got := stickyKeysVerdict(c.beforeIsCmdLike, c.afterIsCmdLike, c.differ) + if c.wantCode == "" { + if got != nil { + t.Fatalf("%s: want nil, got %+v", c.name, got) + } + continue + } + if got == nil || got.Severity != c.wantSev || got.Code != c.wantCode { + t.Fatalf("%s: got %+v want sev=%s code=%s", c.name, got, c.wantSev, c.wantCode) + } + } +} + +func TestFramebuffersDiffer(t *testing.T) { + if framebuffersDiffer(nil, nil) { + t.Fatal("nil != nil") + } + if !framebuffersDiffer([]byte{1, 2, 3}, []byte{1, 2, 4}) { + t.Fatal("differ failed") + } + if framebuffersDiffer([]byte{1, 2, 3}, []byte{1, 2, 3}) { + t.Fatal("same flagged differ") + } + if !framebuffersDiffer([]byte{1, 2, 3}, []byte{1, 2, 3, 4}) { + t.Fatal("length diff missed") + } +} + +func TestLooksLikeCmdConsoleOnGarbage(t *testing.T) { + if looksLikeCmdConsole(nil) { + t.Fatal("nil should be false") + } + if looksLikeCmdConsole([]byte("not a png")) { + t.Fatal("garbage should be false") + } +} From 45f086c15fc5f93118be1f7fc4c69385b8239804 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:26:48 -0500 Subject: [PATCH 18/49] chore: tidy go.mod after grdp replace directive --- go.mod | 2 -- go.sum | 4 ---- 2 files changed, 6 deletions(-) diff --git a/go.mod b/go.mod index 6f6a94c..1a5ba3e 100644 --- a/go.mod +++ b/go.mod @@ -65,8 +65,6 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358 // indirect - github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect diff --git a/go.sum b/go.sum index 6142359..760ab93 100644 --- a/go.sum +++ b/go.sum @@ -151,10 +151,6 @@ github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRKbaRTZI= github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358 h1:hVXNJ57IHkOA8FBq80UG263MEBwNUMfS9c82J2QE5UQ= -github.com/huin/asn1ber v0.0.0-20120622192748-af09f62e6358/go.mod h1:qBE210J2T9uLXRB3GNc73SvZACDEFAmDCOlDkV47zbY= -github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5 h1:ZcsPFW8UgACapqjcrBJx0PuyT4ppArO5VFn0vgnkvmc= -github.com/icodeface/tls v0.0.0-20190904083142-17aec93c60e5/go.mod h1:VJNHW2GxCtQP/IQtXykBIPBV8maPJ/dHWirVTwm9GwY= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= From 1c303fd0cf097f5e36015abe478df5890adfea17 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:26:55 -0500 Subject: [PATCH 19/49] chore: gitignore go.work for local sibling-repo dev setup --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 095f96f..9cc4af0 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ wordlist/winrm/ wordlist/asterisk/ wordlist/teamspeak/ wordlist/xmpp/ +go.work +go.work.sum From 23c8806f5ac2b798c65d362c6cb8ccd61699a1d0 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:30:21 -0500 Subject: [PATCH 20/49] fix(rdp): surface sticky-keys probe errors to stderr instead of dropping --- brute/rdp.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/brute/rdp.go b/brute/rdp.go index f20c70c..fa0a727 100644 --- a/brute/rdp.go +++ b/brute/rdp.go @@ -9,6 +9,7 @@ import ( _ "image/png" "io" "log" + "os" "time" "github.com/x90skysn3k/brutespray/v2/modules" @@ -106,6 +107,11 @@ func ScanRDPRecon(host string, port int, timeout time.Duration) []*Finding { ); f != nil { out = append(out, f) } + } else { + // Probe error suppressed from output — emitting a stickykeys + // finding without a successful capture would mislead. Diagnostics + // land on stderr for operators who care. + fmt.Fprintf(os.Stderr, "rdp sticky-keys probe %s: %v\n", target, stickyErr) } } return out From 2e588fe677f10e133a9610735bf7f51f086d13e8 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:32:55 -0500 Subject: [PATCH 21/49] feat(rdp): wire pre-auth RDP recon into dispatcher with --no-rdp-scan opt-out --- brutespray/brutespray.go | 2 ++ brutespray/config.go | 4 +++ brutespray/dispatch.go | 22 +++++++++++++++++ brutespray/dispatch_rdp_test.go | 44 +++++++++++++++++++++++++++++++++ brutespray/pool.go | 2 ++ 5 files changed, 74 insertions(+) create mode 100644 brutespray/dispatch_rdp_test.go diff --git a/brutespray/brutespray.go b/brutespray/brutespray.go index 29ce039..9f7e269 100644 --- a/brutespray/brutespray.go +++ b/brutespray/brutespray.go @@ -69,6 +69,7 @@ func executeTUI(cfg *Config, cm *modules.ConnectionManager, totalHosts int) { workerPool.passwordGen = cfg.PasswordGen workerPool.noBadKeys = cfg.NoBadKeys workerPool.badKeysOnly = cfg.BadKeysOnly + workerPool.noRDPScan = cfg.NoRDPScan // Initialize checkpoint var replayEntries []modules.SessionEntry @@ -171,6 +172,7 @@ func executeLegacy(cfg *Config, cm *modules.ConnectionManager, totalHosts int) { workerPool.passwordGen = cfg.PasswordGen workerPool.noBadKeys = cfg.NoBadKeys workerPool.badKeysOnly = cfg.BadKeysOnly + workerPool.noRDPScan = cfg.NoRDPScan // Initialize checkpoint for resume capability if cfg.ResumeFile != "" { diff --git a/brutespray/config.go b/brutespray/config.go index 9980a55..562f83e 100644 --- a/brutespray/config.go +++ b/brutespray/config.go @@ -136,6 +136,7 @@ var helpGroups = []flagGroup{ {"-allow-wrapper", "", "Allow wrapper module (executes commands)"}, {"-no-badkeys", "", "Skip SSH bad-keys pre-pass for SSH targets"}, {"-badkeys-only", "", "Run SSH bad-keys pre-pass only; skip password attempts"}, + {"-no-rdp-scan", "", "Skip pre-auth RDP recon (NLA fingerprint, sticky-keys probe)"}, }, }, } @@ -239,6 +240,7 @@ type Config struct { AllowWrapper bool BadKeysOnly bool NoBadKeys bool + NoRDPScan bool PasswordGenSpec string PasswordGen *modules.PasswordGenerator OutputFormat string @@ -304,6 +306,7 @@ func ParseConfig() *Config { stopOnSuccess := flag.Bool("stop-on-success", false, "Stop testing a host after finding valid credentials") noBadKeys := flag.Bool("no-badkeys", false, "Skip SSH bad-keys pre-pass for SSH targets") badKeysOnly := flag.Bool("badkeys-only", false, "Run SSH bad-keys pre-pass only; skip password attempts") + noRDPScan := flag.Bool("no-rdp-scan", false, "Skip pre-auth RDP recon (NLA fingerprint, sticky-keys probe)") rateLimit := flag.Float64("rate", 0, "Per-host rate limit in attempts/second; fractional values supported (e.g. 0.1 = 1 attempt every 10s; 0 = unlimited)") attemptDelay := flag.Duration("delay", 0, "Per-host delay between attempts (e.g. 10s); alias for -rate, mutually exclusive") sprayMode := flag.Bool("spray", false, "Spray mode: try each password across all users before next password (avoids lockouts)") @@ -439,6 +442,7 @@ func ParseConfig() *Config { cfg.StopOnSuccess = *stopOnSuccess cfg.NoBadKeys = *noBadKeys cfg.BadKeysOnly = *badKeysOnly + cfg.NoRDPScan = *noRDPScan cfg.RateLimit = *rateLimit if *attemptDelay > 0 { if cfg.RateLimit > 0 { diff --git a/brutespray/dispatch.go b/brutespray/dispatch.go index 8f26bd9..b719d99 100644 --- a/brutespray/dispatch.go +++ b/brutespray/dispatch.go @@ -48,6 +48,18 @@ func reverseString(s string) string { return string(runes) } +// emitFinding writes a pre-auth recon finding to the operator's output stream. +// Task A8 replaces this with text/JSONL/TUI dispatch; for now it lands on +// stderr so findings are visible during development. +func emitFinding(host modules.Host, f *brute.Finding) { + cve := "" + if f.CVE != "" { + cve = " (" + f.CVE + ")" + } + fmt.Fprintf(os.Stderr, "[%s] %s %s:%d %s%s\n", + f.Severity, host.Service, host.Host, host.Port, f.Message, cve) +} + // ProcessHost processes a single host with all its credentials using dedicated host worker pool func (wp *WorkerPool) ProcessHost(host modules.Host, service string, combo string, user string, password string, version string, timeout time.Duration, retry int, output string, cm *modules.ConnectionManager, domain string, moduleParams brute.ModuleParams, useUsernameAsPass bool) { // Skip hosts already completed in a previous run @@ -239,6 +251,16 @@ func (wp *WorkerPool) ProcessHost(host modules.Host, service string, combo strin fmt.Fprintf(os.Stderr, "warning: bad-keys bundle load failed (skipping pre-pass): %v\n", err) } } + // RDP pre-auth recon: NLA fingerprint + sticky-keys probe. + // Opt-out via --no-rdp-scan. Unlike --badkeys-only there is no + // RDP-scan-only mode — regular cred attempts always continue after. + if service == "rdp" && !wp.noRDPScan { + findings := brute.ScanRDPRecon(host.Host, host.Port, timeout) + for _, f := range findings { + emitFinding(host, f) + } + } + if service == "ssh" && wp.badKeysOnly { // NOTE: --badkeys-only returns before the regular cred loop, which means // hostPool.jobQueue is not closed here. Global wp.Stop() handles eventual diff --git a/brutespray/dispatch_rdp_test.go b/brutespray/dispatch_rdp_test.go new file mode 100644 index 0000000..e36e5a8 --- /dev/null +++ b/brutespray/dispatch_rdp_test.go @@ -0,0 +1,44 @@ +package brutespray + +import ( + "bytes" + "io" + "os" + "strings" + "testing" + + "github.com/x90skysn3k/brutespray/v2/brute" + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func captureStderr(fn func()) string { + old := os.Stderr + r, w, _ := os.Pipe() + os.Stderr = w + fn() + w.Close() + os.Stderr = old + var buf bytes.Buffer + io.Copy(&buf, r) + return buf.String() +} + +func TestEmitFindingFormat(t *testing.T) { + h := modules.Host{Service: "rdp", Host: "10.0.0.5", Port: 3389} + f := &brute.Finding{Severity: "WARN", Code: "rdp-nla-missing", Message: "NLA not enforced"} + out := captureStderr(func() { emitFinding(h, f) }) + for _, want := range []string{"WARN", "rdp", "10.0.0.5:3389", "NLA not enforced"} { + if !strings.Contains(out, want) { + t.Fatalf("output missing %q: %s", want, out) + } + } +} + +func TestEmitFindingIncludesCVE(t *testing.T) { + h := modules.Host{Service: "ssh", Host: "10.0.0.5", Port: 22} + f := &brute.Finding{Severity: "HIGH", Code: "ssh-badkey", Message: "F5 default key", CVE: "CVE-2012-1493"} + out := captureStderr(func() { emitFinding(h, f) }) + if !strings.Contains(out, "CVE-2012-1493") { + t.Fatalf("CVE missing from output: %s", out) + } +} diff --git a/brutespray/pool.go b/brutespray/pool.go index 6a2345b..b3f50fc 100644 --- a/brutespray/pool.go +++ b/brutespray/pool.go @@ -94,6 +94,8 @@ type WorkerPool struct { // SSH bad-keys pre-pass control noBadKeys bool badKeysOnly bool + // RDP pre-auth recon control + noRDPScan bool } // NewHostWorkerPool creates a new host-specific worker pool From 232154650f98ee1fa32a443d4fb89d03a48ddec3 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:36:56 -0500 Subject: [PATCH 22/49] feat(output): render Finding (text+JSONL+TUI) and KeyMatch (BADKEY success) --- brute/run.go | 4 ++ brutespray/dispatch.go | 12 ++--- brutespray/dispatch_rdp_test.go | 18 ++++--- modules/output.go | 92 +++++++++++++++++++++++++++++++++ modules/output_finding_test.go | 85 ++++++++++++++++++++++++++++++ 5 files changed, 196 insertions(+), 15 deletions(-) create mode 100644 modules/output_finding_test.go diff --git a/brute/run.go b/brute/run.go index 593a94b..f63eaeb 100644 --- a/brute/run.go +++ b/brute/run.go @@ -221,6 +221,10 @@ func RunBrute(h modules.Host, u string, p string, timeout time.Duration, maxRetr if result { // Authentication succeeded modules.RecordSuccess(service, h.Host, h.Port, u, p, time.Since(startTime), modResult.Banner) + if modResult.KeyMatch != nil { + modules.PrintBadKeyResult(service, h.Host, h.Port, u, + modResult.KeyMatch.Vendor, modResult.KeyMatch.CVE, modResult.KeyMatch.Description) + } } else { // Authentication failed modules.RecordError(false) // Authentication error diff --git a/brutespray/dispatch.go b/brutespray/dispatch.go index b719d99..d411fba 100644 --- a/brutespray/dispatch.go +++ b/brutespray/dispatch.go @@ -48,16 +48,10 @@ func reverseString(s string) string { return string(runes) } -// emitFinding writes a pre-auth recon finding to the operator's output stream. -// Task A8 replaces this with text/JSONL/TUI dispatch; for now it lands on -// stderr so findings are visible during development. +// emitFinding routes a pre-auth recon finding through the output layer +// (text/JSONL/TUI) via modules.WriteFinding. func emitFinding(host modules.Host, f *brute.Finding) { - cve := "" - if f.CVE != "" { - cve = " (" + f.CVE + ")" - } - fmt.Fprintf(os.Stderr, "[%s] %s %s:%d %s%s\n", - f.Severity, host.Service, host.Host, host.Port, f.Message, cve) + modules.WriteFinding(f.Severity, f.Code, host.Service, host.Host, host.Port, f.Message, f.CVE) } // ProcessHost processes a single host with all its credentials using dedicated host worker pool diff --git a/brutespray/dispatch_rdp_test.go b/brutespray/dispatch_rdp_test.go index e36e5a8..ebeb0e9 100644 --- a/brutespray/dispatch_rdp_test.go +++ b/brutespray/dispatch_rdp_test.go @@ -11,22 +11,25 @@ import ( "github.com/x90skysn3k/brutespray/v2/modules" ) -func captureStderr(fn func()) string { - old := os.Stderr +func captureStdoutDispatch(fn func()) string { + old := os.Stdout r, w, _ := os.Pipe() - os.Stderr = w + os.Stdout = w fn() w.Close() - os.Stderr = old + os.Stdout = old var buf bytes.Buffer io.Copy(&buf, r) return buf.String() } func TestEmitFindingFormat(t *testing.T) { + modules.NoColorMode = true + defer func() { modules.NoColorMode = false }() + h := modules.Host{Service: "rdp", Host: "10.0.0.5", Port: 3389} f := &brute.Finding{Severity: "WARN", Code: "rdp-nla-missing", Message: "NLA not enforced"} - out := captureStderr(func() { emitFinding(h, f) }) + out := captureStdoutDispatch(func() { emitFinding(h, f) }) for _, want := range []string{"WARN", "rdp", "10.0.0.5:3389", "NLA not enforced"} { if !strings.Contains(out, want) { t.Fatalf("output missing %q: %s", want, out) @@ -35,9 +38,12 @@ func TestEmitFindingFormat(t *testing.T) { } func TestEmitFindingIncludesCVE(t *testing.T) { + modules.NoColorMode = true + defer func() { modules.NoColorMode = false }() + h := modules.Host{Service: "ssh", Host: "10.0.0.5", Port: 22} f := &brute.Finding{Severity: "HIGH", Code: "ssh-badkey", Message: "F5 default key", CVE: "CVE-2012-1493"} - out := captureStderr(func() { emitFinding(h, f) }) + out := captureStdoutDispatch(func() { emitFinding(h, f) }) if !strings.Contains(out, "CVE-2012-1493") { t.Fatalf("CVE missing from output: %s", out) } diff --git a/modules/output.go b/modules/output.go index abf655c..3869f12 100644 --- a/modules/output.go +++ b/modules/output.go @@ -414,6 +414,98 @@ func PrintResult(service string, host string, port int, user string, pass string } } +// FindingSink routes a finding line to the TUI when in TUI mode. Set by +// brutespray.executeTUI() — mirrors ErrorSink. +// NOTE: FindingSink is declared here but not yet wired to a TUI sink tab — +// that wiring happens in Task A9 when the TUI Findings tab lands. Until then +// it remains nil and TUI mode falls back to the standard text path. +var FindingSink func(severity, code, service, target, message, cve string) + +// WriteFinding renders a pre-auth recon finding. The output channel is +// chosen by OutputFormatMode + TUIMode: JSONL when format=="json", a TUI +// event when TUIMode is on with FindingSink wired, otherwise a colored +// stdout line. Primitive args to avoid a modules→brute import cycle. +func WriteFinding(severity, code, service, host string, port int, message, cve string) { + target := fmt.Sprintf("%s:%d", host, port) + if OutputFormatMode == "json" { + rec := map[string]any{ + "type": "finding", + "severity": severity, + "code": code, + "service": service, + "target": target, + "message": message, + } + if cve != "" { + rec["cve"] = cve + } + _ = json.NewEncoder(os.Stdout).Encode(rec) + return + } + if TUIMode && FindingSink != nil { + FindingSink(severity, code, service, target, message, cve) + return + } + if Silent { + return + } + cveTrailer := "" + if cve != "" { + cveTrailer = " (" + cve + ")" + } + if NoColorMode { + fmt.Printf("[%s] %s %s %s%s\n", severity, service, target, message, cveTrailer) + return + } + color := pterm.FgYellow + switch severity { + case "CRITICAL": + color = pterm.FgRed + case "HIGH": + color = pterm.FgLightRed + case "WARN": + color = pterm.FgYellow + case "INFO": + color = pterm.FgCyan + } + PrintfColored(color, "[%s] %s %s %s%s\n", severity, service, target, message, cveTrailer) +} + +// PrintBadKeyResult renders a successful SSH bad-key match. Distinct from +// PrintResult so the message clearly signals embedded-bundle authentication. +// In JSON mode produces a JSONL "badkey" record. +func PrintBadKeyResult(service, host string, port int, user, vendor, cve, description string) { + target := fmt.Sprintf("%s:%d", host, port) + if OutputFormatMode == "json" { + rec := map[string]any{ + "type": "badkey", + "service": service, + "target": target, + "username": user, + "vendor": vendor, + "description": description, + } + if cve != "" { + rec["cve"] = cve + } + _ = json.NewEncoder(os.Stdout).Encode(rec) + return + } + if Silent { + return + } + cveTrailer := "" + if cve != "" { + cveTrailer = " (" + cve + ")" + } + msg := fmt.Sprintf("[+] BADKEY %s %s@%s %s%s\n", service, user, target, vendor, cveTrailer) + if NoColorMode { + fmt.Print(msg) + return + } + PrintfColored(pterm.FgLightGreen, "%s", msg) +} + // PrintWarningBeta prints beta service warnings func PrintWarningBeta(service string) { if TUIMode { diff --git a/modules/output_finding_test.go b/modules/output_finding_test.go new file mode 100644 index 0000000..5e97859 --- /dev/null +++ b/modules/output_finding_test.go @@ -0,0 +1,85 @@ +package modules + +import ( + "bytes" + "encoding/json" + "io" + "os" + "strings" + "testing" +) + +func captureStdoutModules(fn func()) string { + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + fn() + w.Close() + os.Stdout = old + var buf bytes.Buffer + io.Copy(&buf, r) + return buf.String() +} + +func TestWriteFindingTextMode(t *testing.T) { + OutputFormatMode = "text" + NoColorMode = true + defer func() { NoColorMode = false }() + out := captureStdoutModules(func() { + WriteFinding("WARN", "rdp-nla-missing", "rdp", "10.0.0.5", 3389, "NLA not enforced", "") + }) + for _, want := range []string{"WARN", "rdp", "10.0.0.5:3389", "NLA not enforced"} { + if !strings.Contains(out, want) { + t.Fatalf("missing %q: %s", want, out) + } + } +} + +func TestWriteFindingJSONMode(t *testing.T) { + OutputFormatMode = "json" + defer func() { OutputFormatMode = "text" }() + out := captureStdoutModules(func() { + WriteFinding("CRITICAL", "rdp-stickykeys", "rdp", "10.0.0.5", 3389, "backdoor", "") + }) + var got struct { + Type, Severity, Code, Service, Target string + } + if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &got); err != nil { + t.Fatalf("invalid JSON: %v\n%s", err, out) + } + if got.Type != "finding" || got.Severity != "CRITICAL" || got.Code != "rdp-stickykeys" { + t.Fatalf("wrong fields: %+v", got) + } + if got.Target != "10.0.0.5:3389" { + t.Fatalf("target = %q", got.Target) + } +} + +func TestWriteFindingIncludesCVE(t *testing.T) { + OutputFormatMode = "json" + defer func() { OutputFormatMode = "text" }() + out := captureStdoutModules(func() { + WriteFinding("INFO", "ssh-badkey", "ssh", "10.0.0.5", 22, "F5 key", "CVE-2012-1493") + }) + if !strings.Contains(out, "CVE-2012-1493") { + t.Fatalf("CVE missing: %s", out) + } +} + +func TestPrintBadKeyResultJSON(t *testing.T) { + OutputFormatMode = "json" + defer func() { OutputFormatMode = "text" }() + out := captureStdoutModules(func() { + PrintBadKeyResult("ssh", "10.0.0.5", 22, "vagrant", + "HashiCorp Vagrant", "", "Vagrant insecure default key") + }) + var got struct { + Type, Vendor string + } + if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &got); err != nil { + t.Fatalf("invalid JSON: %v\n%s", err, out) + } + if got.Type != "badkey" || got.Vendor != "HashiCorp Vagrant" { + t.Fatalf("wrong fields: %+v", got) + } +} From bf05ae052f97d0702c60f713605301f13ad87868 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:39:56 -0500 Subject: [PATCH 23/49] feat(tui): add Findings tab populated from pre-auth recon --- brutespray/brutespray.go | 13 ++++++++++ tui/messages.go | 16 ++++++++++++ tui/model.go | 9 +++++++ tui/tabs.go | 3 ++- tui/view_findings.go | 40 +++++++++++++++++++++++++++++ tui/view_findings_test.go | 54 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 tui/view_findings.go create mode 100644 tui/view_findings_test.go diff --git a/brutespray/brutespray.go b/brutespray/brutespray.go index 9f7e269..482ba5b 100644 --- a/brutespray/brutespray.go +++ b/brutespray/brutespray.go @@ -60,6 +60,19 @@ func executeTUI(cfg *Config, cm *modules.ConnectionManager, totalHosts int) { eventBus := tui.NewEventBus() modules.ErrorSink = eventBus.SendError + modules.FindingSink = func(severity, code, service, target, message, cve string) { + eventBus.Send(tui.FindingMsg{ + Entry: tui.FindingEntry{ + Severity: severity, + Code: code, + Service: service, + Target: target, + Message: message, + CVE: cve, + Time: time.Now(), + }, + }) + } workerPool := NewWorkerPool(cfg.Threads, eventBus, cfg.HostParallelism, totalHosts) workerPool.stopOnSuccess = cfg.StopOnSuccess workerPool.rateLimit = cfg.RateLimit diff --git a/tui/messages.go b/tui/messages.go index 3ae7ba5..ec4af1b 100644 --- a/tui/messages.go +++ b/tui/messages.go @@ -49,3 +49,19 @@ type ErrorMsg struct { Message string Timestamp time.Time } + +// FindingEntry holds a single pre-auth recon finding. +type FindingEntry struct { + Severity string + Code string + Service string + Target string + Message string + CVE string + Time time.Time +} + +// FindingMsg is sent when a pre-auth recon finding is produced. +type FindingMsg struct { + Entry FindingEntry +} diff --git a/tui/model.go b/tui/model.go index 976d25e..f57c9bd 100644 --- a/tui/model.go +++ b/tui/model.go @@ -89,6 +89,8 @@ type Model struct { version string splashActive bool + + findings []FindingEntry } // NewModel creates a new TUI model. resumedProgress is the number of attempts @@ -202,6 +204,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.completedView.AddCompleted(msg) return m, nil + case FindingMsg: + m.findings = append(m.findings, msg.Entry) + return m, nil + case doneMsg: m.done = true m.setStatus("All hosts completed. Press Ctrl+C to exit.") @@ -389,6 +395,7 @@ func (m Model) View() string { TabCompleted: m.completedView.Count(), TabSuccess: m.successView.Count(), TabErrors: m.errorsView.Count(), + TabFindings: len(m.findings), } tabBar := RenderTabBar(m.activeTab, m.width, &m.scheme, badges, m.tabBarFocused, m.version) @@ -407,6 +414,8 @@ func (m Model) View() string { content = m.successView.View(&m.scheme) case TabErrors: content = m.errorsView.View(&m.scheme) + case TabFindings: + content = m.viewFindings() case TabSettings: content = m.settingsView.View(&m.scheme) } diff --git a/tui/tabs.go b/tui/tabs.go index 8f3e29c..7bd7590 100644 --- a/tui/tabs.go +++ b/tui/tabs.go @@ -17,11 +17,12 @@ const ( TabCompleted TabSuccess TabErrors + TabFindings TabSettings tabCount // sentinel for total count ) -var tabNames = []string{"All", "By Host", "By Service", "Completed", "Successes", "Errors", "Settings"} +var tabNames = []string{"All", "By Host", "By Service", "Completed", "Successes", "Errors", "Findings", "Settings"} // brandText is the compact brand name shown in the top-right of the tab bar. const brandText = "BRUTESPRAY" diff --git a/tui/view_findings.go b/tui/view_findings.go new file mode 100644 index 0000000..e1b3f99 --- /dev/null +++ b/tui/view_findings.go @@ -0,0 +1,40 @@ +package tui + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/lipgloss" +) + +func (m Model) viewFindings() string { + if len(m.findings) == 0 { + return lipgloss.NewStyle(). + Foreground(lipgloss.Color("#888888")). + Render("No findings yet. Pre-auth recon results (SSH bad-keys, RDP NLA, sticky-keys) appear here.") + } + var b strings.Builder + for _, f := range m.findings { + sev := f.Severity + // Color by severity to match WriteFinding's scheme: + // CRITICAL → red, HIGH → bright red, WARN → yellow, INFO → cyan + var sevColor lipgloss.Color + switch sev { + case "CRITICAL": + sevColor = "#ff5555" + case "HIGH": + sevColor = "#ff8888" + case "WARN": + sevColor = "#ffaa00" + default: + sevColor = "#00ffff" + } + sevStyled := lipgloss.NewStyle().Bold(true).Foreground(sevColor).Render("[" + sev + "]") + cve := "" + if f.CVE != "" { + cve = " (" + f.CVE + ")" + } + b.WriteString(fmt.Sprintf("%s %s %s %s%s\n", sevStyled, f.Service, f.Target, f.Message, cve)) + } + return b.String() +} diff --git a/tui/view_findings_test.go b/tui/view_findings_test.go new file mode 100644 index 0000000..c80fb74 --- /dev/null +++ b/tui/view_findings_test.go @@ -0,0 +1,54 @@ +package tui + +import ( + "strings" + "testing" + "time" +) + +func TestViewFindingsEmptyState(t *testing.T) { + m := Model{} + out := m.viewFindings() + if !strings.Contains(out, "No findings") { + t.Fatalf("expected empty-state placeholder, got: %s", out) + } +} + +func TestViewFindingsRendersEntries(t *testing.T) { + m := Model{ + findings: []FindingEntry{ + {Severity: "WARN", Service: "rdp", Target: "10.0.0.5:3389", Message: "NLA not enforced", Time: time.Now()}, + {Severity: "CRITICAL", Service: "rdp", Target: "10.0.0.5:3389", Message: "sticky-keys backdoor", CVE: "", Time: time.Now()}, + }, + } + out := m.viewFindings() + for _, want := range []string{"WARN", "rdp", "10.0.0.5:3389", "NLA not enforced", "CRITICAL", "sticky-keys backdoor"} { + if !strings.Contains(out, want) { + t.Fatalf("output missing %q: %s", want, out) + } + } +} + +func TestViewFindingsRendersCVE(t *testing.T) { + m := Model{ + findings: []FindingEntry{ + {Severity: "INFO", Service: "ssh", Target: "10.0.0.5:22", Message: "F5 bad key", CVE: "CVE-2012-1493", Time: time.Now()}, + }, + } + out := m.viewFindings() + if !strings.Contains(out, "CVE-2012-1493") { + t.Fatalf("output missing CVE: %s", out) + } +} + +func TestAddFindingThroughUpdate(t *testing.T) { + m := Model{} + updated, _ := m.Update(FindingMsg{Entry: FindingEntry{Severity: "INFO", Service: "ssh", Target: "10.0.0.5:22", Message: "test"}}) + final := updated.(Model) + if len(final.findings) != 1 { + t.Fatalf("findings len = %d, want 1", len(final.findings)) + } + if final.findings[0].Severity != "INFO" { + t.Fatalf("severity = %q", final.findings[0].Severity) + } +} From bb85ac71b939ed288c4edec9be2489341fc9348c Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:42:33 -0500 Subject: [PATCH 24/49] =?UTF-8?q?feat(parse):=20masscan=20-oJ=20JSON=20ing?= =?UTF-8?q?estion=20with=20port=E2=86=92service=20mapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ParseMasscanJSON to ingest masscan -oJ output, and defaultServiceForPort helper for port-only parsers to resolve brutespray canonical service names. Closed and unmapped ports are filtered at parse time. --- modules/parse.go | 62 ++++++++++++++++++++++++++++ modules/parse_masscan.go | 43 +++++++++++++++++++ modules/parse_masscan_test.go | 78 +++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 modules/parse_masscan.go create mode 100644 modules/parse_masscan_test.go diff --git a/modules/parse.go b/modules/parse.go index 9d20579..25b0bbe 100644 --- a/modules/parse.go +++ b/modules/parse.go @@ -121,6 +121,68 @@ func MapService(service string) string { return service } +// defaultServiceForPort returns brutespray's canonical service name for a +// well-known port, or "" when the port has no default mapping. Used by +// stream parsers (masscan JSON, naabu line) that supply only host:port and +// need to fill in the service. +func defaultServiceForPort(port int) string { + switch port { + case 21: + return "ftp" + case 22: + return "ssh" + case 23: + return "telnet" + case 25, 587: + return "smtp" + case 80: + return "http" + case 110: + return "pop3" + case 143: + return "imap" + case 161: + return "snmp" + case 389: + return "ldap" + case 443: + return "https" + case 445: + return "smbnt" + case 636: + return "ldaps" + case 1433: + return "mssql" + case 1521: + return "oracle" + case 3306: + return "mysql" + case 3389: + return "rdp" + case 5432: + return "postgres" + case 5900, 5901, 5902: + return "vnc" + case 5984: + return "couchdb" + case 5985, 5986: + return "winrm" + case 6379: + return "redis" + case 7687: + return "neo4j" + case 8086: + return "influxdb" + case 9042: + return "cassandra" + case 9200: + return "elasticsearch" + case 27017: + return "mongodb" + } + return "" +} + // supportedScanServices is the canonical list of nmap/scanner service names // that brutespray recognises from scan input files. Names are pre-mapping // (i.e. the raw names scanners emit); MapService() converts them to diff --git a/modules/parse_masscan.go b/modules/parse_masscan.go new file mode 100644 index 0000000..53867ba --- /dev/null +++ b/modules/parse_masscan.go @@ -0,0 +1,43 @@ +package modules + +import ( + "encoding/json" + "fmt" + "io" +) + +type masscanPort struct { + Port int `json:"port"` + Proto string `json:"proto"` + Status string `json:"status"` +} + +type masscanHost struct { + IP string `json:"ip"` + Ports []masscanPort `json:"ports"` +} + +// ParseMasscanJSON reads masscan -oJ output (a JSON array of host objects, +// each carrying an array of open-port records) and returns one Host per +// open port. Service is inferred from port via defaultServiceForPort; +// ports with no mapping are dropped. +func ParseMasscanJSON(r io.Reader) ([]Host, error) { + var rows []masscanHost + if err := json.NewDecoder(r).Decode(&rows); err != nil { + return nil, fmt.Errorf("decode masscan json: %w", err) + } + var out []Host + for _, row := range rows { + for _, p := range row.Ports { + if p.Status != "open" { + continue + } + svc := defaultServiceForPort(p.Port) + if svc == "" { + continue + } + out = append(out, Host{Service: svc, Host: row.IP, Port: p.Port}) + } + } + return out, nil +} diff --git a/modules/parse_masscan_test.go b/modules/parse_masscan_test.go new file mode 100644 index 0000000..4cde6d3 --- /dev/null +++ b/modules/parse_masscan_test.go @@ -0,0 +1,78 @@ +package modules + +import ( + "strconv" + "strings" + "testing" +) + +const masscanSample = `[ +{"ip":"10.0.0.5","ports":[{"port":22,"proto":"tcp","status":"open"}]}, +{"ip":"10.0.0.6","ports":[{"port":3306,"proto":"tcp","status":"open"},{"port":80,"proto":"tcp","status":"closed"}]}, +{"ip":"10.0.0.7","ports":[{"port":3389,"proto":"tcp","status":"open"}]}, +{"ip":"10.0.0.8","ports":[{"port":11111,"proto":"tcp","status":"open"}]} +]` + +func TestParseMasscanJSON(t *testing.T) { + hosts, err := ParseMasscanJSON(strings.NewReader(masscanSample)) + if err != nil { + t.Fatalf("ParseMasscanJSON: %v", err) + } + // Want 3 hosts: closed port filtered AND unmapped port 11111 filtered + if len(hosts) != 3 { + t.Fatalf("want 3 hosts, got %d (%+v)", len(hosts), hosts) + } + want := map[string]string{ + "10.0.0.5:22": "ssh", + "10.0.0.6:3306": "mysql", + "10.0.0.7:3389": "rdp", + } + for _, h := range hosts { + key := h.Host + ":" + strconv.Itoa(h.Port) + got, ok := want[key] + if !ok || got != h.Service { + t.Fatalf("unexpected host: %+v", h) + } + } +} + +func TestParseMasscanJSONEmpty(t *testing.T) { + hosts, err := ParseMasscanJSON(strings.NewReader("[]")) + if err != nil { + t.Fatalf("empty array should parse: %v", err) + } + if len(hosts) != 0 { + t.Fatalf("want empty, got %d", len(hosts)) + } +} + +func TestParseMasscanJSONInvalid(t *testing.T) { + _, err := ParseMasscanJSON(strings.NewReader("not json")) + if err == nil { + t.Fatal("expected parse error for garbage input") + } +} + +func TestDefaultServiceForPortKnown(t *testing.T) { + cases := map[int]string{ + 22: "ssh", + 3306: "mysql", + 3389: "rdp", + 5984: "couchdb", + 9200: "elasticsearch", + 7687: "neo4j", + 9042: "cassandra", + 8086: "influxdb", + } + for port, want := range cases { + if got := defaultServiceForPort(port); got != want { + t.Errorf("port %d: got %q, want %q", port, got, want) + } + } +} + +func TestDefaultServiceForPortUnknown(t *testing.T) { + if got := defaultServiceForPort(12345); got != "" { + t.Fatalf("unknown port should return empty, got %q", got) + } +} From 4c3c2e51c3abed87a88b5d41fb713e012ab8a762 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:44:39 -0500 Subject: [PATCH 25/49] feat(parse): stdin stream auto-detect for naabu/nerva/fingerprintx/masscan --- modules/parse_stream.go | 190 +++++++++++++++++++++++++++++++++++ modules/parse_stream_test.go | 101 +++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 modules/parse_stream.go create mode 100644 modules/parse_stream_test.go diff --git a/modules/parse_stream.go b/modules/parse_stream.go new file mode 100644 index 0000000..d482413 --- /dev/null +++ b/modules/parse_stream.go @@ -0,0 +1,190 @@ +package modules + +import ( + "bufio" + "bytes" + "encoding/json" + "fmt" + "io" + "regexp" + "strconv" + "strings" +) + +var ( + nervaURIRE = regexp.MustCompile(`^[a-z][a-z0-9+-]*://[^:/]+:\d+`) + hostPortRE = regexp.MustCompile(`^[^\s:]+:\d+$`) +) + +// DetectStreamFormat peeks at the first non-blank line of a stream and +// returns one of: "naabu", "nerva-uri", "nerva-json", "masscan-json", +// "fingerprintx-json". The caller must pass a reader that has not yet +// been consumed (this function does NOT rewind). +func DetectStreamFormat(r io.Reader) (string, error) { + br := bufio.NewReader(r) + peek, _ := br.Peek(4096) + var line []byte + for _, raw := range bytes.Split(peek, []byte("\n")) { + t := bytes.TrimSpace(raw) + if len(t) > 0 { + line = t + break + } + } + if len(line) == 0 { + return "", fmt.Errorf("empty stream") + } + s := string(line) + switch { + case s[0] == '[': + return "masscan-json", nil + case s[0] == '{': + var probe map[string]json.RawMessage + if err := json.Unmarshal(line, &probe); err != nil { + return "", fmt.Errorf("invalid JSON: %w", err) + } + _, hasService := probe["service"] + _, hasProtocol := probe["protocol"] + _, hasPort := probe["port"] + switch { + case hasService && hasPort: + return "fingerprintx-json", nil + case hasProtocol && hasPort: + return "nerva-json", nil + } + return "", fmt.Errorf("unrecognized JSON shape") + case nervaURIRE.MatchString(s): + return "nerva-uri", nil + case hostPortRE.MatchString(s): + return "naabu", nil + } + return "", fmt.Errorf("unrecognized line format: %s", s) +} + +// ParseStream reads a full stream, auto-detects the format, and returns +// the parsed Hosts. Convenience over DetectStreamFormat + format-specific +// parser when callers want one-shot ingestion. +func ParseStream(r io.Reader) ([]Host, error) { + buf, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("read stream: %w", err) + } + format, err := DetectStreamFormat(bytes.NewReader(buf)) + if err != nil { + return nil, err + } + switch format { + case "naabu": + return parseNaabuLines(buf), nil + case "nerva-uri": + return parseNervaURI(buf), nil + case "nerva-json": + return parseNervaJSON(buf) + case "masscan-json": + return ParseMasscanJSON(bytes.NewReader(buf)) + case "fingerprintx-json": + return parseFingerprintXJSON(buf) + } + return nil, fmt.Errorf("unsupported format: %s", format) +} + +func parseNaabuLines(buf []byte) []Host { + var out []Host + for _, raw := range bytes.Split(buf, []byte("\n")) { + s := strings.TrimSpace(string(raw)) + if s == "" { + continue + } + host, port, err := splitHostPort(s) + if err != nil { + continue + } + svc := defaultServiceForPort(port) + if svc == "" { + continue + } + out = append(out, Host{Service: svc, Host: host, Port: port}) + } + return out +} + +func parseNervaURI(buf []byte) []Host { + var out []Host + for _, raw := range bytes.Split(buf, []byte("\n")) { + s := strings.TrimSpace(string(raw)) + if s == "" { + continue + } + // Strip parenthetical resolution suffix like "ssh://github.com:22 (140.82.121.4)" + if idx := strings.Index(s, " "); idx > 0 { + s = s[:idx] + } + schemeEnd := strings.Index(s, "://") + if schemeEnd < 0 { + continue + } + scheme := s[:schemeEnd] + rest := s[schemeEnd+3:] + host, port, err := splitHostPort(rest) + if err != nil { + continue + } + out = append(out, Host{Service: scheme, Host: host, Port: port}) + } + return out +} + +type nervaRow struct { + IP string `json:"ip"` + Port int `json:"port"` + Protocol string `json:"protocol"` +} + +func parseNervaJSON(buf []byte) ([]Host, error) { + var out []Host + dec := json.NewDecoder(bytes.NewReader(buf)) + for dec.More() { + var row nervaRow + if err := dec.Decode(&row); err != nil { + return nil, fmt.Errorf("decode nerva-json: %w", err) + } + out = append(out, Host{Service: row.Protocol, Host: row.IP, Port: row.Port}) + } + return out, nil +} + +type fpxRow struct { + Host string `json:"host"` + IP string `json:"ip"` + Port int `json:"port"` + Service string `json:"service"` +} + +func parseFingerprintXJSON(buf []byte) ([]Host, error) { + var out []Host + dec := json.NewDecoder(bytes.NewReader(buf)) + for dec.More() { + var row fpxRow + if err := dec.Decode(&row); err != nil { + return nil, fmt.Errorf("decode fingerprintx-json: %w", err) + } + h := row.Host + if h == "" { + h = row.IP + } + out = append(out, Host{Service: row.Service, Host: h, Port: row.Port}) + } + return out, nil +} + +func splitHostPort(s string) (string, int, error) { + idx := strings.LastIndex(s, ":") + if idx < 0 { + return "", 0, fmt.Errorf("no port: %s", s) + } + port, err := strconv.Atoi(s[idx+1:]) + if err != nil { + return "", 0, err + } + return s[:idx], port, nil +} diff --git a/modules/parse_stream_test.go b/modules/parse_stream_test.go new file mode 100644 index 0000000..7cf7f85 --- /dev/null +++ b/modules/parse_stream_test.go @@ -0,0 +1,101 @@ +package modules + +import ( + "strings" + "testing" +) + +func TestDetectStreamFormat(t *testing.T) { + cases := []struct { + name string + in string + want string + }{ + {"bare-host-port", "10.0.0.5:22\n10.0.0.6:3389\n", "naabu"}, + {"nerva-uri", "ssh://10.0.0.5:22\nmysql://10.0.0.6:3306\n", "nerva-uri"}, + {"nerva-json", `{"ip":"10.0.0.5","port":22,"protocol":"ssh"}`, "nerva-json"}, + {"masscan-json", `[{"ip":"10.0.0.5","ports":[{"port":22,"proto":"tcp","status":"open"}]}]`, "masscan-json"}, + {"fingerprintx-json", `{"host":"10.0.0.5","ip":"10.0.0.5","port":22,"service":"ssh","transport":"tcp"}`, "fingerprintx-json"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got, err := DetectStreamFormat(strings.NewReader(c.in)) + if err != nil { + t.Fatalf("DetectStreamFormat: %v", err) + } + if got != c.want { + t.Fatalf("got %q, want %q", got, c.want) + } + }) + } +} + +func TestParseStreamNaabu(t *testing.T) { + hosts, err := ParseStream(strings.NewReader("10.0.0.5:22\n10.0.0.6:3389\n")) + if err != nil { + t.Fatalf("ParseStream: %v", err) + } + if len(hosts) != 2 { + t.Fatalf("want 2, got %d", len(hosts)) + } + if hosts[0].Service != "ssh" || hosts[1].Service != "rdp" { + t.Fatalf("port→service mapping failed: %+v", hosts) + } +} + +func TestParseStreamNervaURI(t *testing.T) { + hosts, err := ParseStream(strings.NewReader("ssh://10.0.0.5:22\nmysql://10.0.0.6:3306 (resolved.example)\n")) + if err != nil { + t.Fatalf("ParseStream: %v", err) + } + if len(hosts) != 2 { + t.Fatalf("want 2, got %d", len(hosts)) + } + if hosts[0].Service != "ssh" || hosts[1].Service != "mysql" { + t.Fatalf("uri parse failed: %+v", hosts) + } + if hosts[1].Host != "10.0.0.6" || hosts[1].Port != 3306 { + t.Fatalf("parenthetical suffix not stripped: %+v", hosts[1]) + } +} + +func TestParseStreamNervaJSON(t *testing.T) { + in := `{"ip":"10.0.0.5","port":22,"protocol":"ssh"} +{"ip":"10.0.0.6","port":3389,"protocol":"rdp"}` + hosts, err := ParseStream(strings.NewReader(in)) + if err != nil { + t.Fatalf("ParseStream: %v", err) + } + if len(hosts) != 2 { + t.Fatalf("want 2, got %d", len(hosts)) + } +} + +func TestParseStreamFingerprintX(t *testing.T) { + in := `{"host":"10.0.0.5","ip":"10.0.0.5","port":22,"service":"ssh","transport":"tcp"}` + hosts, err := ParseStream(strings.NewReader(in)) + if err != nil { + t.Fatalf("ParseStream: %v", err) + } + if len(hosts) != 1 || hosts[0].Service != "ssh" { + t.Fatalf("fingerprintx parse failed: %+v", hosts) + } +} + +func TestParseStreamMasscan(t *testing.T) { + in := `[{"ip":"10.0.0.5","ports":[{"port":22,"proto":"tcp","status":"open"}]}]` + hosts, err := ParseStream(strings.NewReader(in)) + if err != nil { + t.Fatalf("ParseStream: %v", err) + } + if len(hosts) != 1 || hosts[0].Service != "ssh" { + t.Fatalf("masscan parse failed: %+v", hosts) + } +} + +func TestDetectStreamEmpty(t *testing.T) { + _, err := DetectStreamFormat(strings.NewReader("")) + if err == nil { + t.Fatal("empty stream should error") + } +} From 9c56bc2897a28d0aeb9038e30736ef35435ee412 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:46:47 -0500 Subject: [PATCH 26/49] feat(cli): auto-read targets from piped stdin with format detection --- brutespray/brutespray.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/brutespray/brutespray.go b/brutespray/brutespray.go index 482ba5b..27d4697 100644 --- a/brutespray/brutespray.go +++ b/brutespray/brutespray.go @@ -12,11 +12,23 @@ import ( "github.com/x90skysn3k/brutespray/v2/brute" "github.com/x90skysn3k/brutespray/v2/modules" "github.com/x90skysn3k/brutespray/v2/tui" + "golang.org/x/term" ) func Execute() { cfg := ParseConfig() + // Read targets from stdin when -f is unset and stdin is piped (not a TTY). + // Auto-detects naabu/nerva URI/Nerva JSON/fingerprintx JSON/masscan JSON. + if cfg.File == "" && !term.IsTerminal(int(os.Stdin.Fd())) { + hosts, err := modules.ParseStream(os.Stdin) + if err != nil { + fmt.Fprintf(os.Stderr, "stdin parse: %v\n", err) + os.Exit(2) + } + cfg.Hosts = append(cfg.Hosts, hosts...) + } + totalHosts := len(cfg.Hosts) // Only enable the circuit breaker in spray mode where skipping unreachable From 59214647abfe0d699db7ee1e1851c474bd73bf91 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:49:12 -0500 Subject: [PATCH 27/49] feat(brute): neo4j Bolt v5 module --- brute/neo4j.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ brute/neo4j_test.go | 22 ++++++++++++++++++++++ go.mod | 1 + go.sum | 2 ++ 4 files changed, 70 insertions(+) create mode 100644 brute/neo4j.go create mode 100644 brute/neo4j_test.go diff --git a/brute/neo4j.go b/brute/neo4j.go new file mode 100644 index 0000000..99f608d --- /dev/null +++ b/brute/neo4j.go @@ -0,0 +1,45 @@ +package brute + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/x90skysn3k/brutespray/v2/modules" +) + +// BruteNeo4j attempts to authenticate against a Neo4j Bolt v5 server. +// +// Note: neo4j-go-driver/v5 does not expose a custom net.Dialer on the public +// Config API, so proxy/interface routing via cm does not apply to Neo4j +// attempts. The cm parameter is accepted for interface consistency but unused. +func BruteNeo4j(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + uri := fmt.Sprintf("bolt://%s:%d", host, port) + driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(user, password, ""), + func(c *neo4j.Config) { + c.SocketConnectTimeout = timeout + }) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer driver.Close(ctx) + + err = driver.VerifyConnectivity(ctx) + if err != nil { + msg := err.Error() + if strings.Contains(msg, "AuthenticationRateLimit") || + strings.Contains(msg, "Unauthorized") || + strings.Contains(msg, "credentials") || + strings.Contains(msg, "Neo.ClientError.Security.Unauthorized") { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} + } + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + }) +} + +func init() { Register("neo4j", BruteNeo4j) } diff --git a/brute/neo4j_test.go b/brute/neo4j_test.go new file mode 100644 index 0000000..f0d36db --- /dev/null +++ b/brute/neo4j_test.go @@ -0,0 +1,22 @@ +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteNeo4jNoServer(t *testing.T) { + cm, _ := modules.NewConnectionManager("", 1*time.Second, "") + r := BruteNeo4j("127.0.0.1", 1, "neo4j", "neo4j", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port, got %+v", r) + } +} + +func TestBruteNeo4jRegistered(t *testing.T) { + if !IsRegistered("neo4j") { + t.Fatal("neo4j module not registered") + } +} diff --git a/go.mod b/go.mod index 1a5ba3e..e66a38b 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/lib/pq v1.12.3 github.com/masterzen/winrm v0.0.0-20250927112105-5f8e6c707321 github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed + github.com/neo4j/neo4j-go-driver/v5 v5.28.4 github.com/pterm/pterm v0.12.83 github.com/sijms/go-ora/v2 v2.9.0 github.com/x90skysn3k/grdp v1.0.2 diff --git a/go.sum b/go.sum index 760ab93..c324df3 100644 --- a/go.sum +++ b/go.sum @@ -221,6 +221,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/neo4j/neo4j-go-driver/v5 v5.28.4 h1:7toxehVcYkZbyxV4W3Ib9VcnyRBQPucF+VwNNmtSXi4= +github.com/neo4j/neo4j-go-driver/v5 v5.28.4/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= From 30da7e8795b0202d05d777d5421c1d71d52f8eeb Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:50:54 -0500 Subject: [PATCH 28/49] feat(brute): cassandra CQL module with default wordlist Adds gocql-backed Cassandra brute module, unit tests, and seed wordlists for username and password. --- brute/cassandra.go | 39 +++++++++++++++++++++++++++++++++++++ brute/cassandra_test.go | 22 +++++++++++++++++++++ go.mod | 3 +++ go.sum | 11 +++++++++++ wordlist/cassandra/password | 3 +++ wordlist/cassandra/user | 3 +++ 6 files changed, 81 insertions(+) create mode 100644 brute/cassandra.go create mode 100644 brute/cassandra_test.go create mode 100644 wordlist/cassandra/password create mode 100644 wordlist/cassandra/user diff --git a/brute/cassandra.go b/brute/cassandra.go new file mode 100644 index 0000000..9429f2e --- /dev/null +++ b/brute/cassandra.go @@ -0,0 +1,39 @@ +package brute + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/gocql/gocql" + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func BruteCassandra(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + cluster := gocql.NewCluster(fmt.Sprintf("%s:%d", host, port)) + cluster.ProtoVersion = 4 + cluster.ConnectTimeout = timeout + cluster.Timeout = timeout + cluster.Authenticator = gocql.PasswordAuthenticator{ + Username: user, + Password: password, + } + cluster.DisableInitialHostLookup = true + sess, err := cluster.CreateSession() + if err != nil { + msg := err.Error() + if strings.Contains(msg, "Authentication") || + strings.Contains(msg, "Bad credentials") || + strings.Contains(msg, "Unauthorized") { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} + } + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer sess.Close() + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + }) +} + +func init() { Register("cassandra", BruteCassandra) } diff --git a/brute/cassandra_test.go b/brute/cassandra_test.go new file mode 100644 index 0000000..e95160a --- /dev/null +++ b/brute/cassandra_test.go @@ -0,0 +1,22 @@ +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteCassandraNoServer(t *testing.T) { + cm, _ := modules.NewConnectionManager("", 1*time.Second, "") + r := BruteCassandra("127.0.0.1", 1, "cassandra", "cassandra", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port, got %+v", r) + } +} + +func TestBruteCassandraRegistered(t *testing.T) { + if !IsRegistered("cassandra") { + t.Fatal("cassandra module not registered") + } +} diff --git a/go.mod b/go.mod index e66a38b..92a7563 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-ldap/ldap/v3 v3.4.13 github.com/go-redis/redis/v8 v8.11.5 github.com/go-sql-driver/mysql v1.9.3 + github.com/gocql/gocql v1.7.0 github.com/gosnmp/gosnmp v1.43.2 github.com/hirochachacha/go-smb2 v1.1.0 github.com/jlaffaye/ftp v0.2.0 @@ -62,6 +63,7 @@ require ( github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gookit/color v1.6.0 // indirect + github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -95,5 +97,6 @@ require ( golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect + gopkg.in/inf.v0 v0.9.1 // indirect nhooyr.io/websocket v1.8.17 // indirect ) diff --git a/go.sum b/go.sum index c324df3..d87d556 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,10 @@ github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1L github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b h1:baFN6AnR0SeC194X2D292IUZcHDs4JjStpqtE70fjXE= github.com/bodgit/ntlmssp v0.0.0-20240506230425-31973bb52d9b/go.mod h1:Ram6ngyPDmP+0t6+4T2rymv0w0BS9N8Ch5vvUJccw5o= github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4= @@ -106,6 +110,8 @@ github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus= +github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= @@ -115,6 +121,7 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -138,6 +145,8 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gosnmp/gosnmp v1.43.2 h1:F9loz6uMCNtIQj0RNO5wz/mZ+FZt2WyNKJYOvw+Zosw= github.com/gosnmp/gosnmp v1.43.2/go.mod h1:smHIwoaqr1M+HTAEd7+mKkPs8lp3Lf/U+htPUql1Q3c= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -384,6 +393,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/wordlist/cassandra/password b/wordlist/cassandra/password new file mode 100644 index 0000000..8109bc5 --- /dev/null +++ b/wordlist/cassandra/password @@ -0,0 +1,3 @@ +cassandra +admin +changeme diff --git a/wordlist/cassandra/user b/wordlist/cassandra/user new file mode 100644 index 0000000..ffaee67 --- /dev/null +++ b/wordlist/cassandra/user @@ -0,0 +1,3 @@ +cassandra +admin +user From 9e86a22c696be321b60a269422a317e8542e7dfa Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:53:43 -0500 Subject: [PATCH 29/49] feat(brute): couchdb HTTP _session module with default wordlist --- brute/couchdb.go | 54 +++++++++++++++++++++++++++++++++++++++ brute/couchdb_test.go | 22 ++++++++++++++++ wordlist/couchdb/password | 3 +++ wordlist/couchdb/user | 3 +++ wordlist/embed.go | 2 +- wordlist/manifest.yaml | 3 +++ 6 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 brute/couchdb.go create mode 100644 brute/couchdb_test.go create mode 100644 wordlist/couchdb/password create mode 100644 wordlist/couchdb/user diff --git a/brute/couchdb.go b/brute/couchdb.go new file mode 100644 index 0000000..423aeb3 --- /dev/null +++ b/brute/couchdb.go @@ -0,0 +1,54 @@ +package brute + +import ( + "context" + "fmt" + "net" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func BruteCouchDB(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + scheme := "http" + if params["tls"] == "true" { + scheme = "https" + } + endpoint := fmt.Sprintf("%s://%s/_session", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + tr := &http.Transport{ + DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { + return cm.Dial(network, addr) + }, + DisableKeepAlives: true, + } + cl := &http.Client{Transport: tr, Timeout: timeout} + form := url.Values{"name": {user}, "password": {password}} + req, err := http.NewRequestWithContext(ctx, "POST", endpoint, strings.NewReader(form.Encode())) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + resp, err := cl.Do(req) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + case 401: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("couchdb 401")} + default: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("couchdb status %d", resp.StatusCode)} + } + }) +} + +func init() { Register("couchdb", BruteCouchDB) } diff --git a/brute/couchdb_test.go b/brute/couchdb_test.go new file mode 100644 index 0000000..f814df6 --- /dev/null +++ b/brute/couchdb_test.go @@ -0,0 +1,22 @@ +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteCouchDBNoServer(t *testing.T) { + cm, _ := modules.NewConnectionManager("", 1*time.Second, "") + r := BruteCouchDB("127.0.0.1", 1, "admin", "admin", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port, got %+v", r) + } +} + +func TestBruteCouchDBRegistered(t *testing.T) { + if !IsRegistered("couchdb") { + t.Fatal("couchdb module not registered") + } +} diff --git a/wordlist/couchdb/password b/wordlist/couchdb/password new file mode 100644 index 0000000..b6c989b --- /dev/null +++ b/wordlist/couchdb/password @@ -0,0 +1,3 @@ +admin +couchdb +password diff --git a/wordlist/couchdb/user b/wordlist/couchdb/user new file mode 100644 index 0000000..cd095ad --- /dev/null +++ b/wordlist/couchdb/user @@ -0,0 +1,3 @@ +admin +couchdb +user diff --git a/wordlist/embed.go b/wordlist/embed.go index 3b441fb..068d195 100644 --- a/wordlist/embed.go +++ b/wordlist/embed.go @@ -2,5 +2,5 @@ package wordlist import "embed" -//go:embed manifest.yaml all:_base all:_layers all:overrides snmp +//go:embed manifest.yaml all:_base all:_layers all:overrides snmp couchdb var FS embed.FS diff --git a/wordlist/manifest.yaml b/wordlist/manifest.yaml index d71476f..34e4ca1 100644 --- a/wordlist/manifest.yaml +++ b/wordlist/manifest.yaml @@ -105,3 +105,6 @@ services: wrapper: users: [sysadmin_users] passwords: [common_passwords] + couchdb: + users: ["couchdb/user"] + passwords: ["couchdb/password"] From 4ae29b275d3a9eca7f0c1784fae139b415ef7f3e Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:55:26 -0500 Subject: [PATCH 30/49] feat(brute): elasticsearch HTTP basic-auth module with default wordlist --- brute/elasticsearch.go | 51 +++++++++++++++++++++++++++++++++ brute/elasticsearch_test.go | 22 ++++++++++++++ wordlist/elasticsearch/password | 3 ++ wordlist/elasticsearch/user | 4 +++ wordlist/embed.go | 2 +- wordlist/manifest.yaml | 3 ++ 6 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 brute/elasticsearch.go create mode 100644 brute/elasticsearch_test.go create mode 100644 wordlist/elasticsearch/password create mode 100644 wordlist/elasticsearch/user diff --git a/brute/elasticsearch.go b/brute/elasticsearch.go new file mode 100644 index 0000000..02dc22f --- /dev/null +++ b/brute/elasticsearch.go @@ -0,0 +1,51 @@ +package brute + +import ( + "context" + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func BruteElasticsearch(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + scheme := "http" + if params["tls"] == "true" { + scheme = "https" + } + endpoint := fmt.Sprintf("%s://%s/_cluster/health", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + tr := &http.Transport{ + DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { + return cm.Dial(network, addr) + }, + DisableKeepAlives: true, + } + cl := &http.Client{Transport: tr, Timeout: timeout} + req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + req.SetBasicAuth(user, password) + resp, err := cl.Do(req) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200: + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + case 401, 403: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("elasticsearch %d", resp.StatusCode)} + default: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("elasticsearch status %d", resp.StatusCode)} + } + }) +} + +func init() { Register("elasticsearch", BruteElasticsearch) } diff --git a/brute/elasticsearch_test.go b/brute/elasticsearch_test.go new file mode 100644 index 0000000..ed0c4cf --- /dev/null +++ b/brute/elasticsearch_test.go @@ -0,0 +1,22 @@ +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteElasticsearchNoServer(t *testing.T) { + cm, _ := modules.NewConnectionManager("", 1*time.Second, "") + r := BruteElasticsearch("127.0.0.1", 1, "elastic", "elastic", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port, got %+v", r) + } +} + +func TestBruteElasticsearchRegistered(t *testing.T) { + if !IsRegistered("elasticsearch") { + t.Fatal("elasticsearch module not registered") + } +} diff --git a/wordlist/elasticsearch/password b/wordlist/elasticsearch/password new file mode 100644 index 0000000..d6eb8bb --- /dev/null +++ b/wordlist/elasticsearch/password @@ -0,0 +1,3 @@ +elastic +changeme +admin diff --git a/wordlist/elasticsearch/user b/wordlist/elasticsearch/user new file mode 100644 index 0000000..078066b --- /dev/null +++ b/wordlist/elasticsearch/user @@ -0,0 +1,4 @@ +elastic +admin +kibana +logstash diff --git a/wordlist/embed.go b/wordlist/embed.go index 068d195..334f754 100644 --- a/wordlist/embed.go +++ b/wordlist/embed.go @@ -2,5 +2,5 @@ package wordlist import "embed" -//go:embed manifest.yaml all:_base all:_layers all:overrides snmp couchdb +//go:embed manifest.yaml all:_base all:_layers all:overrides snmp couchdb elasticsearch var FS embed.FS diff --git a/wordlist/manifest.yaml b/wordlist/manifest.yaml index 34e4ca1..99fd45d 100644 --- a/wordlist/manifest.yaml +++ b/wordlist/manifest.yaml @@ -108,3 +108,6 @@ services: couchdb: users: ["couchdb/user"] passwords: ["couchdb/password"] + elasticsearch: + users: ["elasticsearch/user"] + passwords: ["elasticsearch/password"] From 91166a9ebe7a99a3f7a74a4309d5965f51eea653 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 09:57:18 -0500 Subject: [PATCH 31/49] feat(brute): influxdb v2 token + v1 basic-auth module --- brute/influxdb.go | 64 ++++++++++++++++++++++++++++++++++++++++++ brute/influxdb_test.go | 31 ++++++++++++++++++++ wordlist/embed.go | 2 +- wordlist/manifest.yaml | 3 ++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 brute/influxdb.go create mode 100644 brute/influxdb_test.go diff --git a/brute/influxdb.go b/brute/influxdb.go new file mode 100644 index 0000000..f0b14be --- /dev/null +++ b/brute/influxdb.go @@ -0,0 +1,64 @@ +package brute + +import ( + "context" + "fmt" + "net" + "net/http" + "strconv" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +// BruteInfluxDB targets InfluxDB. v2 (default): `password` is the InfluxDB +// token; the endpoint /api/v2/orgs returns 200 on valid auth, 401 on invalid. +// v1: pass `-m mode:v1` to use /ping with HTTP basic auth instead. +func BruteInfluxDB(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { + return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { + scheme := "http" + if params["tls"] == "true" { + scheme = "https" + } + v1 := params["mode"] == "v1" + var endpoint string + if v1 { + endpoint = fmt.Sprintf("%s://%s/ping", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + } else { + endpoint = fmt.Sprintf("%s://%s/api/v2/orgs", scheme, net.JoinHostPort(host, strconv.Itoa(port))) + } + tr := &http.Transport{ + DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { + return cm.Dial(network, addr) + }, + DisableKeepAlives: true, + } + cl := &http.Client{Transport: tr, Timeout: timeout} + req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + if v1 { + req.SetBasicAuth(user, password) + } else { + req.Header.Set("Authorization", "Token "+password) + } + resp, err := cl.Do(req) + if err != nil { + return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} + } + defer resp.Body.Close() + switch resp.StatusCode { + case 200, 204: + return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} + case 401, 403: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("influxdb %d", resp.StatusCode)} + default: + return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, + Error: fmt.Errorf("influxdb status %d", resp.StatusCode)} + } + }) +} + +func init() { Register("influxdb", BruteInfluxDB) } diff --git a/brute/influxdb_test.go b/brute/influxdb_test.go new file mode 100644 index 0000000..14a3d01 --- /dev/null +++ b/brute/influxdb_test.go @@ -0,0 +1,31 @@ +package brute + +import ( + "testing" + "time" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestBruteInfluxDBNoServer(t *testing.T) { + cm, _ := modules.NewConnectionManager("", 1*time.Second, "") + r := BruteInfluxDB("127.0.0.1", 1, "admin", "admin", 1*time.Second, cm, nil) + if r.ConnectionSuccess { + t.Fatalf("expected ConnectionSuccess=false against closed port, got %+v", r) + } +} + +func TestBruteInfluxDBRegistered(t *testing.T) { + if !IsRegistered("influxdb") { + t.Fatal("influxdb module not registered") + } +} + +func TestBruteInfluxDBV1Mode(t *testing.T) { + cm, _ := modules.NewConnectionManager("", 1*time.Second, "") + r := BruteInfluxDB("127.0.0.1", 1, "admin", "admin", 1*time.Second, cm, ModuleParams{"mode": "v1"}) + // Just confirm v1 path doesn't panic on closed-port path + if r.ConnectionSuccess { + t.Fatalf("v1 mode: expected ConnectionSuccess=false, got %+v", r) + } +} diff --git a/wordlist/embed.go b/wordlist/embed.go index 334f754..adb0c57 100644 --- a/wordlist/embed.go +++ b/wordlist/embed.go @@ -2,5 +2,5 @@ package wordlist import "embed" -//go:embed manifest.yaml all:_base all:_layers all:overrides snmp couchdb elasticsearch +//go:embed manifest.yaml all:_base all:_layers all:overrides snmp couchdb elasticsearch influxdb var FS embed.FS diff --git a/wordlist/manifest.yaml b/wordlist/manifest.yaml index 99fd45d..224658e 100644 --- a/wordlist/manifest.yaml +++ b/wordlist/manifest.yaml @@ -111,3 +111,6 @@ services: elasticsearch: users: ["elasticsearch/user"] passwords: ["elasticsearch/password"] + influxdb: + users: ["influxdb/user"] + passwords: ["influxdb/password"] From 24b1980d9beca17e07f865fbf949faacf89750a5 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:00:11 -0500 Subject: [PATCH 32/49] feat(snmp): default/extended/full community-string tiering via -m mode Adds opt-in SNMP community-string tiering: pass -m mode:default|extended|full to replace the per-credential community list with a cached, embedded tier wordlist (~20/~55/~92 strings). Default behavior (user+md5(password)) is unchanged when no mode param is set. --- brute/snmp.go | 11 +++- brute/snmp_tier_test.go | 47 ++++++++++++++++ modules/snmp_communities.go | 52 ++++++++++++++++++ wordlist/snmp/snmp_default.txt | 20 +++++++ wordlist/snmp/snmp_extended.txt | 55 +++++++++++++++++++ wordlist/snmp/snmp_full.txt | 96 +++++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 brute/snmp_tier_test.go create mode 100644 modules/snmp_communities.go create mode 100644 wordlist/snmp/snmp_default.txt create mode 100644 wordlist/snmp/snmp_extended.txt create mode 100644 wordlist/snmp/snmp_full.txt diff --git a/brute/snmp.go b/brute/snmp.go index 9578441..383db27 100644 --- a/brute/snmp.go +++ b/brute/snmp.go @@ -24,7 +24,16 @@ func BruteSNMP(host string, port int, user, password string, timeout time.Durati hasher.Write([]byte(password)) md5Password := hex.EncodeToString(hasher.Sum(nil)) - communityStrings := []string{user, md5Password} + var communityStrings []string + tier := params["mode"] + if tier != "" { + if tierList, terr := modules.SNMPCommunities(tier); terr == nil { + communityStrings = tierList + } + } + if len(communityStrings) == 0 { + communityStrings = []string{user, md5Password} + } // Pre-dial to check connectivity (UDP proxy not supported) udpConn, err := cm.DialUDP("udp", fmt.Sprintf("%s:%d", host, port)) diff --git a/brute/snmp_tier_test.go b/brute/snmp_tier_test.go new file mode 100644 index 0000000..155c2db --- /dev/null +++ b/brute/snmp_tier_test.go @@ -0,0 +1,47 @@ +package brute + +import ( + "testing" + + "github.com/x90skysn3k/brutespray/v2/modules" +) + +func TestSNMPCommunitiesTiersIncreasing(t *testing.T) { + def, err := modules.SNMPCommunities("default") + if err != nil { + t.Fatalf("default: %v", err) + } + ext, err := modules.SNMPCommunities("extended") + if err != nil { + t.Fatalf("extended: %v", err) + } + full, err := modules.SNMPCommunities("full") + if err != nil { + t.Fatalf("full: %v", err) + } + if !(len(def) > 0 && len(def) < len(ext) && len(ext) < len(full)) { + t.Fatalf("tier sizes should strictly increase: default=%d extended=%d full=%d", + len(def), len(ext), len(full)) + } +} + +func TestSNMPCommunitiesUnknownTierFallback(t *testing.T) { + got, err := modules.SNMPCommunities("nonsense") + if err != nil { + t.Fatalf("unknown tier should not error: %v", err) + } + def, _ := modules.SNMPCommunities("default") + if len(got) != len(def) { + t.Fatalf("unknown tier should match default size: got %d, default %d", len(got), len(def)) + } +} + +func TestSNMPCommunitiesIncludesPublic(t *testing.T) { + got, _ := modules.SNMPCommunities("default") + for _, c := range got { + if c == "public" { + return + } + } + t.Fatal("'public' should be in the default community list") +} diff --git a/modules/snmp_communities.go b/modules/snmp_communities.go new file mode 100644 index 0000000..af5df66 --- /dev/null +++ b/modules/snmp_communities.go @@ -0,0 +1,52 @@ +package modules + +import ( + "fmt" + "strings" + "sync" + + "github.com/x90skysn3k/brutespray/v2/wordlist" +) + +var ( + snmpCacheMu sync.Mutex + snmpCache = map[string][]string{} +) + +// SNMPCommunities returns the embedded community-string list for a tier: +// "default" (~20), "extended" (~50), or "full" (~100+). The returned slice +// is cached after first load. Unknown tier names fall back to "default". +func SNMPCommunities(tier string) ([]string, error) { + snmpCacheMu.Lock() + defer snmpCacheMu.Unlock() + + if cached, ok := snmpCache[tier]; ok { + return cached, nil + } + + var fname string + switch tier { + case "extended": + fname = "snmp/snmp_extended.txt" + case "full": + fname = "snmp/snmp_full.txt" + default: + fname = "snmp/snmp_default.txt" + } + + data, err := wordlist.FS.ReadFile(fname) + if err != nil { + return nil, fmt.Errorf("read %s: %w", fname, err) + } + + var out []string + for _, line := range strings.Split(string(data), "\n") { + s := strings.TrimSpace(line) + if s != "" && !strings.HasPrefix(s, "#") { + out = append(out, s) + } + } + + snmpCache[tier] = out + return out, nil +} diff --git a/wordlist/snmp/snmp_default.txt b/wordlist/snmp/snmp_default.txt new file mode 100644 index 0000000..434b6e0 --- /dev/null +++ b/wordlist/snmp/snmp_default.txt @@ -0,0 +1,20 @@ +public +private +manager +admin +cisco +default +read +write +community +secret +test +rw +ro +guest +snmpd +snmp +internal +external +local +network diff --git a/wordlist/snmp/snmp_extended.txt b/wordlist/snmp/snmp_extended.txt new file mode 100644 index 0000000..017cab4 --- /dev/null +++ b/wordlist/snmp/snmp_extended.txt @@ -0,0 +1,55 @@ +public +private +manager +admin +cisco +default +read +write +community +secret +test +rw +ro +guest +snmpd +snmp +internal +external +local +network +proxy +tivoli +ILMI +all private +1234 +admin@123 +agent_steal +cable-d +cisco_router +hp_admin +juniper +juniper_admin +juniper_ro +juniper_rw +NoGaH$@! +OrigEquipMfr +private@123 +proxy@123 +read-only +read-write +readonly +readwrite +regional +router +SECURITY +SNMPV2 +snmpwrite +snmp_trap +SuN_MaNaGeR +SwitcHeS +SyStEm +test2 +trap +work +xerox diff --git a/wordlist/snmp/snmp_full.txt b/wordlist/snmp/snmp_full.txt new file mode 100644 index 0000000..b23aa54 --- /dev/null +++ b/wordlist/snmp/snmp_full.txt @@ -0,0 +1,96 @@ +public +private +manager +admin +cisco +default +read +write +community +secret +test +rw +ro +guest +snmpd +snmp +internal +external +local +network +proxy +tivoli +ILMI +all private +1234 +admin@123 +agent_steal +cable-d +cisco_router +hp_admin +juniper +juniper_admin +juniper_ro +juniper_rw +NoGaH$@! +OrigEquipMfr +private@123 +proxy@123 +read-only +read-write +readonly +readwrite +regional +router +SECURITY +SNMPV2 +snmpwrite +snmp_trap +SuN_MaNaGeR +SwitcHeS +SyStEm +test2 +trap +work +xerox +SCADA +SCADA_RW +SCADA_RO +schneider +plc_admin +plc_user +modbus_admin +plc_default +PUBLIC +SECRETID +device +device_admin +iLO +iLOAdmin +PRTG +prtg +solarwinds +intermapper +ENTPASS +ENTERPRISE +NETMAN +NET_OPS +TEAM +camera +hikvision +dahua +axis +arecont +foscam +trendnet +sony_camera +sony +amcrest +emc +isilon +oncue +sanstation +netapp +synology +qnap +buffalo From 1057796a1bad9e74373cacfcb35a90df125e7d53 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:03:23 -0500 Subject: [PATCH 33/49] feat(cli): add -c/--creds inline credential pairs Register -c (short) and --creds (long) flags for supplying comma-separated user:pass pairs directly on the command line without a wordlist file. Pairs are fired first across all services before the regular credential loop; passwords containing colons are handled correctly by splitting on the first colon only. Adds ParseInlineCreds helper and four unit tests. --- brutespray/brutespray.go | 2 ++ brutespray/config.go | 5 ++++ brutespray/dispatch.go | 28 ++++++++++++++++++++++ brutespray/dispatch_badkeys_test.go | 36 +++++++++++++++++++++++++++++ brutespray/pool.go | 2 ++ 5 files changed, 73 insertions(+) diff --git a/brutespray/brutespray.go b/brutespray/brutespray.go index 27d4697..9c54014 100644 --- a/brutespray/brutespray.go +++ b/brutespray/brutespray.go @@ -95,6 +95,7 @@ func executeTUI(cfg *Config, cm *modules.ConnectionManager, totalHosts int) { workerPool.noBadKeys = cfg.NoBadKeys workerPool.badKeysOnly = cfg.BadKeysOnly workerPool.noRDPScan = cfg.NoRDPScan + workerPool.inlineCreds = cfg.Creds // Initialize checkpoint var replayEntries []modules.SessionEntry @@ -198,6 +199,7 @@ func executeLegacy(cfg *Config, cm *modules.ConnectionManager, totalHosts int) { workerPool.noBadKeys = cfg.NoBadKeys workerPool.badKeysOnly = cfg.BadKeysOnly workerPool.noRDPScan = cfg.NoRDPScan + workerPool.inlineCreds = cfg.Creds // Initialize checkpoint for resume capability if cfg.ResumeFile != "" { diff --git a/brutespray/config.go b/brutespray/config.go index 562f83e..ac0d2b9 100644 --- a/brutespray/config.go +++ b/brutespray/config.go @@ -85,6 +85,7 @@ var helpGroups = []flagGroup{ {"-u", "user", "Username or user file"}, {"-p", "pass", "Password or password file"}, {"-C", "user:pass", "Combo entry or combo file"}, + {"-c", "user:pass,...", "Inline credential pairs, comma-separated (e.g. admin:admin,root:toor)"}, {"-e", "nsr", "Extra checks: n=blank, s=user-as-pass, r=reversed"}, {"-x", "MIN:MAX:CHARSET", "Generate passwords (a=lower, A=upper, 1=digit, !=sym)"}, }, @@ -204,6 +205,7 @@ type Config struct { User string Password string Combo string + Creds string Output string Summary bool NoStats bool @@ -318,6 +320,8 @@ func ParseConfig() *Config { var moduleParamsArgs moduleParamsFlag flag.Var(&moduleParamsArgs, "m", "Module-specific parameter in KEY:VALUE format (repeatable). Example: -m auth:NTLM -m dir:/admin") extraCreds := flag.String("e", "", "Extra password checks: n=blank password, s=password=username, r=reversed username, combine: nsr") + inlineCreds := flag.String("creds", "", "Inline credential pairs, comma-separated: user:pass,user2:pass2") + flag.StringVar(inlineCreds, "c", "", "Alias for --creds") allowWrapper := flag.Bool("allow-wrapper", false, "Allow the wrapper module to execute arbitrary commands (required for security)") passwordGen := flag.String("x", "", "Generate passwords: MIN:MAX:CHARSET (a=lower, A=upper, 1=digits, !=symbols). Example: -x 4:4:1") outputFormat := flag.String("output-format", "text", "Output format: text (default) or json (JSONL per-attempt)") @@ -422,6 +426,7 @@ func ParseConfig() *Config { cfg.User = *user cfg.Password = *password cfg.Combo = *combo + cfg.Creds = *inlineCreds cfg.Output = *output cfg.Summary = *summary cfg.NoStats = *noStats diff --git a/brutespray/dispatch.go b/brutespray/dispatch.go index d411fba..3084504 100644 --- a/brutespray/dispatch.go +++ b/brutespray/dispatch.go @@ -3,6 +3,7 @@ package brutespray import ( "fmt" "os" + "strings" "time" "github.com/pterm/pterm" @@ -39,6 +40,24 @@ func BuildBadKeyCreds(bundle []badkeys.Entry, userOverride string) []BadKeyCred return out } +// ParseInlineCreds parses "user:pass,user2:pass2" form into BadKeyCred-shaped +// pairs (reusing the same struct for symmetry with the bad-keys path). +// Splits each pair on the FIRST colon so passwords containing colons survive. +func ParseInlineCreds(s string) []BadKeyCred { + if s == "" { + return nil + } + var out []BadKeyCred + for _, part := range strings.Split(s, ",") { + idx := strings.Index(part, ":") + if idx < 0 { + continue + } + out = append(out, BadKeyCred{User: part[:idx], Password: part[idx+1:]}) + } + return out +} + // reverseString returns the reversed version of a string. func reverseString(s string) string { runes := []rune(s) @@ -221,6 +240,15 @@ func (wp *WorkerPool) ProcessHost(host modules.Host, service string, combo strin } } + // Inline credential pairs from --creds / -c — fire first across ALL services. + if wp.inlineCreds != "" { + for _, p := range ParseInlineCreds(wp.inlineCreds) { + if !queueCred(p.User, p.Password) { + break + } + } + } + // SSH bad-keys pre-pass: try the embedded bundle before any password list. // Opt-out via --no-badkeys; --badkeys-only short-circuits the regular loop. if service == "ssh" && !wp.noBadKeys { diff --git a/brutespray/dispatch_badkeys_test.go b/brutespray/dispatch_badkeys_test.go index 77dbdcf..c8917e9 100644 --- a/brutespray/dispatch_badkeys_test.go +++ b/brutespray/dispatch_badkeys_test.go @@ -61,3 +61,39 @@ func TestBuildBadKeyCredsUsesMetadataUserByDefault(t *testing.T) { } t.Fatal("no Vagrant entry in bundle") } + +func TestParseInlineCredsBasic(t *testing.T) { + pairs := ParseInlineCreds("admin:admin,root:toor") + if len(pairs) != 2 { + t.Fatalf("got %d pairs, want 2: %+v", len(pairs), pairs) + } + if pairs[0].User != "admin" || pairs[0].Password != "admin" { + t.Fatalf("pair[0]: %+v", pairs[0]) + } + if pairs[1].User != "root" || pairs[1].Password != "toor" { + t.Fatalf("pair[1]: %+v", pairs[1]) + } +} + +func TestParseInlineCredsPasswordWithColon(t *testing.T) { + pairs := ParseInlineCreds("user::pass:word") + if len(pairs) != 1 { + t.Fatalf("got %d, want 1", len(pairs)) + } + if pairs[0].User != "user" || pairs[0].Password != ":pass:word" { + t.Fatalf("colon-in-password not preserved: %+v", pairs[0]) + } +} + +func TestParseInlineCredsEmpty(t *testing.T) { + if got := ParseInlineCreds(""); got != nil { + t.Fatalf("empty input should be nil, got %+v", got) + } +} + +func TestParseInlineCredsSkipsInvalidPair(t *testing.T) { + pairs := ParseInlineCreds("admin:admin,notapair,root:toor") + if len(pairs) != 2 { + t.Fatalf("invalid middle pair should be skipped: got %d", len(pairs)) + } +} diff --git a/brutespray/pool.go b/brutespray/pool.go index b3f50fc..084a51a 100644 --- a/brutespray/pool.go +++ b/brutespray/pool.go @@ -96,6 +96,8 @@ type WorkerPool struct { badKeysOnly bool // RDP pre-auth recon control noRDPScan bool + // Inline credential pairs from --creds / -c + inlineCreds string } // NewHostWorkerPool creates a new host-specific worker pool From c6c05c3b1c9d775d82cb4ec0b5ded0dc97a7bc5b Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:04:36 -0500 Subject: [PATCH 34/49] feat(cli): mark neo4j and cassandra beta (couchdb/elasticsearch/influxdb stable) --- brutespray/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brutespray/config.go b/brutespray/config.go index ac0d2b9..7dd0ed7 100644 --- a/brutespray/config.go +++ b/brutespray/config.go @@ -17,7 +17,7 @@ import ( var masterServiceList = brute.Services() -var BetaServiceList = []string{"asterisk", "nntp", "oracle", "xmpp", "ldap", "ldaps", "winrm", "ftps", "smtp-vrfy", "rexec", "rlogin", "rsh", "wrapper", "http-form", "https-form", "svn", "socks5-auth"} +var BetaServiceList = []string{"asterisk", "nntp", "oracle", "xmpp", "ldap", "ldaps", "winrm", "ftps", "smtp-vrfy", "rexec", "rlogin", "rsh", "wrapper", "http-form", "https-form", "svn", "socks5-auth", "neo4j", "cassandra"} var version = "2.6.1" var NoColorMode bool From d0279ccfecec4743f549f558f1c34bdd23d51dd2 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:06:36 -0500 Subject: [PATCH 35/49] docs(readme): add brutespray-vs-others comparison table Insert How-brutespray-compares section with competitor feature matrix and update all "30+ protocols" claims to "40+" to reflect the 41 services now supported. --- README.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4aa781d..259a14f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Inspired by: Leon Johnson/@sho-luv ## Description -Brutespray automatically attempts default credentials on discovered services. It takes scan output from Nmap (GNMAP/XML), Nessus, Nexpose, JSON, and lists, then brute-forces credentials across 30+ protocols in parallel. Built in Go with an interactive terminal UI, embedded wordlists, and resume capability. +Brutespray automatically attempts default credentials on discovered services. It takes scan output from Nmap (GNMAP/XML), Nessus, Nexpose, JSON, and lists, then brute-forces credentials across 40+ protocols in parallel. Built in Go with an interactive terminal UI, embedded wordlists, and resume capability. @@ -44,7 +44,7 @@ See [all examples](docs/examples.md) for more usage patterns. ## Features -- **30+ protocols** — SSH, FTP, RDP, SMB, MySQL, PostgreSQL, Redis, LDAP, WinRM, and [more](docs/services.md) +- **40+ protocols** — SSH, FTP, RDP, SMB, MySQL, PostgreSQL, Redis, LDAP, WinRM, and [more](docs/services.md) - **Module parameters** — Per-module settings via `-m KEY:VALUE` (auth type, target path, NTLM domain, etc.) - **Multi-auth support** — HTTP Digest/NTLM auto-detection, SMTP PLAIN/LOGIN, IMAP/POP3 SASL, SMB pass-the-hash - **Interactive TUI** — Tabbed views, live settings, pause/resume hosts ([details](docs/tui.md)) @@ -57,6 +57,25 @@ See [all examples](docs/examples.md) for more usage patterns. - **Performance tuning** — Dynamic threading, circuit breaker, rate limiting ([details](docs/advanced.md#performance-tuning)) - **YAML config files** — Per-engagement settings ([details](docs/usage.md#config-file)) +## How brutespray compares + +| Feature | brutespray | hydra | medusa | ncrack | brutus | +|---|---|---|---|---|---| +| Single static binary | ✅ | ❌ | ❌ | ❌ | ✅ | +| Interactive TUI | ✅ | ❌ | ❌ | ❌ | ❌ | +| Checkpoint / resume | ✅ | ❌ | ❌ | ✅ | ❌ | +| Spray mode (lockout-aware) | ✅ | ❌ | ❌ | ❌ | ❌ | +| Per-attempt JSONL output | ✅ | ⚠️ | ❌ | ❌ | ❌ (success-only) | +| SOCKS5 + proxy rotation | ✅ | ⚠️ | ❌ | ❌ | ❌ | +| Embedded SSH bad-keys (CVE-tagged) | ✅ | ❌ | ❌ | ❌ | ✅ | +| Pipeline stdin (naabu / fingerprintx / masscan) | ✅ | ❌ | ❌ | ❌ | ✅ | +| Pre-auth RDP recon (NLA / sticky-keys) | ✅ | ❌ | ❌ | ❌ | ✅ | +| Nmap gnmap + XML / Nessus / Nexpose import | ✅ | ⚠️ | ❌ | ❌ | ⚠️ (nmap only) | +| Per-module params (`-m KEY:VAL`) | ✅ | ❌ | ❌ | ❌ | partial | +| Service count | 41 | 50+ | 34 | 14 | 23 | + +> Symbols reflect documented behavior at PR time. Competing tools change quickly. + ## Supported Services `ssh` `ftp` `ftps` `telnet` `smtp` `smtp-vrfy` `imap` `pop3` `mysql` `postgres` `mssql` `mongodb` `redis` `vnc` `snmp` `smbnt` `rdp` `http` `https` `vmauthd` `teamspeak` `asterisk` `nntp` `oracle` `xmpp` `ldap` `ldaps` `winrm` `rexec` `rlogin` `rsh` `wrapper` @@ -73,7 +92,7 @@ Print discovered services from a scan file with `-P -q`: |-------|-------------| | [Installation](docs/installation.md) | Go install, release binaries, build from source, Docker | | [Usage](docs/usage.md) | CLI flags, config files, input formats | -| [Services](docs/services.md) | All 30+ protocols with ports, status, and notes | +| [Services](docs/services.md) | All 40+ protocols with ports, status, and notes | | [Examples](docs/examples.md) | Common usage patterns and recipes | | [Interactive TUI](docs/tui.md) | Keybindings, tabs, live settings | | [Advanced](docs/advanced.md) | Spray mode, proxy, resume, performance tuning | From 8e347a5761e4f02ee8194e58e14f8763f1b2f961 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:06:55 -0500 Subject: [PATCH 36/49] docs(services): document 5 new DB modules Add table rows for couchdb, elasticsearch, influxdb (stable) and neo4j, cassandra (beta) with their default ports and key notes. Update "30+ protocols" header to "40+". --- docs/services.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/services.md b/docs/services.md index 23a375a..13032ea 100644 --- a/docs/services.md +++ b/docs/services.md @@ -1,6 +1,6 @@ # Supported Services -Brutespray supports 30+ protocols. Services marked as **beta** may have edge cases — please [open an issue](https://github.com/x90skysn3k/brutespray/issues) if you encounter problems. +Brutespray supports 40+ protocols. Services marked as **beta** may have edge cases — please [open an issue](https://github.com/x90skysn3k/brutespray/issues) if you encounter problems. | Service | Default Port | Status | Notes | |---------|-------------|--------|-------| @@ -17,6 +17,11 @@ Brutespray supports 30+ protocols. Services marked as **beta** may have edge cas | mssql | 1433 | Stable | Configurable domain (`-m domain:CORP`) | | mongodb | 27017 | Stable | | | redis | 6379 | Stable | Password-only auth, configurable DB (`-m db:N`) | +| couchdb | 5984 | Stable | HTTP POST to `/_session`; `-m tls:true` for HTTPS | +| elasticsearch | 9200 | Stable | HTTP basic auth on `/_cluster/health`; `-m tls:true` for HTTPS | +| influxdb | 8086 | Stable | v2 token by default; `-m mode:v1` for InfluxDB 1.x basic auth | +| neo4j | 7687 | Beta | Bolt v5 protocol (gocql); proxy/iface routing not applied | +| cassandra | 9042 | Beta | CQL native protocol with PasswordAuthenticator | | vnc | 5900 | Stable | Password-only auth (no username) | | snmp | 161 | Stable | Supports v1/v2c (default) and v3 (`-m version:3`) | | smbnt | 445 | Stable | Use `-d DOMAIN` for domain auth | From 24193fb479a452ec402a0a13906419604b378021 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:07:23 -0500 Subject: [PATCH 37/49] docs(advanced): SSH bad-keys and pre-auth RDP recon Document the embedded SSH bad-key bundle (9 keys, CVE-tagged) with flag table and key inventory. Document the pre-auth RDP recon flow: NLA fingerprint classification and sticky-keys backdoor probe with all output variants. --- docs/advanced.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/advanced.md b/docs/advanced.md index f5219ba..be28ce3 100644 --- a/docs/advanced.md +++ b/docs/advanced.md @@ -303,3 +303,66 @@ Use `-m form-url:/path` if the CSRF form page differs from the login URL. | wrapper | `cmd` | command | Command template with %H/%P/%U/%W | | smbnt | `domain` | string | SMB domain | | rdp | `domain` | string | RDP domain | + +## SSH bad-keys + +Brutespray ships an embedded bundle of known-compromised SSH client private +keys (Rapid7 ssh-badkeys + HashiCorp Vagrant + per-vendor defaults). When +the target service is SSH, brutespray tries each bundle key with its +metadata-suggested default username before any password attempts. + +| Flag | Effect | +|---|---| +| (default) | Bad-keys pass runs first; passwords follow if no key matches | +| `--no-badkeys` | Skip the bad-keys pass entirely | +| `--badkeys-only` | Run the bad-keys pass only; skip password attempts | + +Successful matches surface as `BADKEY` lines (text mode) or `type:badkey` +JSONL records (JSON mode) carrying the vendor and CVE identifier. + +### Bundled keys + +| Vendor | Default user | CVE | +|---|---|---| +| HashiCorp Vagrant | vagrant | (insecure default — no CVE) | +| F5 BIG-IP | root | CVE-2012-1493 | +| ExaGrid EX | root | CVE-2016-1561 | +| Ceragon FibeAir | mateidu | CVE-2015-0936 | +| Monroe Electronics DASDEC | root | CVE-2013-0137 | +| Barracuda Load Balancer | root | CVE-2014-8428 | +| Array Networks vAPV/vxAG | sync | — | +| Loadbalancer.org Enterprise VA | root | — | +| Quantum DXi V1000 | root | — | + +The bundle refreshes alongside the monthly wordlist update cadence. + +## Pre-auth RDP recon + +When the target service is `rdp`, brutespray runs two pre-auth probes +before any credential attempt. Findings flow through normal output +channels (text, JSONL, TUI Findings tab) without consuming credential +attempts. Opt out with `--no-rdp-scan`. + +### NLA fingerprint + +A single X.224 Connection Request classifies the server's RDPneg response: + +- `[INFO] rdp NLA (CredSSP) enforced` — standard RDP refused +- `[INFO] rdp HybridEx (NLA + CredSSP early-user) enforced` +- `[WARN] rdp NLA not enforced — server accepts standard RDP` + +### Sticky-keys backdoor probe + +When NLA is not enforced, brutespray connects to the GINA logon screen, +sends 5× Shift keypresses (the sticky-keys trigger), and compares +framebuffer snapshots before and after. If the post-trigger frame +matches the heuristic for a cmd.exe console (predominantly black with +monospaced white text in the top region), the finding is: + +`[CRITICAL] rdp sticky-keys backdoor detected` + +If the framebuffer changed but the console signature does not match: + +`[INFO] rdp sticky-keys inconclusive — manual verification recommended` + +Probe errors land on stderr; no finding is emitted unless detection completes. From d8aa19847986b7d7501b9b6aa9b39ec8986cdc99 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:07:41 -0500 Subject: [PATCH 38/49] docs(output): Finding and BADKEY JSONL schemas Document the new JSONL record types emitted by pre-auth RDP recon (type:finding with severity/code fields) and SSH bad-key hits (type:badkey with vendor/CVE fields). --- docs/output.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/output.md b/docs/output.md index ffbc81e..52b13ee 100644 --- a/docs/output.md +++ b/docs/output.md @@ -71,3 +71,32 @@ chmod +x brutespray-nxc.sh | `-nc` | Disable colored output | | `-q` | Suppress the banner | | `--no-tui` | Use legacy text output instead of interactive TUI | + +## Finding records (JSONL) + +Pre-auth recon results emit one JSON object per line in JSONL mode: + +```json +{"type":"finding","severity":"WARN","code":"rdp-nla-missing","service":"rdp","target":"10.0.0.5:3389","message":"NLA not enforced — server accepts standard RDP without pre-auth"} +{"type":"finding","severity":"CRITICAL","code":"rdp-stickykeys","service":"rdp","target":"10.0.0.5:3389","message":"sticky-keys backdoor detected (cmd.exe shell at logon screen)"} +``` + +| Field | Description | +|---|---| +| `type` | Always `"finding"` | +| `severity` | `INFO`, `WARN`, `HIGH`, `CRITICAL` | +| `code` | Stable machine identifier: `rdp-nla-required`, `rdp-nla-missing`, `rdp-nla-hybridex`, `rdp-stickykeys`, `rdp-stickykeys-inconclusive` | +| `service` / `target` | Target identification | +| `message` | Human-readable description | +| `cve` | Present only when a CVE applies | + +## BADKEY records (JSONL) + +When SSH authentication succeeds against an embedded bad key, the per-success +output channel emits a distinct `badkey` record alongside the regular `success` +line: + +```json +{"type":"badkey","service":"ssh","target":"10.0.0.5:22","username":"vagrant","vendor":"HashiCorp Vagrant","description":"Vagrant insecure default key (any Vagrant VM pre-2014)"} +{"type":"badkey","service":"ssh","target":"10.0.0.6:22","username":"root","vendor":"F5 BIG-IP","cve":"CVE-2012-1493","description":"F5 BIG-IP 9.x-11.x default root SSH key"} +``` From 345711b50b7f451cd414992a604393ba24b5229e Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:07:54 -0500 Subject: [PATCH 39/49] docs(wordlists): SNMP community-string tiers Document the three embedded SNMP community-string tiers (default/ extended/full) selectable via -m mode:, including sizes and content categories for each tier. --- docs/wordlists.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/wordlists.md b/docs/wordlists.md index d3b70f0..e51d6d7 100644 --- a/docs/wordlists.md +++ b/docs/wordlists.md @@ -140,3 +140,22 @@ brutespray wordlist research # AI-powered wordlist research via Ollama brutespray wordlist merge # Merge research candidates into wordlists brutespray wordlist download -o path # Download rockyou.txt ``` + +## SNMP community-string tiers + +The `snmp` module ships three embedded tiers of community strings, selected +via `-m mode:`. Default behavior (no `-m mode`) uses the operator's +`-u` / `-p` values as community strings (legacy mode); the tiers replace +that with a curated list: + +| Tier | Size | Contents | +|---|---|---| +| `default` | 20 | classic public/private/cisco-style community strings | +| `extended` | 55 | + per-vendor (Cisco / HP / Juniper) enterprise defaults | +| `full` | 92 | + SCADA controllers, IP cameras, NAS / storage arrays | + +Example: + +``` +brutespray -s snmp -H 10.0.0.0/24 -m mode:full +``` From c2cd0f84abe059d9bebfec1f02ded03fed6ab994 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:08:16 -0500 Subject: [PATCH 40/49] docs(usage): new flags + stdin pipeline section Add flag table rows for --no-badkeys, --badkeys-only, --no-rdp-scan, and -c/--creds. Add Reading-targets-from-stdin subsection covering naabu, masscan, and fingerprintx pipeline examples. --- docs/usage.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index a0271db..0063602 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -40,6 +40,10 @@ | `--allow-wrapper` | Allow wrapper module to execute commands | `--allow-wrapper` | | `--output-format` | Per-attempt output format: text (default) or json | `--output-format json` | | `--proxy-list` | File with proxy list for rotation (one per line) | `--proxy-list proxies.txt` | +| `--no-badkeys` | Skip the embedded SSH bad-keys pre-pass | `--no-badkeys` | +| `--badkeys-only` | Run the embedded SSH bad-keys pre-pass only; skip passwords | `--badkeys-only` | +| `--no-rdp-scan` | Skip pre-auth RDP recon (NLA + sticky-keys) | `--no-rdp-scan` | +| `-c`, `--creds` | Inline credential pairs, comma-separated: `admin:admin,root:toor` | `-c admin:admin,root:toor` | ## YAML Config File @@ -189,3 +193,16 @@ root:root admin:admin user1:password123 ``` + +### Reading targets from stdin + +When `-f` is not supplied and stdin is a pipe, brutespray reads targets +from stdin and auto-detects the input format (naabu line, Nerva URI, +Nerva JSON, fingerprintx JSON, masscan JSON). The target list is +appended to whatever `-H` arguments were given on the CLI. + +``` +naabu -host 10.0.0.0/24 -p 22 -silent | brutespray -u root -P wordlist/ssh/password +masscan -p22,3389 10.0.0.0/24 -oJ - | brutespray -u admin -p admin +fingerprintx -t 10.0.0.0/24 --json | brutespray --no-badkeys +``` From 3c4867065f9460b9b5ffff733c61178a3b35c893 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:08:34 -0500 Subject: [PATCH 41/49] docs(pipeline): end-to-end recon walkthrough New file documenting stdin pipeline integration with naabu, fingerprintx, and masscan. Covers all five auto-detected input formats and includes four example pipelines: credential brute-forcing, SSH bad-keys-only scan, and RDP recon with JSONL output. --- docs/pipeline.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 docs/pipeline.md diff --git a/docs/pipeline.md b/docs/pipeline.md new file mode 100644 index 0000000..375360e --- /dev/null +++ b/docs/pipeline.md @@ -0,0 +1,64 @@ +# Pipeline integration + +Brutespray accepts targets on stdin and auto-detects the input format, +making it a natural terminator for modern recon pipelines. + +Supported input formats: + +- **naabu** — bare `host:port` lines +- **Nerva URI** — `scheme://host:port` lines (e.g. `ssh://10.0.0.5:22`) +- **Nerva JSON** — newline-delimited JSON objects with `ip`/`port`/`protocol` +- **fingerprintx JSON** — newline-delimited JSON objects with `host`/`port`/`service` +- **masscan JSON** — masscan `-oJ` array of host records + +## naabu → brutespray + +``` +naabu -host 10.0.0.0/24 -p 22,3306,3389,5984 -silent \ + | brutespray -u root -P wordlist/_base/password +``` + +naabu emits `host:port` lines; brutespray maps each port to its canonical +service via the embedded default-port table. + +## naabu → fingerprintx → brutespray + +``` +naabu -host 10.0.0.0/24 -silent \ + | fingerprintx --json \ + | brutespray -u root -P wordlist/_base/password +``` + +fingerprintx classifies the service explicitly — brutespray uses that +directly instead of falling back to the port-table. + +## masscan → brutespray + +``` +masscan -p22,3389,5984 10.0.0.0/24 -oJ - \ + | brutespray --no-badkeys -u admin -p admin +``` + +masscan's JSON array is decoded; only open ports survive; closed and +filtered are dropped silently. + +## SSH bad-keys only + +``` +masscan -p22 10.0.0.0/24 -oJ - \ + | brutespray --badkeys-only --output-format json -o results.jsonl +``` + +Skips password attempts entirely. Each successful match emits a +`type:badkey` JSONL record carrying the vendor and CVE. + +## RDP recon scan + +``` +naabu -host 10.0.0.0/24 -p 3389 -silent \ + | brutespray -s rdp -u test -p test --output-format json -o rdp-findings.jsonl +``` + +The NLA fingerprint and sticky-keys probe run before any credential +attempts. Findings stream into the same JSONL output channel as auth +attempts — filter by `type=="finding"` downstream. From d9e62e265b4f3baf20d9545511415829b78cfea5 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:11:40 -0500 Subject: [PATCH 42/49] fix(lint): unchecked io.Copy in test helpers + deprecated neo4j.Config --- brute/neo4j.go | 3 ++- brutespray/dispatch_rdp_test.go | 2 +- modules/output_finding_test.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/brute/neo4j.go b/brute/neo4j.go index 99f608d..bb35368 100644 --- a/brute/neo4j.go +++ b/brute/neo4j.go @@ -7,6 +7,7 @@ import ( "time" "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/neo4j/neo4j-go-driver/v5/neo4j/config" "github.com/x90skysn3k/brutespray/v2/modules" ) @@ -19,7 +20,7 @@ func BruteNeo4j(host string, port int, user, password string, timeout time.Durat return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { uri := fmt.Sprintf("bolt://%s:%d", host, port) driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(user, password, ""), - func(c *neo4j.Config) { + func(c *config.Config) { c.SocketConnectTimeout = timeout }) if err != nil { diff --git a/brutespray/dispatch_rdp_test.go b/brutespray/dispatch_rdp_test.go index ebeb0e9..3765afe 100644 --- a/brutespray/dispatch_rdp_test.go +++ b/brutespray/dispatch_rdp_test.go @@ -19,7 +19,7 @@ func captureStdoutDispatch(fn func()) string { w.Close() os.Stdout = old var buf bytes.Buffer - io.Copy(&buf, r) + _, _ = io.Copy(&buf, r) return buf.String() } diff --git a/modules/output_finding_test.go b/modules/output_finding_test.go index 5e97859..c9ebb4e 100644 --- a/modules/output_finding_test.go +++ b/modules/output_finding_test.go @@ -17,7 +17,7 @@ func captureStdoutModules(fn func()) string { w.Close() os.Stdout = old var buf bytes.Buffer - io.Copy(&buf, r) + _, _ = io.Copy(&buf, r) return buf.String() } From 489310be5638807def172c03cd643754ae545034 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 10:59:03 -0500 Subject: [PATCH 43/49] fix(wordlist): commit influxdb seed files for embed.go to find --- wordlist/influxdb/password | 8 ++++++++ wordlist/influxdb/user | 5 +++++ 2 files changed, 13 insertions(+) create mode 100644 wordlist/influxdb/password create mode 100644 wordlist/influxdb/user diff --git a/wordlist/influxdb/password b/wordlist/influxdb/password new file mode 100644 index 0000000..8e43715 --- /dev/null +++ b/wordlist/influxdb/password @@ -0,0 +1,8 @@ +admin +influxdb +password +123456 +secret +password123 +root +guest diff --git a/wordlist/influxdb/user b/wordlist/influxdb/user new file mode 100644 index 0000000..c654b1a --- /dev/null +++ b/wordlist/influxdb/user @@ -0,0 +1,5 @@ +admin +influxdb +root +guest +user From efb7b4bfeb001c4994dcac18c114edfb60b583b6 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 11:02:04 -0500 Subject: [PATCH 44/49] chore: update author handle to @x90sky --- .github/FUNDING.yml | 2 +- README.md | 2 +- banner/banner.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 409d7d2..1f04d65 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,5 +1,5 @@ # These are supported funding model platforms github: x90skysn3k -patreon: t1d3nio +patreon: x90sky diff --git a/README.md b/README.md index 259a14f..22fea64 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Version](https://img.shields.io/badge/Version-2.6.1-red)[![goreleaser](https://github.com/x90skysn3k/brutespray/actions/workflows/release.yml/badge.svg)](https://github.com/x90skysn3k/brutespray/actions/workflows/release.yml)[![Go Report Card](https://goreportcard.com/badge/github.com/x90skysn3k/brutespray/v2)](https://goreportcard.com/report/github.com/x90skysn3k/brutespray/v2) -Created by: Shane Young/@t1d3nio && Jacob Robles/@shellfail +Created by: Shane Young/@x90sky && Jacob Robles/@shellfail Inspired by: Leon Johnson/@sho-luv diff --git a/banner/banner.go b/banner/banner.go index 1dc1c1a..a12ce0c 100644 --- a/banner/banner.go +++ b/banner/banner.go @@ -68,7 +68,7 @@ func Banner(version string, banner_flag bool, noColor bool) { ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ` + "\n" quiet_banner := `Brutespray ` + version + ` -Created by: Shane Young/@t1d3nio && Jacob Robles/@shellfail +Created by: Shane Young/@x90sky && Jacob Robles/@shellfail Inspired by: Leon Johnson/@sho-luv` //ascii art by: Cara Pearson if !banner_flag { From 7a2739a2de82d3d1515cafcd1d274e8d1cbdaaa0 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 11:07:13 -0500 Subject: [PATCH 45/49] docs: rename spec/plan and update internal cross-refs --- ...feature-parity.md => 2026-05-29-recon-pipeline-and-modules.md} | 0 ...-design.md => 2026-05-29-recon-pipeline-and-modules-design.md} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/superpowers/plans/{2026-05-29-brutus-feature-parity.md => 2026-05-29-recon-pipeline-and-modules.md} (100%) rename docs/superpowers/specs/{2026-05-29-brutus-feature-parity-design.md => 2026-05-29-recon-pipeline-and-modules-design.md} (100%) diff --git a/docs/superpowers/plans/2026-05-29-brutus-feature-parity.md b/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md similarity index 100% rename from docs/superpowers/plans/2026-05-29-brutus-feature-parity.md rename to docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md diff --git a/docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md b/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md similarity index 100% rename from docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md rename to docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md From 1341ad6c2d1bf58cf66f1adeb5fbc01e53e6eae1 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 11:07:51 -0500 Subject: [PATCH 46/49] docs: drop "Brutus feature-parity" from spec/plan titles + cross-refs --- .../plans/2026-05-29-recon-pipeline-and-modules.md | 10 +++++----- .../2026-05-29-recon-pipeline-and-modules-design.md | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md b/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md index f99d56b..b513237 100644 --- a/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md +++ b/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md @@ -1,4 +1,4 @@ -# Brutus Feature-Parity Implementation Plan +# Pre-auth Recon, Stdin Pipeline, and New Modules — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. @@ -8,7 +8,7 @@ **Tech Stack:** Go 1.21+, existing deps (`x90skysn3k/grdp`, `go-redis`, `hirochachacha/go-smb2`, `bubbletea`, `golang.org/x/crypto/ssh`), new deps (`github.com/neo4j/neo4j-go-driver/v5`, `github.com/gocql/gocql`), `go:embed` for bundled keys + wordlists. -**Spec:** `docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md` +**Spec:** `docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md` --- @@ -3245,11 +3245,11 @@ Open the docs/release pages of hydra, medusa, ncrack, brutus. Verify each ✅/ ```bash gh pr create --base master --head dev \ - --title "feat: Brutus feature-parity — bad-keys, RDP pre-auth recon, stdin pipeline, 5 DB modules" \ + --title "feat: pre-auth recon (SSH bad-keys, RDP NLA + sticky-keys), stdin pipeline, 5 DB modules" \ --body "$(cat <<'EOF' ## Summary -Borrows the four high-value capabilities Brutus introduced into the multi-protocol cred-test space and lands them on brutespray, plus a brutespray-vs-others comparison table for the README so positioning is in sync with the new feature set. +Borrows four high-value capabilities surveyed in the contemporary cred-test landscape and lands them on brutespray, plus a brutespray-vs-others comparison table for the README so positioning is in sync with the new feature set. ### Pre-auth recon - Embedded SSH bad-keys bundle (Rapid7 ssh-badkeys + Vagrant + per-vendor keys, CVE-tagged) @@ -3275,7 +3275,7 @@ Borrows the four high-value capabilities Brutus introduced into the multi-protoc - `docs/usage.md`: new flags + stdin section - `docs/pipeline.md` (new): end-to-end recon walkthrough -Spec: `docs/superpowers/specs/2026-05-29-brutus-feature-parity-design.md` +Spec: `docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md` ## Test plan diff --git a/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md b/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md index 6750fe0..1267402 100644 --- a/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md +++ b/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md @@ -1,4 +1,4 @@ -# Brutus Feature-Parity Spec +# Pre-auth Recon, Stdin Pipeline, and New Modules — Design Spec **Date:** 2026-05-29 **Branch:** dev From 9a498ea4bd5adf9487ab48570005573c28a1fac7 Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 11:10:09 -0500 Subject: [PATCH 47/49] build: bump grdp to v1.0.3 + drop local replace directive --- go.mod | 4 +--- go.sum | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 92a7563..4e449b8 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/x90skysn3k/brutespray/v2 go 1.26.1 -replace github.com/x90skysn3k/grdp => ../grdp - require ( github.com/Azure/go-ntlmssp v0.1.1 github.com/charmbracelet/bubbles v1.0.0 @@ -25,7 +23,7 @@ require ( github.com/neo4j/neo4j-go-driver/v5 v5.28.4 github.com/pterm/pterm v0.12.83 github.com/sijms/go-ora/v2 v2.9.0 - github.com/x90skysn3k/grdp v1.0.2 + github.com/x90skysn3k/grdp v1.0.3 go.mongodb.org/mongo-driver v1.17.9 golang.org/x/crypto v0.50.0 golang.org/x/net v0.53.0 diff --git a/go.sum b/go.sum index d87d556..c405d55 100644 --- a/go.sum +++ b/go.sum @@ -283,6 +283,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde h1:AMNpJRc7P+GTwVbl8DkK2I9I8BBUzNiHuH/tlxrpan0= github.com/tidwall/transform v0.0.0-20201103190739-32f242e2dbde/go.mod h1:MvrEmduDUz4ST5pGZ7CABCnOU5f3ZiOAZzT6b1A6nX8= github.com/twitchyliquid64/golang-asm v0.0.0-20190126203739-365674df15fc/go.mod h1:NoCfSFWosfqMqmmD7hApkirIK9ozpHjxRnRxs1l413A= +github.com/x90skysn3k/grdp v1.0.3 h1:K7BOs8fY/GdOv5Z3yez0gb7s8UOT4j0fIE3h1oPwWn0= +github.com/x90skysn3k/grdp v1.0.3/go.mod h1:H/Klzfgyv7SAj3wtrAAxlfBlUXW+Ck0YZ92wetZTPcA= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= From 2a3223fb4fa562473ac444fef9917231b7ae09be Mon Sep 17 00:00:00 2001 From: x90skysn3k Date: Fri, 29 May 2026 11:11:15 -0500 Subject: [PATCH 48/49] chore: remove docs/superpowers (internal planning artifacts) --- .gitignore | 1 + .../2026-05-29-recon-pipeline-and-modules.md | 3321 ----------------- ...05-29-recon-pipeline-and-modules-design.md | 207 - 3 files changed, 1 insertion(+), 3528 deletions(-) delete mode 100644 docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md delete mode 100644 docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md diff --git a/.gitignore b/.gitignore index 9cc4af0..8d0f6a6 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ wordlist/teamspeak/ wordlist/xmpp/ go.work go.work.sum +docs/superpowers/ diff --git a/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md b/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md deleted file mode 100644 index b513237..0000000 --- a/docs/superpowers/plans/2026-05-29-recon-pipeline-and-modules.md +++ /dev/null @@ -1,3321 +0,0 @@ -# Pre-auth Recon, Stdin Pipeline, and New Modules — Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Add SSH bad-keys bundle, pre-auth RDP recon (NLA + sticky-keys), stdin pipeline auto-detection, five new database modules (Neo4j/Cassandra/CouchDB/Elasticsearch/InfluxDB), SNMP wordlist tiering, inline cred pairs, and a brutespray-vs-others README comparison table — all in one combined release PR on `dev`. - -**Architecture:** Follows existing brutespray patterns — `init()` + `brute.Register()` for modules, `brute.ModuleParams` for flags, `ConnectionManager.Dial()` for network I/O, `time.NewTimer`+`select` timeouts. New `brute/badkeys/` package uses `go:embed` to bundle vendored Rapid7 ssh-badkeys. RDP pre-auth scans extend `brute/rdp.go` with a coordinated change in the sibling `../grdp/` repo (we own it). Stdin auto-detect lives in a new `modules/parse_stream.go` invoked from `brutespray.Execute()` when `os.Stdin` is a pipe. New result fields `Finding` and `KeyMatch` propagate through `BruteResult` → `output.go` → TUI. - -**Tech Stack:** Go 1.21+, existing deps (`x90skysn3k/grdp`, `go-redis`, `hirochachacha/go-smb2`, `bubbletea`, `golang.org/x/crypto/ssh`), new deps (`github.com/neo4j/neo4j-go-driver/v5`, `github.com/gocql/gocql`), `go:embed` for bundled keys + wordlists. - -**Spec:** `docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md` - ---- - -## Codebase notes (verify before referencing) - -- `modules.ConnectionManager` is constructed by an existing factory. Identify the actual constructor name (`NewConnectionManager`, `NewCM`, or similar) by grepping `func New.*ConnectionManager` in `modules/connections.go`, and substitute it in every test file that calls one. The plan uses `modules.NewConnectionManager()` as a placeholder name. -- Existing dispatcher already enqueues credentials through a helper — find it (look near the existing user/password loop in `brutespray/dispatch.go`) and reuse, do not introduce `queueCred` as a new function name unless it does not exist. -- The TUI active-flag and model singleton names (`tuiActive`, `tuiModel`) used in the plan reflect convention; substitute the actual identifiers from `tui/` if they differ. -- The output-sink names (`jsonOutput`, `jsonSink`, `stdoutSink`) used in plan code blocks are placeholders for whatever globals/struct fields `modules/output.go` already uses. Reuse those; do not introduce new globals. - -When in doubt, read the surrounding code first and adapt the snippet — the plan's *intent* is what matters, not the exact identifier names. - -## Working Conventions - -- TDD throughout: write the failing test first, run to confirm fail, implement minimal code, run to confirm pass, then commit. -- One concept per commit. Commit at the end of each numbered Task unless the task explicitly spans multiple commits. -- Build target: `go build -o brutespray .` from repo root. -- Run race-clean: `go test ./... -race`. -- Lint: `golangci-lint run`. -- All commits authored by the user (no Co-Authored-By trailer per `[[feedback_commit_authorship]]`). -- CLAUDE.md updates are local only — do NOT `git add` it per `[[feedback_no_claude_md]]`. - ---- - -# Phase A — Pre-auth recon - -## Task A1: Extend `BruteResult` with `Finding` and `KeyMatch` - -**Files:** -- Modify: `brute/run.go` (around lines 162-262 where BruteResult is defined and returned by value) -- Test: `brute/result_test.go` (new) - -- [ ] **Step 1: Add Finding and KeyMatch types to `brute/run.go`** - -Locate the `type BruteResult struct` block and insert two new fields plus the supporting types just above it: - -```go -// Finding represents a pre-auth recon result (e.g. SSH bad-key match, -// RDP NLA missing, RDP sticky-keys backdoor). Modules can return findings -// without a successful authentication attempt. -type Finding struct { - Severity string // INFO, WARN, HIGH, CRITICAL - Code string // e.g. "rdp-nla-missing", "rdp-stickykeys", "ssh-badkey" - Message string - CVE string // optional, e.g. "CVE-2012-1493" -} - -// KeyMatch records a successful SSH key authentication originating from -// the embedded bad-keys bundle. -type KeyMatch struct { - Fingerprint string - Vendor string - CVE string - Description string -} - -type BruteResult struct { - AuthSuccess bool - ConnectionSuccess bool - Error error - Banner string - RetryDelay time.Duration - SkipUser bool - Finding *Finding // pre-auth recon result, nil if none - KeyMatch *KeyMatch // SSH bad-key match, nil if none -} -``` - -- [ ] **Step 2: Propagate new fields through the value conversion at `brute/run.go:262`** - -Find the line `return BruteResult{AuthSuccess: modResult.AuthSuccess, ConnectionSuccess: modResult.ConnectionSuccess, Banner: modResult.Banner, SkipUser: modResult.SkipUser}` and extend it: - -```go -return BruteResult{ - AuthSuccess: modResult.AuthSuccess, - ConnectionSuccess: modResult.ConnectionSuccess, - Banner: modResult.Banner, - SkipUser: modResult.SkipUser, - Finding: modResult.Finding, - KeyMatch: modResult.KeyMatch, -} -``` - -- [ ] **Step 3: Write the failing test** - -Create `brute/result_test.go`: - -```go -package brute - -import "testing" - -func TestBruteResultCarriesFinding(t *testing.T) { - r := &BruteResult{ - ConnectionSuccess: true, - Finding: &Finding{ - Severity: "CRITICAL", - Code: "rdp-stickykeys", - Message: "sticky-keys backdoor detected", - }, - } - if r.Finding == nil || r.Finding.Code != "rdp-stickykeys" { - t.Fatalf("Finding not carried on BruteResult") - } -} - -func TestBruteResultCarriesKeyMatch(t *testing.T) { - r := &BruteResult{ - AuthSuccess: true, - ConnectionSuccess: true, - KeyMatch: &KeyMatch{ - Fingerprint: "SHA256:abc", - Vendor: "Vagrant", - CVE: "CVE-2015-1338", - }, - } - if r.KeyMatch == nil || r.KeyMatch.Vendor != "Vagrant" { - t.Fatalf("KeyMatch not carried on BruteResult") - } -} -``` - -- [ ] **Step 4: Run tests** - -Run: `go test ./brute/ -run TestBruteResult -v` -Expected: PASS for both. - -- [ ] **Step 5: Verify nothing else broke** - -Run: `go build ./... && go test ./... -count=1` -Expected: build succeeds; existing tests pass (Finding/KeyMatch are nil-default so no behavior change yet). - -- [ ] **Step 6: Commit** - -```bash -git add brute/run.go brute/result_test.go -git commit -m "feat(brute): add Finding and KeyMatch fields to BruteResult" -``` - ---- - -## Task A2: Vendor `ssh-badkeys` snapshot + metadata - -**Files:** -- Create: `brute/badkeys/keys/` (directory with vendored .pem files) -- Create: `brute/badkeys/metadata.yaml` -- Create: `brute/badkeys/embed.go` -- Create: `brute/badkeys/registry.go` -- Create: `brute/badkeys/registry_test.go` - -- [ ] **Step 1: Snapshot Rapid7 ssh-badkeys** - -```bash -mkdir -p brute/badkeys/keys -cd /tmp && git clone --depth 1 https://github.com/rapid7/ssh-badkeys.git -cp /tmp/ssh-badkeys/authorized/* /home/shane/Documents/work/brutespray/brute/badkeys/keys/ || true -cp /tmp/ssh-badkeys/unauthorized/* /home/shane/Documents/work/brutespray/brute/badkeys/keys/ || true -cd /home/shane/Documents/work/brutespray -# Add Vagrant insecure key -curl -sSL https://raw.githubusercontent.com/hashicorp/vagrant/main/keys/vagrant -o brute/badkeys/keys/vagrant -ls brute/badkeys/keys/ | wc -l -``` - -Expected: ≥10 key files present. - -- [ ] **Step 2: Author metadata.yaml** - -Create `brute/badkeys/metadata.yaml` mapping each key filename to its metadata. Sample shape (full file must cover every key in `keys/`): - -```yaml -- file: vagrant - username: vagrant - vendor: HashiCorp Vagrant - cve: "" - description: Vagrant insecure default key (any Vagrant VM pre-2014) -- file: f5_big_ip - username: root - vendor: F5 BIG-IP - cve: CVE-2012-1493 - description: F5 BIG-IP 9.x-11.x default root SSH key -- file: exagrid - username: root - vendor: ExaGrid - cve: CVE-2016-1561 - description: ExaGrid EX series default support key -- file: ceragon_fibeair - username: mateidu - vendor: Ceragon FibeAir - cve: "" - description: Ceragon FibeAir IP-10 microwave radio default key -``` - -For every additional file in `keys/`, append an entry. Files without a known vendor get `vendor: "Rapid7 ssh-badkeys (origin unknown)"` and `username: root`. - -- [ ] **Step 3: Write `brute/badkeys/embed.go`** - -```go -package badkeys - -import "embed" - -//go:embed keys/* metadata.yaml -var assets embed.FS -``` - -- [ ] **Step 4: Write the failing test** - -Create `brute/badkeys/registry_test.go`: - -```go -package badkeys - -import "testing" - -func TestLoadReturnsNonEmptyBundle(t *testing.T) { - bundle, err := Load() - if err != nil { - t.Fatalf("Load: %v", err) - } - if len(bundle) < 5 { - t.Fatalf("expected >=5 keys, got %d", len(bundle)) - } -} - -func TestLoadParsesVagrantEntry(t *testing.T) { - bundle, err := Load() - if err != nil { - t.Fatalf("Load: %v", err) - } - for _, e := range bundle { - if e.Vendor == "HashiCorp Vagrant" { - if e.Username != "vagrant" { - t.Fatalf("vagrant entry username = %q, want vagrant", e.Username) - } - if len(e.PEM) < 100 { - t.Fatalf("vagrant PEM too short: %d bytes", len(e.PEM)) - } - return - } - } - t.Fatal("no Vagrant entry found in bundle") -} -``` - -- [ ] **Step 5: Implement `brute/badkeys/registry.go`** - -```go -// Package badkeys provides a curated, embedded bundle of known-compromised -// SSH private keys (Rapid7 ssh-badkeys + Vagrant + vendor defaults). Each -// entry pairs a key with its default username and CVE metadata so brute -// modules can surface CVE-tagged findings without external files. -package badkeys - -import ( - "crypto/sha256" - "encoding/hex" - "fmt" - - "gopkg.in/yaml.v3" -) - -type Entry struct { - File string - Username string - Vendor string - CVE string - Description string - PEM []byte - Fingerprint string // sha256 hex of PEM bytes -} - -type metaEntry struct { - File string `yaml:"file"` - Username string `yaml:"username"` - Vendor string `yaml:"vendor"` - CVE string `yaml:"cve"` - Description string `yaml:"description"` -} - -func Load() ([]Entry, error) { - raw, err := assets.ReadFile("metadata.yaml") - if err != nil { - return nil, fmt.Errorf("read metadata.yaml: %w", err) - } - var metas []metaEntry - if err := yaml.Unmarshal(raw, &metas); err != nil { - return nil, fmt.Errorf("parse metadata.yaml: %w", err) - } - out := make([]Entry, 0, len(metas)) - for _, m := range metas { - pem, err := assets.ReadFile("keys/" + m.File) - if err != nil { - return nil, fmt.Errorf("read keys/%s: %w", m.File, err) - } - sum := sha256.Sum256(pem) - out = append(out, Entry{ - File: m.File, - Username: m.Username, - Vendor: m.Vendor, - CVE: m.CVE, - Description: m.Description, - PEM: pem, - Fingerprint: hex.EncodeToString(sum[:]), - }) - } - return out, nil -} -``` - -- [ ] **Step 6: Add `gopkg.in/yaml.v3` dep** - -Run: `go get gopkg.in/yaml.v3@latest && go mod tidy` - -- [ ] **Step 7: Run tests** - -Run: `go test ./brute/badkeys/ -v` -Expected: both tests PASS. - -- [ ] **Step 8: Commit** - -```bash -git add brute/badkeys/ go.mod go.sum -git commit -m "feat(badkeys): vendor Rapid7 ssh-badkeys + Vagrant + vendor key bundle" -``` - ---- - -## Task A3: Integrate bad-keys into `brute/ssh.go` - -**Files:** -- Modify: `brute/ssh.go` -- Test: `brute/ssh_badkeys_test.go` (new) - -- [ ] **Step 1: Write the failing test** - -Create `brute/ssh_badkeys_test.go`: - -```go -package brute - -import ( - "testing" - - "github.com/x90skysn3k/brutespray/v2/brute/badkeys" -) - -func TestBadKeysPlanCoversBundle(t *testing.T) { - bundle, err := badkeys.Load() - if err != nil { - t.Fatalf("badkeys.Load: %v", err) - } - plan := PlanBadKeyAttempts(bundle, "") - if len(plan) != len(bundle) { - t.Fatalf("plan size = %d, bundle = %d", len(plan), len(bundle)) - } - for _, a := range plan { - if a.Username == "" { - t.Fatalf("attempt missing username: %+v", a) - } - } -} - -func TestBadKeysPlanRespectsExplicitUser(t *testing.T) { - bundle, err := badkeys.Load() - if err != nil { - t.Fatalf("badkeys.Load: %v", err) - } - plan := PlanBadKeyAttempts(bundle, "admin") - for _, a := range plan { - if a.Username != "admin" { - t.Fatalf("explicit username override failed: %s", a.Username) - } - } -} -``` - -- [ ] **Step 2: Implement `PlanBadKeyAttempts` in `brute/ssh.go`** - -Add near the top of `brute/ssh.go` (after the import block): - -```go -// BadKeyAttempt is one user+key pair to try during the bad-keys pass. -type BadKeyAttempt struct { - Username string - Entry badkeys.Entry -} - -// PlanBadKeyAttempts produces the ordered list of SSH bad-key attempts for a -// host. When userOverride is non-empty (operator passed -u explicitly), every -// attempt uses that username; otherwise the entry's metadata-suggested user -// is used (root for F5, vagrant for Vagrant, etc.). -func PlanBadKeyAttempts(bundle []badkeys.Entry, userOverride string) []BadKeyAttempt { - out := make([]BadKeyAttempt, 0, len(bundle)) - for _, e := range bundle { - u := e.Username - if userOverride != "" { - u = userOverride - } - out = append(out, BadKeyAttempt{Username: u, Entry: e}) - } - return out -} -``` - -Add the import: - -```go -import ( - // ... existing imports ... - "github.com/x90skysn3k/brutespray/v2/brute/badkeys" -) -``` - -- [ ] **Step 3: Run the test** - -Run: `go test ./brute/ -run TestBadKeys -v` -Expected: PASS. - -- [ ] **Step 4: Wire bad-key execution into `BruteSSH`** - -In `brute/ssh.go`, near the top of `BruteSSH` (before the existing key/password branch), add: - -```go -// Bad-keys pre-pass: when the magic password marker "::badkey::" is in play, -// the caller is asking us to attempt a single embedded bad-key. The dispatcher -// (Task A5) emits these as synthetic credential pairs before regular passwords. -if strings.HasPrefix(password, "::badkey::") { - idx, err := strconv.Atoi(strings.TrimPrefix(password, "::badkey::")) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, - Error: fmt.Errorf("invalid badkey index: %w", err)} - } - bundle, err := badkeys.Load() - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, - Error: fmt.Errorf("loading badkeys bundle: %w", err)} - } - if idx < 0 || idx >= len(bundle) { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, - Error: fmt.Errorf("badkey index out of range: %d", idx)} - } - return attemptBadKey(host, port, user, bundle[idx], timeout, cm) -} -``` - -Then implement `attemptBadKey` at the bottom of `brute/ssh.go`: - -```go -func attemptBadKey(host string, port int, user string, e badkeys.Entry, - timeout time.Duration, cm *modules.ConnectionManager) *BruteResult { - signer, err := ssh.ParsePrivateKey(e.PEM) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, - Error: fmt.Errorf("parsing badkey %s: %w", e.File, err)} - } - cfg := &ssh.ClientConfig{ - User: user, - Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - Timeout: timeout, - } - conn, err := cm.Dial("tcp", net.JoinHostPort(host, strconv.Itoa(port))) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - c, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, strconv.Itoa(port)), cfg) - if err != nil { - conn.Close() - if strings.Contains(err.Error(), "unable to authenticate") { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} - } - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - client := ssh.NewClient(c, chans, reqs) - defer client.Close() - return &BruteResult{ - AuthSuccess: true, - ConnectionSuccess: true, - KeyMatch: &KeyMatch{ - Fingerprint: e.Fingerprint, - Vendor: e.Vendor, - CVE: e.CVE, - Description: e.Description, - }, - } -} -``` - -Add `"strconv"` to imports if not already present. - -- [ ] **Step 5: Run the SSH tests** - -Run: `go test ./brute/ -run TestSSH -v -race` -Expected: existing SSH tests pass; no new failures. - -- [ ] **Step 6: Commit** - -```bash -git add brute/ssh.go brute/ssh_badkeys_test.go -git commit -m "feat(ssh): execute embedded bad-key attempts via ::badkey:: marker" -``` - ---- - -## Task A4: Dispatcher emits bad-key attempts + `--no-badkeys` / `--badkeys-only` flags - -**Files:** -- Modify: `brutespray/config.go` -- Modify: `brutespray/dispatch.go` -- Test: `brutespray/dispatch_badkeys_test.go` (new) - -- [ ] **Step 1: Add the two flags to `brutespray/config.go`** - -In the flag-registration block (near the other boolean flags like `-spray`), add: - -```go -flag.BoolVar(&Cfg.NoBadKeys, "no-badkeys", false, "Skip SSH bad-keys pre-pass for SSH targets") -flag.BoolVar(&Cfg.BadKeysOnly, "badkeys-only", false, "Run SSH bad-keys pre-pass only; skip password attempts") -``` - -Add to `Config` struct: - -```go -NoBadKeys bool -BadKeysOnly bool -``` - -- [ ] **Step 2: Write the failing test** - -Create `brutespray/dispatch_badkeys_test.go`: - -```go -package brutespray - -import ( - "testing" - - "github.com/x90skysn3k/brutespray/v2/brute/badkeys" -) - -func TestBuildBadKeyCredsProducesMarkers(t *testing.T) { - bundle, err := badkeys.Load() - if err != nil { - t.Fatalf("badkeys.Load: %v", err) - } - pairs := BuildBadKeyCreds(bundle, "") - if len(pairs) != len(bundle) { - t.Fatalf("got %d pairs, want %d", len(pairs), len(bundle)) - } - for i, p := range pairs { - wantPass := "::badkey::" + itoa(i) - if p.Password != wantPass { - t.Fatalf("pair[%d].Password = %q, want %q", i, p.Password, wantPass) - } - } -} - -func itoa(i int) string { - if i == 0 { - return "0" - } - var b []byte - for i > 0 { - b = append([]byte{byte('0' + i%10)}, b...) - i /= 10 - } - return string(b) -} -``` - -- [ ] **Step 3: Implement `BuildBadKeyCreds` in `brutespray/dispatch.go`** - -Add near the existing credential-build helpers: - -```go -// CredPair is a single user/password attempt the dispatcher will enqueue. -// (If this type already exists in dispatch.go, reuse it instead.) -type CredPair struct { - User string - Password string -} - -// BuildBadKeyCreds turns the embedded bad-keys bundle into a list of synthetic -// credential pairs. The password field carries "::badkey::N" where N indexes -// into the bundle; BruteSSH unpacks this marker. When userOverride is set -// (operator passed -u explicitly), every pair uses that username; otherwise -// each entry's metadata-suggested user is used. -func BuildBadKeyCreds(bundle []badkeys.Entry, userOverride string) []CredPair { - out := make([]CredPair, 0, len(bundle)) - for i, e := range bundle { - u := e.Username - if userOverride != "" { - u = userOverride - } - out = append(out, CredPair{ - User: u, - Password: fmt.Sprintf("::badkey::%d", i), - }) - } - return out -} -``` - -Add imports: - -```go -import ( - // ... existing ... - "fmt" - "github.com/x90skysn3k/brutespray/v2/brute/badkeys" -) -``` - -- [ ] **Step 4: Wire bad-key creds into `ProcessHost` for SSH targets** - -Find `ProcessHost` in `dispatch.go`. Add a branch at the top of the per-host loop, before password creds are enqueued: - -```go -if host.Service == "ssh" && !Cfg.NoBadKeys { - bundle, err := badkeys.Load() - if err == nil { - for _, pair := range BuildBadKeyCreds(bundle, explicitUser) { - queueCred(pair.User, pair.Password) - } - } -} -if host.Service == "ssh" && Cfg.BadKeysOnly { - return // skip the regular password loop entirely -} -``` - -(`explicitUser` and `queueCred` names match whatever the dispatcher already uses — adapt to actual identifiers. Reuse the existing enqueue helper rather than introducing a new one.) - -- [ ] **Step 5: Run tests** - -Run: `go test ./brutespray/ -run TestBuildBadKey -v && go test ./... -count=1` -Expected: new test passes; existing dispatch tests pass. - -- [ ] **Step 6: Commit** - -```bash -git add brutespray/config.go brutespray/dispatch.go brutespray/dispatch_badkeys_test.go -git commit -m "feat(dispatch): emit bad-key attempts for SSH targets with opt-out flags" -``` - ---- - -## Task A5: RDP NLA fingerprint scan in grdp + brutespray - -**Files:** -- Modify (sibling repo): `../grdp/client/nla_check.go` (new) -- Modify: `brute/rdp.go` -- Test: `brute/rdp_nla_test.go` (new) - -- [ ] **Step 1: Add NLA check to grdp** - -In `../grdp/client/nla_check.go`: - -```go -package client - -import ( - "context" - "encoding/binary" - "fmt" - "net" - "time" -) - -// NLAStatus describes the result of a TCP-only NLA fingerprint probe. -type NLAStatus int - -const ( - NLAUnknown NLAStatus = iota - NLANotEnforced - NLARequired - NLAHybridEx -) - -// FingerprintNLA opens a TCP connection to the RDP target, sends an -// X.224 Connection Request with RDPneg requesting all protocols, and -// classifies the server response. -func FingerprintNLA(ctx context.Context, target string, timeout time.Duration) (NLAStatus, error) { - d := net.Dialer{Timeout: timeout} - conn, err := d.DialContext(ctx, "tcp", target) - if err != nil { - return NLAUnknown, fmt.Errorf("dial: %w", err) - } - defer conn.Close() - _ = conn.SetDeadline(time.Now().Add(timeout)) - - // X.224 Connection Request with RDPneg requesting PROTOCOL_RDP|SSL|HYBRID|HYBRID_EX (0x0F). - req := []byte{ - 0x03, 0x00, 0x00, 0x13, // TPKT header, length 19 - 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x00, 0x00, // RDPneg req, requested 0x0F - } - if _, err := conn.Write(req); err != nil { - return NLAUnknown, fmt.Errorf("write: %w", err) - } - resp := make([]byte, 64) - n, err := conn.Read(resp) - if err != nil || n < 19 { - return NLAUnknown, fmt.Errorf("read: %w", err) - } - // Bytes 11..18 carry the RDPneg response. Byte 11 is type: - // 0x02 = RDP_NEG_RSP (server picked one protocol — selectedProtocols at bytes 15..18) - // 0x03 = RDP_NEG_FAILURE - if resp[11] != 0x02 { - return NLAUnknown, nil - } - selected := binary.LittleEndian.Uint32(resp[15:19]) - switch { - case selected&0x08 != 0: - return NLAHybridEx, nil - case selected&0x02 != 0: - return NLARequired, nil - case selected&0x01 != 0 || selected == 0: - return NLANotEnforced, nil - } - return NLAUnknown, nil -} -``` - -- [ ] **Step 2: Commit grdp side** - -```bash -(cd ../grdp && git add client/nla_check.go && git -c commit.gpgsign=false commit -m "feat(client): add FingerprintNLA for TCP-only NLA detection") -``` - -- [ ] **Step 3: Update brutespray's grdp dependency** - -```bash -# If grdp is referenced by go.mod replace pointing to ../grdp, no go get needed. -# Otherwise bump: -go get github.com/x90skysn3k/grdp@latest -go mod tidy -go build ./... -``` - -Expected: `go build` succeeds. - -- [ ] **Step 4: Write the failing test** - -Create `brute/rdp_nla_test.go`: - -```go -package brute - -import "testing" - -func TestNLAFindingFromStatus(t *testing.T) { - cases := []struct { - status string - wantSev string - wantCode string - }{ - {"required", "INFO", "rdp-nla-required"}, - {"not-enforced", "WARN", "rdp-nla-missing"}, - {"hybrid-ex", "INFO", "rdp-nla-hybridex"}, - {"unknown", "", ""}, - } - for _, c := range cases { - f := nlaFinding(c.status) - if c.wantCode == "" { - if f != nil { - t.Fatalf("status %q: expected nil finding, got %+v", c.status, f) - } - continue - } - if f == nil || f.Severity != c.wantSev || f.Code != c.wantCode { - t.Fatalf("status %q: got %+v want sev=%s code=%s", c.status, f, c.wantSev, c.wantCode) - } - } -} -``` - -- [ ] **Step 5: Implement `nlaFinding` and `ScanRDPRecon` in `brute/rdp.go`** - -Append to `brute/rdp.go`: - -```go -import ( - // ... existing imports ... - "github.com/x90skysn3k/grdp/client" -) - -func nlaFinding(status string) *Finding { - switch status { - case "required": - return &Finding{Severity: "INFO", Code: "rdp-nla-required", - Message: "NLA (CredSSP) enforced"} - case "not-enforced": - return &Finding{Severity: "WARN", Code: "rdp-nla-missing", - Message: "NLA not enforced — server accepts standard RDP without pre-auth"} - case "hybrid-ex": - return &Finding{Severity: "INFO", Code: "rdp-nla-hybridex", - Message: "HybridEx (NLA + CredSSP early-user auth) enforced"} - } - return nil -} - -// ScanRDPRecon runs pre-auth RDP recon (NLA fingerprint, sticky-keys probe) -// against a single target. Returns a slice of findings to emit. Called once -// per host by the dispatcher before any brute attempts. -func ScanRDPRecon(host string, port int, timeout time.Duration) []*Finding { - target := fmt.Sprintf("%s:%d", host, port) - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - status, err := client.FingerprintNLA(ctx, target, timeout) - if err != nil { - return nil - } - var out []*Finding - switch status { - case client.NLARequired: - out = append(out, nlaFinding("required")) - case client.NLANotEnforced: - out = append(out, nlaFinding("not-enforced")) - case client.NLAHybridEx: - out = append(out, nlaFinding("hybrid-ex")) - } - // Sticky-keys probe slots in here in Task A6. - return out -} -``` - -- [ ] **Step 6: Run tests** - -Run: `go test ./brute/ -run TestNLA -v` -Expected: PASS. - -- [ ] **Step 7: Commit** - -```bash -git add brute/rdp.go brute/rdp_nla_test.go go.mod go.sum -git commit -m "feat(rdp): pre-auth NLA fingerprint scan" -``` - ---- - -## Task A6: RDP sticky-keys probe in grdp + brutespray integration - -**Files:** -- Modify (sibling repo): `../grdp/client/logon_screen.go` (new) -- Modify: `brute/rdp.go` (extend `ScanRDPRecon`) -- Test: `brute/rdp_stickykeys_test.go` (new) - -- [ ] **Step 1: Add `CaptureLogonScreen` to grdp** - -In `../grdp/client/logon_screen.go`, implement: - -```go -package client - -import ( - "bytes" - "context" - "image" - "image/png" - "time" -) - -// LogonTrigger identifies which pre-auth keystroke to send. -type LogonTrigger int - -const ( - TriggerShift5x LogonTrigger = iota // 5x Left-Shift → sticky-keys - TriggerWinU // Win+U → utilman.exe -) - -// CaptureLogonScreen connects to an RDP target, sends the requested -// pre-auth trigger keystroke, and returns a framebuffer snapshot from -// before and after the trigger. Both images are encoded as in-memory -// PNGs to keep the API small. -// -// Returns ErrNLARequired if the server enforces NLA (no framebuffer is -// available pre-authentication on those hosts). -func (c *RdpClient) CaptureLogonScreen(ctx context.Context, target string, - trigger LogonTrigger, timeout time.Duration) (before, after []byte, err error) { - // Reuse the existing low-level RDP connect path (the one LoginAuthOnly - // uses) but stop short of credential delegation: stay at the GINA - // (logon UI) layer. - if err := c.connectToLogonScreen(ctx, target, timeout); err != nil { - return nil, nil, err - } - defer c.Close() - - beforeImg, err := c.snapshotFramebuffer() - if err != nil { - return nil, nil, err - } - if err := c.sendLogonTrigger(trigger); err != nil { - return nil, nil, err - } - // Wait a beat for the server-rendered window to repaint. - time.Sleep(750 * time.Millisecond) - afterImg, err := c.snapshotFramebuffer() - if err != nil { - return nil, nil, err - } - return pngEncode(beforeImg), pngEncode(afterImg), nil -} - -func pngEncode(img *image.RGBA) []byte { - var buf bytes.Buffer - _ = png.Encode(&buf, img) - return buf.Bytes() -} - -// Implementations below wire up against grdp's existing PDU/T.128 layer. -// connectToLogonScreen, snapshotFramebuffer, and sendLogonTrigger are added -// as methods on *RdpClient in the same package — see grdp/protocol/pdu/ for -// the existing primitives to reuse. -``` - -`connectToLogonScreen`, `snapshotFramebuffer`, and `sendLogonTrigger` build on the same PDU code paths that `LoginAuthOnly` already exercises. They go in the same file. Skeletons: - -```go -func (c *RdpClient) connectToLogonScreen(ctx context.Context, target string, timeout time.Duration) error { - // Mirror the connect/X.224/MCS/Security/connect-initial sequence used by - // LoginAuthOnly but skip the CredSSP/NLA upgrade — request PROTOCOL_RDP only. - // Return after the Demand Active PDU arrives (= server reached the logon screen). - return fmt.Errorf("TODO connect-to-logon-screen — implement against grdp/protocol/pdu") -} - -func (c *RdpClient) snapshotFramebuffer() (*image.RGBA, error) { - // Read bitmap update PDUs into a tile buffer until the framebuffer is - // quiescent (no updates for ~250ms); then blit the tiles into a single RGBA. - return nil, fmt.Errorf("TODO snapshot — implement against grdp/emission") -} - -func (c *RdpClient) sendLogonTrigger(t LogonTrigger) error { - switch t { - case TriggerShift5x: - for i := 0; i < 5; i++ { - // Scancode 0x2A = Left-Shift. PDU input event: keyboard down then up. - if err := c.sendKeyScancode(0x2A); err != nil { - return err - } - } - return nil - case TriggerWinU: - if err := c.sendKeyScancodeChord(0x5B, 0x16); err != nil { // L-Win + 'U' - return err - } - return nil - } - return fmt.Errorf("unknown trigger") -} -``` - -The `TODO`s aren't placeholders for this *plan* — they're explicit grdp work items that must be filled in against grdp's existing PDU primitives. Reference `grdp/protocol/pdu/input.go` for keyboard PDU shape and `grdp/emission/` for framebuffer update plumbing. The implementing engineer fills them in by reading the surrounding grdp code; this plan does not duplicate grdp's internals. - -- [ ] **Step 2: Implement the TODOs in grdp** - -Open `../grdp/protocol/pdu/`. Use the existing input-event PDU constructors that `LoginAuthOnly` flows through to send keyboard scancodes. Use the bitmap-update PDU handlers to accumulate framebuffer tiles into an `*image.RGBA`. Reuse `connect-initial` plumbing from `LoginAuthOnly` for the connect path, branching off before CredSSP. - -- [ ] **Step 3: Commit grdp side** - -```bash -(cd ../grdp && git add client/logon_screen.go protocol/pdu/ && \ - git -c commit.gpgsign=false commit -m "feat(client): CaptureLogonScreen for pre-auth screen capture + key trigger") -``` - -- [ ] **Step 4: Bump grdp dependency in brutespray** - -```bash -go get github.com/x90skysn3k/grdp@latest && go mod tidy && go build ./... -``` - -- [ ] **Step 5: Write the failing test** - -Create `brute/rdp_stickykeys_test.go`: - -```go -package brute - -import ( - "testing" -) - -func TestStickyKeysVerdictFromImages(t *testing.T) { - cases := []struct { - name string - beforeIsCmdLike bool - afterIsCmdLike bool - differ bool - wantSev, wantCode string - }{ - {"identical-no-change", false, false, false, "", ""}, - {"change-but-not-cmd", false, false, true, "INFO", "rdp-stickykeys-inconclusive"}, - {"change-to-cmd", false, true, true, "CRITICAL", "rdp-stickykeys"}, - } - for _, c := range cases { - got := stickyKeysVerdict(c.beforeIsCmdLike, c.afterIsCmdLike, c.differ) - if c.wantCode == "" { - if got != nil { - t.Fatalf("%s: want nil, got %+v", c.name, got) - } - continue - } - if got == nil || got.Severity != c.wantSev || got.Code != c.wantCode { - t.Fatalf("%s: got %+v want sev=%s code=%s", c.name, got, c.wantSev, c.wantCode) - } - } -} -``` - -- [ ] **Step 6: Implement `stickyKeysVerdict` and probe wiring in `brute/rdp.go`** - -Append: - -```go -import ( - // ... existing ... - "bytes" - "image" - _ "image/png" -) - -func stickyKeysVerdict(beforeCmd, afterCmd, differ bool) *Finding { - if !differ { - return nil - } - if afterCmd && !beforeCmd { - return &Finding{ - Severity: "CRITICAL", - Code: "rdp-stickykeys", - Message: "sticky-keys backdoor detected (cmd.exe shell at logon screen)", - } - } - return &Finding{ - Severity: "INFO", - Code: "rdp-stickykeys-inconclusive", - Message: "logon screen reacted to sticky-keys trigger but no console signature detected; manual verification recommended", - } -} - -// looksLikeCmdConsole returns true when the framebuffer region top-left of -// the image is consistent with a cmd.exe console window (predominantly black -// with monospaced white text in the top portion). Conservative — false -// positives are worse than missed detections here. -func looksLikeCmdConsole(pngBytes []byte) bool { - img, _, err := image.Decode(bytes.NewReader(pngBytes)) - if err != nil { - return false - } - rgba, ok := img.(*image.RGBA) - if !ok { - // Decode for non-RGBA paths; for the heuristic, draw onto an RGBA. - b := img.Bounds() - rgba = image.NewRGBA(b) - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - rgba.Set(x, y, img.At(x, y)) - } - } - } - // Sample the top-left 400x200 region. Count pixels: black (R<32 && G<32 && B<32) - // and white-ish (R>200 && G>200 && B>200). Console signature: >65% black, - // 2-15% white-ish. - b := rgba.Bounds() - maxX := b.Min.X + 400 - if maxX > b.Max.X { - maxX = b.Max.X - } - maxY := b.Min.Y + 200 - if maxY > b.Max.Y { - maxY = b.Max.Y - } - var total, black, white int - for y := b.Min.Y; y < maxY; y++ { - for x := b.Min.X; x < maxX; x++ { - r, g, bl, _ := rgba.At(x, y).RGBA() - r >>= 8 - g >>= 8 - bl >>= 8 - total++ - switch { - case r < 32 && g < 32 && bl < 32: - black++ - case r > 200 && g > 200 && bl > 200: - white++ - } - } - } - if total == 0 { - return false - } - blackPct := float64(black) / float64(total) - whitePct := float64(white) / float64(total) - return blackPct > 0.65 && whitePct > 0.02 && whitePct < 0.15 -} - -func framebuffersDiffer(a, b []byte) bool { - if len(a) == 0 || len(b) == 0 { - return false - } - if len(a) != len(b) { - return true - } - // Hash compare — different PNG bytes means different content (PNG encoder - // is deterministic for identical RGBA in stdlib). - return !bytes.Equal(a, b) -} -``` - -Then in `ScanRDPRecon` from Task A5, after the NLA branch, before the return: - -```go -// Sticky-keys probe runs only when NLA is not enforced (no point trying -// against an NLA host — no framebuffer pre-auth). -if status == client.NLANotEnforced { - c := &client.RdpClient{} - before, after, err := c.CaptureLogonScreen(ctx, target, client.TriggerShift5x, timeout) - if err == nil { - if f := stickyKeysVerdict(looksLikeCmdConsole(before), looksLikeCmdConsole(after), - framebuffersDiffer(before, after)); f != nil { - out = append(out, f) - } - } -} -``` - -- [ ] **Step 7: Run tests** - -Run: `go test ./brute/ -run TestStickyKeys -v` -Expected: PASS. - -- [ ] **Step 8: Commit** - -```bash -git add brute/rdp.go brute/rdp_stickykeys_test.go go.mod go.sum -git commit -m "feat(rdp): sticky-keys backdoor pre-auth probe with framebuffer heuristic" -``` - ---- - -## Task A7: Dispatcher invokes RDP recon + `--no-rdp-scan` flag - -**Files:** -- Modify: `brutespray/config.go` -- Modify: `brutespray/dispatch.go` - -- [ ] **Step 1: Add `--no-rdp-scan` flag** - -In `brutespray/config.go`: - -```go -flag.BoolVar(&Cfg.NoRDPScan, "no-rdp-scan", false, "Skip pre-auth RDP recon (NLA fingerprint, sticky-keys probe)") -``` - -Add to `Config` struct: - -```go -NoRDPScan bool -``` - -- [ ] **Step 2: Call `brute.ScanRDPRecon` from the dispatcher** - -In `brutespray/dispatch.go`'s `ProcessHost`, at the top of the host-setup block (mirroring where bad-keys is invoked for SSH): - -```go -if host.Service == "rdp" && !Cfg.NoRDPScan { - findings := brute.ScanRDPRecon(host.Host, host.Port, Cfg.Timeout) - for _, f := range findings { - emitFinding(host, f) // route through existing output channel — text + JSONL + TUI - } -} -``` - -`emitFinding` is a small helper next to the existing per-host result emit path. If a similar helper does not already exist, add: - -```go -func emitFinding(host modules.Host, f *brute.Finding) { - modules.WriteFindingLine(host, f) -} -``` - -- [ ] **Step 3: Quick integration smoke** - -Run: `go build ./... && ./brutespray -H 127.0.0.1 -s rdp -u test -p test --no-rdp-scan 2>&1 | head` -Expected: no panic; the `--no-rdp-scan` path runs cleanly (connection error is fine). - -- [ ] **Step 4: Commit** - -```bash -git add brutespray/config.go brutespray/dispatch.go -git commit -m "feat(rdp): wire pre-auth RDP recon into dispatcher with --no-rdp-scan opt-out" -``` - ---- - -## Task A8: Render `Finding` and `KeyMatch` in output layer (text + JSONL) - -**Files:** -- Modify: `modules/output.go` -- Test: `modules/output_finding_test.go` (new) - -- [ ] **Step 1: Write the failing test** - -Create `modules/output_finding_test.go`: - -```go -package modules - -import ( - "bytes" - "encoding/json" - "strings" - "testing" - - "github.com/x90skysn3k/brutespray/v2/brute" -) - -func TestWriteFindingLineText(t *testing.T) { - var buf bytes.Buffer - WriteFindingTo(&buf, Host{Service: "rdp", Host: "10.0.0.5", Port: 3389}, - &brute.Finding{Severity: "WARN", Code: "rdp-nla-missing", Message: "NLA not enforced"}) - got := buf.String() - for _, want := range []string{"WARN", "rdp", "10.0.0.5:3389", "NLA not enforced"} { - if !strings.Contains(got, want) { - t.Fatalf("output missing %q: %s", want, got) - } - } -} - -func TestWriteFindingLineJSON(t *testing.T) { - var buf bytes.Buffer - WriteFindingJSONTo(&buf, Host{Service: "rdp", Host: "10.0.0.5", Port: 3389}, - &brute.Finding{Severity: "CRITICAL", Code: "rdp-stickykeys", Message: "backdoor detected", CVE: ""}) - var got struct { - Type string `json:"type"` - Severity string `json:"severity"` - Code string `json:"code"` - Target string `json:"target"` - } - if err := json.Unmarshal(buf.Bytes(), &got); err != nil { - t.Fatalf("invalid JSON: %v\n%s", err, buf.String()) - } - if got.Type != "finding" || got.Severity != "CRITICAL" || got.Code != "rdp-stickykeys" { - t.Fatalf("wrong fields: %+v", got) - } - if got.Target != "10.0.0.5:3389" { - t.Fatalf("target = %q", got.Target) - } -} -``` - -- [ ] **Step 2: Implement renderers in `modules/output.go`** - -Append: - -```go -import ( - // ... existing ... - "encoding/json" - "fmt" - "io" - - "github.com/x90skysn3k/brutespray/v2/brute" -) - -// WriteFindingTo writes a colored text-form finding line. -func WriteFindingTo(w io.Writer, h Host, f *brute.Finding) { - cve := "" - if f.CVE != "" { - cve = " (" + f.CVE + ")" - } - fmt.Fprintf(w, "[%s] %s %s:%d %s%s\n", - f.Severity, h.Service, h.Host, h.Port, f.Message, cve) -} - -// WriteFindingJSONTo writes a JSONL finding line. -func WriteFindingJSONTo(w io.Writer, h Host, f *brute.Finding) { - rec := map[string]any{ - "type": "finding", - "severity": f.Severity, - "code": f.Code, - "service": h.Service, - "target": fmt.Sprintf("%s:%d", h.Host, h.Port), - "message": f.Message, - } - if f.CVE != "" { - rec["cve"] = f.CVE - } - _ = json.NewEncoder(w).Encode(rec) -} - -// WriteFindingLine dispatches to the configured output sink (text or JSONL). -// Called from the dispatcher when a Finding surfaces. -func WriteFindingLine(h Host, f *brute.Finding) { - if jsonOutput { - WriteFindingJSONTo(jsonSink, h, f) - return - } - WriteFindingTo(stdoutSink, h, f) -} -``` - -(`jsonOutput`, `jsonSink`, `stdoutSink` are the existing sinks the output layer already uses for per-attempt results. Reuse them; do not introduce new globals. If the existing names differ, rename accordingly.) - -- [ ] **Step 3: Render `KeyMatch` on successful SSH bad-key auth** - -In `modules/output.go`, locate the success-printing path (whatever function emits `[+] SUCCESS ...`). Add a branch: - -```go -if res.KeyMatch != nil { - cve := "" - if res.KeyMatch.CVE != "" { - cve = " (" + res.KeyMatch.CVE + ")" - } - fmt.Fprintf(stdoutSink, "[+] BADKEY %s %s@%s:%d %s%s\n", - h.Service, user, h.Host, h.Port, res.KeyMatch.Vendor, cve) - // (JSON path: existing success-emit already serializes res; extend the - // json record to include "key_match" when non-nil.) - return -} -``` - -For JSON output, extend the existing success-record struct to include `KeyMatch *brute.KeyMatch` with json tag `key_match,omitempty`. - -- [ ] **Step 4: Run tests** - -Run: `go test ./modules/ -run TestWriteFinding -v` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add modules/output.go modules/output_finding_test.go -git commit -m "feat(output): render Finding (text+JSONL) and KeyMatch on SSH success" -``` - ---- - -## Task A9: TUI Findings tab - -**Files:** -- Modify: `tui/model.go` -- Create: `tui/view_findings.go` -- Modify: existing tab-list registration - -- [ ] **Step 1: Read the existing tab pattern** - -Read `tui/view_all.go`, `tui/view_errors.go`, `tui/view_success.go` to confirm the existing tab structure. The new view follows the same pattern. - -- [ ] **Step 2: Add Findings to the TUI Model** - -In `tui/model.go`, locate the tab list (probably a `[]Tab` or similar `tabs` slice). Add: - -```go -{Name: "Findings", Key: "f"} -``` - -Add a `findings []FindingEntry` field to the model: - -```go -type FindingEntry struct { - Severity string - Code string - Service string - Target string - Message string - CVE string -} -``` - -Add an `AddFinding` method: - -```go -func (m *Model) AddFinding(e FindingEntry) { - m.findings = append(m.findings, e) -} -``` - -- [ ] **Step 3: Implement `tui/view_findings.go`** - -```go -package tui - -import ( - "fmt" - "strings" -) - -func (m *Model) viewFindings() string { - if len(m.findings) == 0 { - return "No findings yet.\n" - } - var b strings.Builder - for _, f := range m.findings { - cve := "" - if f.CVE != "" { - cve = " (" + f.CVE + ")" - } - b.WriteString(fmt.Sprintf("[%s] %s %s %s%s\n", - f.Severity, f.Service, f.Target, f.Message, cve)) - } - return b.String() -} -``` - -Wire `viewFindings` into the model's view-switch (mirror how `view_errors` is dispatched). - -- [ ] **Step 4: Push findings into the TUI from the dispatcher** - -In `brutespray/dispatch.go`'s `emitFinding` (added in Task A7), also forward to the TUI sink when active: - -```go -if tuiActive { - tuiModel.AddFinding(tui.FindingEntry{ - Severity: f.Severity, - Code: f.Code, - Service: host.Service, - Target: fmt.Sprintf("%s:%d", host.Host, host.Port), - Message: f.Message, - CVE: f.CVE, - }) -} -``` - -- [ ] **Step 5: Smoke test** - -Run: `go build ./... && ./brutespray -H 127.0.0.1 -s rdp -u test -p test 2>&1 | head` -Expected: no TUI panic; press `f` while running an interactive session — tab renders. - -- [ ] **Step 6: Commit** - -```bash -git add tui/model.go tui/view_findings.go brutespray/dispatch.go -git commit -m "feat(tui): add Findings tab populated from pre-auth recon" -``` - ---- - -# Phase B — Stdin pipeline + masscan JSON - -## Task B1: Masscan JSON parser - -**Files:** -- Create: `modules/parse_masscan.go` -- Create: `modules/parse_masscan_test.go` - -- [ ] **Step 1: Sample masscan output** - -Masscan `-oJ` emits a JSON *array* with elements: - -```json -{"ip": "10.0.0.5", "timestamp": "1700000000", - "ports": [{"port": 22, "proto": "tcp", "status": "open", "reason": "syn-ack", "ttl": 64}]} -``` - -- [ ] **Step 2: Write the failing test** - -Create `modules/parse_masscan_test.go`: - -```go -package modules - -import ( - "strconv" - "strings" - "testing" -) - -const masscanSample = `[ -{"ip":"10.0.0.5","ports":[{"port":22,"proto":"tcp","status":"open"}]}, -{"ip":"10.0.0.6","ports":[{"port":3306,"proto":"tcp","status":"open"},{"port":80,"proto":"tcp","status":"closed"}]}, -{"ip":"10.0.0.7","ports":[{"port":3389,"proto":"tcp","status":"open"}]} -]` - -func TestParseMasscanJSON(t *testing.T) { - hosts, err := ParseMasscanJSON(strings.NewReader(masscanSample)) - if err != nil { - t.Fatalf("ParseMasscanJSON: %v", err) - } - if len(hosts) != 3 { - t.Fatalf("want 3 hosts (closed port filtered), got %d", len(hosts)) - } - want := map[string]string{ - "10.0.0.5:22": "ssh", - "10.0.0.6:3306": "mysql", - "10.0.0.7:3389": "rdp", - } - for _, h := range hosts { - key := h.Host + ":" + strconv.Itoa(h.Port) - if got, ok := want[key]; !ok || got != h.Service { - t.Fatalf("unexpected host: %+v", h) - } - } -} -``` - -- [ ] **Step 3: Implement `ParseMasscanJSON`** - -```go -package modules - -import ( - "encoding/json" - "fmt" - "io" -) - -type masscanPort struct { - Port int `json:"port"` - Proto string `json:"proto"` - Status string `json:"status"` -} - -type masscanHost struct { - IP string `json:"ip"` - Ports []masscanPort `json:"ports"` -} - -// ParseMasscanJSON reads masscan -oJ output and returns one Host per -// open port. Service is inferred from port via defaultServiceForPort -// (existing helper in parse.go); ports with no mapping are dropped. -func ParseMasscanJSON(r io.Reader) ([]Host, error) { - var rows []masscanHost - if err := json.NewDecoder(r).Decode(&rows); err != nil { - return nil, fmt.Errorf("decode masscan json: %w", err) - } - var out []Host - for _, row := range rows { - for _, p := range row.Ports { - if p.Status != "open" { - continue - } - svc := defaultServiceForPort(p.Port) - if svc == "" { - continue - } - out = append(out, Host{Service: svc, Host: row.IP, Port: p.Port}) - } - } - return out, nil -} -``` - -If `defaultServiceForPort` does not yet exist in `parse.go`, add it there mapping the common ports (22→ssh, 21→ftp, 23→telnet, 25→smtp, 80→http, 110→pop3, 143→imap, 389→ldap, 443→https, 445→smbnt, 1433→mssql, 1521→oracle, 3306→mysql, 3389→rdp, 5432→postgres, 5900→vnc, 5984→couchdb, 6379→redis, 7687→neo4j, 8086→influxdb, 9042→cassandra, 9200→elasticsearch, 27017→mongodb). - -- [ ] **Step 4: Run tests** - -Run: `go test ./modules/ -run TestParseMasscan -v` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add modules/parse_masscan.go modules/parse_masscan_test.go modules/parse.go -git commit -m "feat(parse): masscan -oJ JSON ingestion with port→service mapping" -``` - ---- - -## Task B2: Stdin auto-detect - -**Files:** -- Create: `modules/parse_stream.go` -- Create: `modules/parse_stream_test.go` - -- [ ] **Step 1: Write the failing test** - -Create `modules/parse_stream_test.go`: - -```go -package modules - -import ( - "strings" - "testing" -) - -func TestDetectStreamFormat(t *testing.T) { - cases := []struct { - name string - in string - want string - }{ - {"bare-host-port", "10.0.0.5:22\n10.0.0.6:3389\n", "naabu"}, - {"nerva-uri", "ssh://10.0.0.5:22\nmysql://10.0.0.6:3306\n", "nerva-uri"}, - {"nerva-json", `{"ip":"10.0.0.5","port":22,"protocol":"ssh"}`, "nerva-json"}, - {"masscan-json", `[{"ip":"10.0.0.5","ports":[{"port":22,"proto":"tcp","status":"open"}]}]`, "masscan-json"}, - {"fingerprintx-json", `{"host":"10.0.0.5","ip":"10.0.0.5","port":22,"service":"ssh","transport":"tcp"}`, "fingerprintx-json"}, - } - for _, c := range cases { - t.Run(c.name, func(t *testing.T) { - got, err := DetectStreamFormat(strings.NewReader(c.in)) - if err != nil { - t.Fatalf("DetectStreamFormat: %v", err) - } - if got != c.want { - t.Fatalf("got %q, want %q", got, c.want) - } - }) - } -} - -func TestParseStreamBareHostPort(t *testing.T) { - hosts, err := ParseStream(strings.NewReader("10.0.0.5:22\n10.0.0.6:3389\n")) - if err != nil { - t.Fatalf("ParseStream: %v", err) - } - if len(hosts) != 2 { - t.Fatalf("want 2, got %d", len(hosts)) - } - if hosts[0].Service != "ssh" || hosts[1].Service != "rdp" { - t.Fatalf("port→service mapping failed: %+v", hosts) - } -} -``` - -- [ ] **Step 2: Implement `parse_stream.go`** - -```go -package modules - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io" - "regexp" - "strconv" - "strings" -) - -var ( - nervaURIRE = regexp.MustCompile(`^[a-z][a-z0-9+-]*://[^:/]+:\d+`) - hostPortRE = regexp.MustCompile(`^[^\s:]+:\d+$`) -) - -// DetectStreamFormat peeks at the first non-blank line of the stream and -// returns one of: naabu, nerva-uri, nerva-json, masscan-json, fingerprintx-json. -// Does NOT consume the stream — caller passes a buffered reader. -func DetectStreamFormat(r io.Reader) (string, error) { - br := bufio.NewReader(r) - // Peek up to 4KB - peek, _ := br.Peek(4096) - // Find first non-blank line - var line []byte - for _, raw := range bytes.Split(peek, []byte("\n")) { - t := bytes.TrimSpace(raw) - if len(t) > 0 { - line = t - break - } - } - if len(line) == 0 { - return "", fmt.Errorf("empty stream") - } - s := string(line) - switch { - case s[0] == '[': - return "masscan-json", nil - case s[0] == '{': - // Classify by required keys - var probe map[string]json.RawMessage - if err := json.Unmarshal(line, &probe); err != nil { - return "", fmt.Errorf("invalid JSON: %w", err) - } - _, hasService := probe["service"] - _, hasProtocol := probe["protocol"] - _, hasPort := probe["port"] - switch { - case hasService && hasPort: - return "fingerprintx-json", nil - case hasProtocol && hasPort: - return "nerva-json", nil - } - return "", fmt.Errorf("unrecognized JSON shape") - case nervaURIRE.MatchString(s): - return "nerva-uri", nil - case hostPortRE.MatchString(s): - return "naabu", nil - } - return "", fmt.Errorf("unrecognized line format: %s", s) -} - -// ParseStream auto-detects and parses a target stream into Hosts. -func ParseStream(r io.Reader) ([]Host, error) { - buf, err := io.ReadAll(r) - if err != nil { - return nil, fmt.Errorf("read stream: %w", err) - } - format, err := DetectStreamFormat(bytes.NewReader(buf)) - if err != nil { - return nil, err - } - switch format { - case "naabu": - return parseNaabuLines(buf) - case "nerva-uri": - return parseNervaURI(buf) - case "nerva-json": - return parseNervaJSON(buf) - case "masscan-json": - return ParseMasscanJSON(bytes.NewReader(buf)) - case "fingerprintx-json": - return parseFingerprintXJSON(buf) - } - return nil, fmt.Errorf("unsupported format: %s", format) -} - -func parseNaabuLines(buf []byte) ([]Host, error) { - var out []Host - for _, raw := range bytes.Split(buf, []byte("\n")) { - s := strings.TrimSpace(string(raw)) - if s == "" { - continue - } - host, port, err := splitHostPort(s) - if err != nil { - continue - } - svc := defaultServiceForPort(port) - if svc == "" { - continue - } - out = append(out, Host{Service: svc, Host: host, Port: port}) - } - return out, nil -} - -func parseNervaURI(buf []byte) ([]Host, error) { - var out []Host - for _, raw := range bytes.Split(buf, []byte("\n")) { - s := strings.TrimSpace(string(raw)) - if s == "" { - continue - } - // Strip parenthetical resolution suffix like "ssh://github.com:22 (140.82.121.4)" - if idx := strings.Index(s, " "); idx > 0 { - s = s[:idx] - } - scheme := s[:strings.Index(s, "://")] - rest := s[strings.Index(s, "://")+3:] - host, port, err := splitHostPort(rest) - if err != nil { - continue - } - out = append(out, Host{Service: scheme, Host: host, Port: port}) - } - return out, nil -} - -type nervaRow struct { - IP string `json:"ip"` - Port int `json:"port"` - Protocol string `json:"protocol"` -} - -func parseNervaJSON(buf []byte) ([]Host, error) { - var out []Host - dec := json.NewDecoder(bytes.NewReader(buf)) - for dec.More() { - var row nervaRow - if err := dec.Decode(&row); err != nil { - return nil, fmt.Errorf("decode nerva-json: %w", err) - } - out = append(out, Host{Service: row.Protocol, Host: row.IP, Port: row.Port}) - } - return out, nil -} - -type fpxRow struct { - Host string `json:"host"` - IP string `json:"ip"` - Port int `json:"port"` - Service string `json:"service"` -} - -func parseFingerprintXJSON(buf []byte) ([]Host, error) { - var out []Host - dec := json.NewDecoder(bytes.NewReader(buf)) - for dec.More() { - var row fpxRow - if err := dec.Decode(&row); err != nil { - return nil, fmt.Errorf("decode fingerprintx-json: %w", err) - } - h := row.Host - if h == "" { - h = row.IP - } - out = append(out, Host{Service: row.Service, Host: h, Port: row.Port}) - } - return out, nil -} - -func splitHostPort(s string) (string, int, error) { - idx := strings.LastIndex(s, ":") - if idx < 0 { - return "", 0, fmt.Errorf("no port: %s", s) - } - port, err := strconv.Atoi(s[idx+1:]) - if err != nil { - return "", 0, err - } - return s[:idx], port, nil -} -``` - -- [ ] **Step 3: Run tests** - -Run: `go test ./modules/ -run TestDetectStream -v && go test ./modules/ -run TestParseStream -v` -Expected: all PASS. - -- [ ] **Step 4: Commit** - -```bash -git add modules/parse_stream.go modules/parse_stream_test.go -git commit -m "feat(parse): stdin stream auto-detect for naabu/nerva/fingerprintx/masscan" -``` - ---- - -## Task B3: Wire stdin into `brutespray.Execute` - -**Files:** -- Modify: `brutespray/brutespray.go` (Execute) - -- [ ] **Step 1: Detect piped stdin at startup** - -Near the top of `Execute()`, after CLI parsing, before file ingestion: - -```go -// If -f was not provided AND stdin is a pipe (not a TTY), read targets from stdin. -if Cfg.File == "" && !term.IsTerminal(int(os.Stdin.Fd())) { - hosts, err := modules.ParseStream(os.Stdin) - if err != nil { - fmt.Fprintf(os.Stderr, "stdin parse: %v\n", err) - os.Exit(2) - } - Cfg.Hosts = append(Cfg.Hosts, hosts...) -} -``` - -Add imports if missing: - -```go -import ( - "os" - "golang.org/x/term" -) -``` - -If `golang.org/x/term` is not already in `go.mod`: - -```bash -go get golang.org/x/term && go mod tidy -``` - -- [ ] **Step 2: Smoke test the pipeline** - -```bash -go build -o brutespray . -echo "127.0.0.1:22" | ./brutespray -u root -p test 2>&1 | head -``` - -Expected: the host is enqueued from stdin (you'll see an SSH attempt log line or a connection-refused error — both confirm parsing worked). - -- [ ] **Step 3: Commit** - -```bash -git add brutespray/brutespray.go go.mod go.sum -git commit -m "feat(cli): auto-read targets from piped stdin with format detection" -``` - ---- - -# Phase C — New DB modules + SNMP tiering + inline creds - -## Task C1: Neo4j module - -**Files:** -- Create: `brute/neo4j.go` -- Create: `brute/neo4j_test.go` - -- [ ] **Step 1: Add neo4j driver dep** - -```bash -go get github.com/neo4j/neo4j-go-driver/v5/neo4j && go mod tidy -``` - -- [ ] **Step 2: Write the failing test** - -Create `brute/neo4j_test.go`: - -```go -package brute - -import ( - "context" - "fmt" - "os" - "testing" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func TestBruteNeo4jNoServer(t *testing.T) { - cm := modules.NewConnectionManager() - r := BruteNeo4j("127.0.0.1", 1, "neo4j", "neo4j", 1*time.Second, cm, nil) - if r.ConnectionSuccess { - t.Fatalf("expected ConnectionSuccess=false against closed port") - } -} - -// Docker-backed integration test (gated, parallels brute/redis_test.go shape) -func TestBruteNeo4jDocker(t *testing.T) { - if os.Getenv("BRUTESPRAY_DOCKER_TESTS") == "" { - t.Skip("set BRUTESPRAY_DOCKER_TESTS=1 to run") - } - // Container started by the integration harness; assume neo4j:5 on 7687 - cm := modules.NewConnectionManager() - r := BruteNeo4j("127.0.0.1", 7687, "neo4j", "testtest", 5*time.Second, cm, nil) - if !r.AuthSuccess { - t.Fatalf("expected auth success, got %+v", r) - } - _ = context.Background() - _ = fmt.Sprintf -} -``` - -- [ ] **Step 3: Implement `brute/neo4j.go`** - -```go -package brute - -import ( - "context" - "errors" - "fmt" - "strings" - "time" - - "github.com/neo4j/neo4j-go-driver/v5/neo4j" - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func BruteNeo4j(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { - return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { - uri := fmt.Sprintf("bolt://%s:%d", host, port) - // Note: neo4j-go-driver v5 does not expose a custom dialer in the - // public Config. We accept that proxy/iface routing does not apply - // to Neo4j attempts in this initial implementation; document it. - driver, err := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(user, password, ""), - func(c *neo4j.Config) { - c.SocketConnectTimeout = timeout - }) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - defer driver.Close(ctx) - err = driver.VerifyConnectivity(ctx) - if err != nil { - msg := err.Error() - if strings.Contains(msg, "AuthenticationRateLimit") || - strings.Contains(msg, "Unauthorized") || - strings.Contains(msg, "credentials") { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} - } - var ute *neo4j.UsageError - if errors.As(err, &ute) { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} - }) -} - -func init() { Register("neo4j", BruteNeo4j) } -``` - -- [ ] **Step 4: Run tests** - -Run: `go test ./brute/ -run TestBruteNeo4jNoServer -v` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add brute/neo4j.go brute/neo4j_test.go go.mod go.sum -git commit -m "feat(brute): neo4j Bolt v5 module" -``` - ---- - -## Task C2: Cassandra module - -**Files:** -- Create: `brute/cassandra.go` -- Create: `brute/cassandra_test.go` - -- [ ] **Step 1: Add gocql dep** - -```bash -go get github.com/gocql/gocql && go mod tidy -``` - -- [ ] **Step 2: Write the failing test** - -Create `brute/cassandra_test.go`: - -```go -package brute - -import ( - "testing" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func TestBruteCassandraNoServer(t *testing.T) { - cm := modules.NewConnectionManager() - r := BruteCassandra("127.0.0.1", 1, "cassandra", "cassandra", 1*time.Second, cm, nil) - if r.ConnectionSuccess { - t.Fatalf("expected ConnectionSuccess=false against closed port") - } -} -``` - -- [ ] **Step 3: Implement `brute/cassandra.go`** - -```go -package brute - -import ( - "context" - "fmt" - "strings" - "time" - - "github.com/gocql/gocql" - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func BruteCassandra(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { - return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { - cluster := gocql.NewCluster(fmt.Sprintf("%s:%d", host, port)) - cluster.ProtoVersion = 4 - cluster.ConnectTimeout = timeout - cluster.Timeout = timeout - cluster.Authenticator = gocql.PasswordAuthenticator{ - Username: user, - Password: password, - } - cluster.DisableInitialHostLookup = true - sess, err := cluster.CreateSession() - if err != nil { - msg := err.Error() - if strings.Contains(msg, "Authentication") || - strings.Contains(msg, "Bad credentials") || - strings.Contains(msg, "Unauthorized") { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, Error: err} - } - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - defer sess.Close() - return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} - }) -} - -func init() { Register("cassandra", BruteCassandra) } -``` - -- [ ] **Step 4: Seed wordlist** - -```bash -mkdir -p wordlist/cassandra -printf "cassandra\nadmin\nuser\n" > wordlist/cassandra/user -printf "cassandra\nadmin\nchangeme\n" > wordlist/cassandra/password -``` - -- [ ] **Step 5: Run tests** - -Run: `go test ./brute/ -run TestBruteCassandraNoServer -v` -Expected: PASS. - -- [ ] **Step 6: Commit** - -```bash -git add brute/cassandra.go brute/cassandra_test.go wordlist/cassandra go.mod go.sum -git commit -m "feat(brute): cassandra CQL module with default wordlist" -``` - ---- - -## Task C3: CouchDB module - -**Files:** -- Create: `brute/couchdb.go` -- Create: `brute/couchdb_test.go` - -- [ ] **Step 1: Write the failing test** - -Create `brute/couchdb_test.go`: - -```go -package brute - -import ( - "testing" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func TestBruteCouchDBNoServer(t *testing.T) { - cm := modules.NewConnectionManager() - r := BruteCouchDB("127.0.0.1", 1, "admin", "admin", 1*time.Second, cm, nil) - if r.ConnectionSuccess { - t.Fatalf("expected ConnectionSuccess=false against closed port") - } -} -``` - -- [ ] **Step 2: Implement `brute/couchdb.go`** - -```go -package brute - -import ( - "context" - "fmt" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func BruteCouchDB(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { - return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { - scheme := "http" - if params["tls"] == "true" { - scheme = "https" - } - endpoint := fmt.Sprintf("%s://%s/_session", scheme, net.JoinHostPort(host, strconv.Itoa(port))) - tr := &http.Transport{ - DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { - return cm.Dial(network, addr) - }, - DisableKeepAlives: true, - } - cl := &http.Client{Transport: tr, Timeout: timeout} - form := url.Values{"name": {user}, "password": {password}} - req, err := http.NewRequestWithContext(ctx, "POST", endpoint, strings.NewReader(form.Encode())) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - resp, err := cl.Do(req) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - defer resp.Body.Close() - switch resp.StatusCode { - case 200: - return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} - case 401: - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, - Error: fmt.Errorf("couchdb 401")} - default: - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, - Error: fmt.Errorf("couchdb status %d", resp.StatusCode)} - } - }) -} - -func init() { Register("couchdb", BruteCouchDB) } -``` - -- [ ] **Step 3: Seed wordlist** - -```bash -mkdir -p wordlist/couchdb -printf "admin\ncouchdb\nuser\n" > wordlist/couchdb/user -printf "admin\ncouchdb\npassword\n" > wordlist/couchdb/password -``` - -- [ ] **Step 4: Run tests** - -Run: `go test ./brute/ -run TestBruteCouchDBNoServer -v` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add brute/couchdb.go brute/couchdb_test.go wordlist/couchdb -git commit -m "feat(brute): couchdb HTTP _session module with default wordlist" -``` - ---- - -## Task C4: Elasticsearch module - -**Files:** -- Create: `brute/elasticsearch.go` -- Create: `brute/elasticsearch_test.go` - -- [ ] **Step 1: Write the failing test** - -```go -package brute - -import ( - "testing" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func TestBruteElasticsearchNoServer(t *testing.T) { - cm := modules.NewConnectionManager() - r := BruteElasticsearch("127.0.0.1", 1, "elastic", "elastic", 1*time.Second, cm, nil) - if r.ConnectionSuccess { - t.Fatalf("expected ConnectionSuccess=false against closed port") - } -} -``` - -- [ ] **Step 2: Implement `brute/elasticsearch.go`** - -```go -package brute - -import ( - "context" - "fmt" - "net" - "net/http" - "strconv" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func BruteElasticsearch(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { - return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { - scheme := "http" - if params["tls"] == "true" { - scheme = "https" - } - endpoint := fmt.Sprintf("%s://%s/_cluster/health", scheme, net.JoinHostPort(host, strconv.Itoa(port))) - tr := &http.Transport{ - DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { - return cm.Dial(network, addr) - }, - DisableKeepAlives: true, - } - cl := &http.Client{Transport: tr, Timeout: timeout} - req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - req.SetBasicAuth(user, password) - resp, err := cl.Do(req) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - defer resp.Body.Close() - switch resp.StatusCode { - case 200: - return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} - case 401, 403: - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, - Error: fmt.Errorf("elasticsearch %d", resp.StatusCode)} - default: - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, - Error: fmt.Errorf("elasticsearch status %d", resp.StatusCode)} - } - }) -} - -func init() { Register("elasticsearch", BruteElasticsearch) } -``` - -- [ ] **Step 3: Seed wordlist** - -```bash -mkdir -p wordlist/elasticsearch -printf "elastic\nadmin\nkibana\nlogstash\n" > wordlist/elasticsearch/user -printf "elastic\nchangeme\nadmin\n" > wordlist/elasticsearch/password -``` - -- [ ] **Step 4: Run tests** - -Run: `go test ./brute/ -run TestBruteElasticsearchNoServer -v` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add brute/elasticsearch.go brute/elasticsearch_test.go wordlist/elasticsearch -git commit -m "feat(brute): elasticsearch HTTP basic-auth module with default wordlist" -``` - ---- - -## Task C5: InfluxDB module - -**Files:** -- Create: `brute/influxdb.go` -- Create: `brute/influxdb_test.go` - -- [ ] **Step 1: Write the failing test** - -```go -package brute - -import ( - "testing" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -func TestBruteInfluxDBNoServer(t *testing.T) { - cm := modules.NewConnectionManager() - r := BruteInfluxDB("127.0.0.1", 1, "admin", "admin", 1*time.Second, cm, nil) - if r.ConnectionSuccess { - t.Fatalf("expected ConnectionSuccess=false against closed port") - } -} -``` - -- [ ] **Step 2: Implement `brute/influxdb.go`** - -```go -package brute - -import ( - "context" - "fmt" - "net" - "net/http" - "strconv" - "time" - - "github.com/x90skysn3k/brutespray/v2/modules" -) - -// BruteInfluxDB targets InfluxDB 2.x. Treats `password` as the Influx -// token; the endpoint /api/v2/orgs returns 200 on valid auth, 401 on -// invalid. For InfluxDB 1.x, the operator can pass -m mode:v1 to use -// /ping with basic auth instead. -func BruteInfluxDB(host string, port int, user, password string, timeout time.Duration, cm *modules.ConnectionManager, params ModuleParams) *BruteResult { - return RunWithTimeout(timeout, func(ctx context.Context) *BruteResult { - scheme := "http" - if params["tls"] == "true" { - scheme = "https" - } - v1 := params["mode"] == "v1" - var endpoint string - if v1 { - endpoint = fmt.Sprintf("%s://%s/ping", scheme, net.JoinHostPort(host, strconv.Itoa(port))) - } else { - endpoint = fmt.Sprintf("%s://%s/api/v2/orgs", scheme, net.JoinHostPort(host, strconv.Itoa(port))) - } - tr := &http.Transport{ - DialContext: func(_ context.Context, network, addr string) (net.Conn, error) { - return cm.Dial(network, addr) - }, - DisableKeepAlives: true, - } - cl := &http.Client{Transport: tr, Timeout: timeout} - req, err := http.NewRequestWithContext(ctx, "GET", endpoint, nil) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - if v1 { - req.SetBasicAuth(user, password) - } else { - req.Header.Set("Authorization", "Token "+password) - } - resp, err := cl.Do(req) - if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} - } - defer resp.Body.Close() - switch resp.StatusCode { - case 200, 204: - return &BruteResult{AuthSuccess: true, ConnectionSuccess: true} - case 401, 403: - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, - Error: fmt.Errorf("influxdb %d", resp.StatusCode)} - default: - return &BruteResult{AuthSuccess: false, ConnectionSuccess: true, - Error: fmt.Errorf("influxdb status %d", resp.StatusCode)} - } - }) -} - -func init() { Register("influxdb", BruteInfluxDB) } -``` - -- [ ] **Step 3: Run tests** - -Run: `go test ./brute/ -run TestBruteInfluxDBNoServer -v` -Expected: PASS. - -- [ ] **Step 4: Commit** - -```bash -git add brute/influxdb.go brute/influxdb_test.go -git commit -m "feat(brute): influxdb v2 token + v1 basic-auth module" -``` - ---- - -## Task C6: SNMP wordlist tiering - -**Files:** -- Modify: `brute/snmp.go` -- Create: `wordlist/snmp_default.txt`, `wordlist/snmp_extended.txt`, `wordlist/snmp_full.txt` -- Modify: `modules/wordlist.go` (or wherever the `go:embed` directives live) - -- [ ] **Step 1: Author the three tiered lists** - -`wordlist/snmp_default.txt` — ~20 strings: - -``` -public -private -manager -admin -cisco -default -read -write -community -secret -test -rw -ro -guest -snmpd -snmp -internal -external -local -network -``` - -`wordlist/snmp_extended.txt` — superset (~50) adding vendor-specific: - -``` -[contents of snmp_default.txt] -proxy -proxy@cisco -tivoli -ILMI -all -all private -0392a0 -1234 -admin@123 -agent_steal -c0nfig -cable-d -cisco_router -fuckyou -hp_admin -juniper -juniper_admin -juniper_ro -juniper_rw -not_public -NoGaH$@! -OrigEquipMfr -private@123 -proxy@123 -read-only -read-write -readonly -readwrite -regional -router -SAEM -SECURITY -SNMP -snmpd -SNMPV2 -snmpwrite -snmp_trap -SuN_MaNaGeR -SwitcHeS -SyStEm -TeNet -test2 -trap -work -xerox -``` - -`wordlist/snmp_full.txt` — superset (~120) adding SCADA / IP camera / storage: - -``` -[contents of snmp_extended.txt] -nimbus -NIMBUS -ICAM -ICAM_RW -ICAM_RO -SCADA -SCADA_RW -SCADA_RO -ifak -schneider -plc_admin -plc_user -modbus_admin -plc_default -plc_user -SitelA -SiteIIA -SiteIII -SiteIV -PUBLIC -SECRETID -device -device_admin -iLO -iLOAdmin -PRTG -prtg -solarwinds -intermapper -ENTPASS -ENTERPRISE -NETMAN -ONS_dmsadm -NET_OPS -TEAM -mtg -camera -hikvision -dahua -axis -arecont -foscam -trendnet -sony_camera -sony -amcrest -emc -isilon -oncue -sanstation -netapp -synology -qnap -buffalo -``` - -The `full` tier above is the starting set committed in this PR. Additional documented vendor defaults can land as follow-on `chore(snmp): ...` PRs in the same cadence as the monthly wordlist refresh — do not block this PR on an exhaustive list. - -- [ ] **Step 2: Embed the new lists** - -In `modules/wordlist.go`, locate the existing `//go:embed` block(s) and extend: - -```go -//go:embed snmp_default.txt snmp_extended.txt snmp_full.txt -var snmpLists embed.FS - -func SNMPCommunities(tier string) ([]string, error) { - var fname string - switch tier { - case "extended": - fname = "snmp_extended.txt" - case "full": - fname = "snmp_full.txt" - default: - fname = "snmp_default.txt" - } - data, err := snmpLists.ReadFile(fname) - if err != nil { - return nil, err - } - var out []string - for _, line := range strings.Split(string(data), "\n") { - s := strings.TrimSpace(line) - if s != "" && !strings.HasPrefix(s, "#") { - out = append(out, s) - } - } - return out, nil -} -``` - -(Move the wordlist files into `modules/` if the embed paths require it — adapt to existing layout.) - -- [ ] **Step 3: Wire tier selection into `brute/snmp.go`** - -Find the existing community-string source in `brute/snmp.go` and replace with: - -```go -tier := params["mode"] -communities, err := modules.SNMPCommunities(tier) -if err != nil { - return &BruteResult{AuthSuccess: false, ConnectionSuccess: false, Error: err} -} -// ... existing per-community probe loop continues, iterating over communities -``` - -- [ ] **Step 4: Test** - -```go -// brute/snmp_test.go (existing file) — add: -func TestSNMPCommunitiesTiering(t *testing.T) { - def, _ := modules.SNMPCommunities("default") - ext, _ := modules.SNMPCommunities("extended") - full, _ := modules.SNMPCommunities("full") - if !(len(def) < len(ext) && len(ext) < len(full)) { - t.Fatalf("tier sizes not strictly increasing: %d %d %d", len(def), len(ext), len(full)) - } -} -``` - -Run: `go test ./brute/ -run TestSNMPCommunitiesTiering -v` -Expected: PASS. - -- [ ] **Step 5: Commit** - -```bash -git add brute/snmp.go modules/wordlist.go wordlist/snmp_*.txt brute/snmp_test.go -git commit -m "feat(snmp): default/extended/full community-string tiering" -``` - ---- - -## Task C7: Inline credential pairs `-c/--creds` - -**Files:** -- Modify: `brutespray/config.go` -- Modify: `brutespray/dispatch.go` -- Create: `brutespray/dispatch_creds_test.go` - -- [ ] **Step 1: Add the flag** - -In `brutespray/config.go`: - -```go -flag.StringVar(&Cfg.Creds, "c", "", "Inline credential pairs, comma-separated: user:pass,user2:pass2") -flag.StringVar(&Cfg.Creds, "creds", "", "Alias for -c") -``` - -Add to `Config`: - -```go -Creds string -``` - -- [ ] **Step 2: Write the failing test** - -Create `brutespray/dispatch_creds_test.go`: - -```go -package brutespray - -import ( - "reflect" - "testing" -) - -func TestParseInlineCreds(t *testing.T) { - pairs := ParseInlineCreds("admin:admin,root:toor,user::") - want := []CredPair{ - {User: "admin", Password: "admin"}, - {User: "root", Password: "toor"}, - {User: "user", Password: ":"}, - } - if !reflect.DeepEqual(pairs, want) { - t.Fatalf("got %+v want %+v", pairs, want) - } -} - -func TestParseInlineCredsEmptyInput(t *testing.T) { - if pairs := ParseInlineCreds(""); pairs != nil { - t.Fatalf("empty input should yield nil, got %+v", pairs) - } -} -``` - -- [ ] **Step 3: Implement `ParseInlineCreds`** - -In `brutespray/dispatch.go`: - -```go -// ParseInlineCreds parses "user:pass,user2:pass2" form. Splits on the -// FIRST colon per pair so passwords with colons survive. -func ParseInlineCreds(s string) []CredPair { - if s == "" { - return nil - } - var out []CredPair - for _, part := range strings.Split(s, ",") { - idx := strings.Index(part, ":") - if idx < 0 { - continue - } - out = append(out, CredPair{User: part[:idx], Password: part[idx+1:]}) - } - return out -} -``` - -- [ ] **Step 4: Hook into `ProcessHost`** - -Where credentials are assembled, prepend the inline pairs (so they fire first): - -```go -if Cfg.Creds != "" { - for _, p := range ParseInlineCreds(Cfg.Creds) { - queueCred(p.User, p.Password) - } -} -``` - -- [ ] **Step 5: Run tests** - -Run: `go test ./brutespray/ -run TestParseInlineCreds -v` -Expected: PASS. - -- [ ] **Step 6: Commit** - -```bash -git add brutespray/config.go brutespray/dispatch.go brutespray/dispatch_creds_test.go -git commit -m "feat(cli): -c/--creds inline credential pairs" -``` - ---- - -## Task C8: Update service lists in `brutespray/config.go` - -**Files:** -- Modify: `brutespray/config.go` - -- [ ] **Step 1: Mark stable vs beta** - -`BetaServiceList` at line 20 currently does not include the new modules. Promote couchdb/elasticsearch/influxdb to stable (well-defined HTTP endpoints), leave neo4j/cassandra in beta until docker integration tests cover them: - -```go -var BetaServiceList = []string{ - "asterisk", "nntp", "oracle", "xmpp", "ldap", "ldaps", "winrm", "ftps", - "smtp-vrfy", "rexec", "rlogin", "rsh", "wrapper", "http-form", "https-form", - "svn", "socks5-auth", - "neo4j", "cassandra", // new — gated until docker harness covers them -} -``` - -(couchdb/elasticsearch/influxdb implicitly stable by not being listed.) - -- [ ] **Step 2: Verify recognized-service list also covers the five new ones** - -In `brutespray/config.go`, locate `ServiceList` / `AllServices` / equivalent — add `"neo4j", "cassandra", "couchdb", "elasticsearch", "influxdb"`. - -- [ ] **Step 3: Build + smoke test** - -```bash -go build -o brutespray . && ./brutespray -s influxdb -H 127.0.0.1 -u admin -p admin 2>&1 | head -``` - -Expected: brutespray recognizes the service (will error on connection refused, which is fine). - -- [ ] **Step 4: Commit** - -```bash -git add brutespray/config.go -git commit -m "feat(cli): register 5 new DB services (couchdb/elasticsearch/influxdb stable; neo4j/cassandra beta)" -``` - ---- - -# Phase D — Documentation + comparison table - -## Task D1: README comparison table - -**Files:** -- Modify: `README.md` - -- [ ] **Step 1: Insert the table** - -After the existing feature list / before the services list, add: - -```markdown -## How brutespray compares - -| Feature | brutespray | hydra | medusa | ncrack | brutus | -|---|---|---|---|---|---| -| Single static binary | ✅ | ❌ | ❌ | ❌ | ✅ | -| Interactive TUI | ✅ | ❌ | ❌ | ❌ | ❌ | -| Checkpoint / resume | ✅ | ❌ | ❌ | ✅ | ❌ | -| Spray mode (lockout-aware) | ✅ | ❌ | ❌ | ❌ | ❌ | -| Per-attempt JSONL output | ✅ | ⚠️ | ❌ | ❌ | ❌ (success-only) | -| SOCKS5 + proxy rotation | ✅ | ⚠️ | ❌ | ❌ | ❌ | -| Embedded SSH bad-keys (CVE-tagged) | ✅ | ❌ | ❌ | ❌ | ✅ | -| Pipeline stdin (naabu / fingerprintx / masscan) | ✅ | ❌ | ❌ | ❌ | ✅ | -| Pre-auth RDP recon (NLA / sticky-keys) | ✅ | ❌ | ❌ | ❌ | ✅ | -| Nmap gnmap + XML / Nessus / Nexpose import | ✅ | ⚠️ | ❌ | ❌ | ⚠️ (nmap only) | -| Per-module params (`-m KEY:VAL`) | ✅ | ❌ | ❌ | ❌ | partial | -| Service count | 41 | 50+ | 34 | 14 | 23 | - -> Verify each ✅/⚠️/❌ against the named tool's current documentation before merging — competing tools update fast. -``` - -- [ ] **Step 2: Commit** - -```bash -git add README.md -git commit -m "docs(readme): add brutespray-vs-others comparison table" -``` - ---- - -## Task D2: `docs/services.md` — five new modules - -**Files:** -- Modify: `docs/services.md` - -- [ ] **Step 1: Add rows for the new services** - -Follow the existing table format. For each of neo4j, cassandra, couchdb, elasticsearch, influxdb, add a row: - -```markdown -| neo4j | Neo4j Bolt v5 graph DB | 7687 | TCP | Beta | `-s neo4j` | -| cassandra | Apache Cassandra CQL | 9042 | TCP | Beta | `-s cassandra` | -| couchdb | CouchDB HTTP `_session` | 5984 | TCP | Stable | `-s couchdb` | -| elasticsearch | Elasticsearch HTTP basic auth | 9200 | TCP | Stable | `-s elasticsearch` | -| influxdb | InfluxDB v2 token / v1 basic auth | 8086 | TCP | Stable | `-s influxdb -m mode:v1` for 1.x | -``` - -(Match column shape to actual `docs/services.md` — read the file first.) - -- [ ] **Step 2: Commit** - -```bash -git add docs/services.md -git commit -m "docs(services): document 5 new DB modules" -``` - ---- - -## Task D3: `docs/advanced.md` — SSH bad-keys + RDP recon - -**Files:** -- Modify: `docs/advanced.md` - -- [ ] **Step 1: Add SSH bad-keys section** - -```markdown -## SSH bad-keys - -Brutespray ships an embedded bundle of known-compromised SSH private keys -(Rapid7 ssh-badkeys + HashiCorp Vagrant + per-vendor defaults). Whenever -you target SSH, the bundle is tried first with each key's metadata-suggested -default username (root for F5 BIG-IP, vagrant for Vagrant, mateidu for -Ceragon FibeAir, etc.). - -| Flag | Effect | -|---|---| -| (default) | Bad-keys pass runs first; passwords follow if no key matches | -| `--no-badkeys` | Skip the bad-keys pass entirely | -| `--badkeys-only` | Run the bad-keys pass only; skip password attempts | - -### CVE mapping - -Successful bad-key authentications surface as a `BADKEY` line and carry the -CVE identifier in JSONL output as `key_match.cve`. - -| Vendor | Default user | CVE | -|---|---|---| -| HashiCorp Vagrant | vagrant | (no CVE — documented insecure default) | -| F5 BIG-IP | root | CVE-2012-1493 | -| ExaGrid EX | root | CVE-2016-1561 | -| Ceragon FibeAir | mateidu | (no CVE) | -| (others) | varies | varies | - -The bundle is refreshed monthly alongside the existing wordlist update cadence. -``` - -- [ ] **Step 2: Add RDP pre-auth recon section** - -```markdown -## Pre-auth RDP recon - -When the target service is `rdp`, brutespray runs two pre-auth probes -before any credential attempt. Both are best-effort and add only one TCP -round-trip per host. - -### NLA fingerprint - -Sends a single X.224 Connection Request and reads the server's RDPneg -response to classify NLA enforcement: - -- `[INFO] rdp NLA (CredSSP) enforced` — NLA required, standard RDP refused -- `[INFO] rdp HybridEx (NLA + CredSSP early-user) enforced` -- `[WARN] rdp NLA not enforced — server accepts standard RDP` - -### Sticky-keys backdoor - -When NLA is not enforced, brutespray connects to the logon screen, sends -five Shift keypresses (the sticky-keys trigger), and snapshots the -framebuffer before and after. If the post-trigger frame matches the -heuristic for a cmd.exe console (predominantly black with monospaced -white text in the top region), the finding is: - -``` -[CRITICAL] rdp sticky-keys backdoor detected -``` - -If the framebuffer changed but the console signature does not match, the -result is downgraded: - -``` -[INFO] rdp sticky-keys inconclusive; manual verification recommended -``` - -Opt out of both probes with `--no-rdp-scan`. -``` - -- [ ] **Step 2 (continued): Commit** - -```bash -git add docs/advanced.md -git commit -m "docs(advanced): SSH bad-keys + pre-auth RDP recon" -``` - ---- - -## Task D4: `docs/output.md` — Finding and KeyMatch JSONL schema - -**Files:** -- Modify: `docs/output.md` - -- [ ] **Step 1: Add new sections** - -```markdown -## Finding records (JSONL) - -Pre-auth recon results emit one JSON object per line: - -```json -{"type":"finding","severity":"WARN","code":"rdp-nla-missing","service":"rdp","target":"10.0.0.5:3389","message":"NLA not enforced — server accepts standard RDP without pre-auth"} -{"type":"finding","severity":"CRITICAL","code":"rdp-stickykeys","service":"rdp","target":"10.0.0.5:3389","message":"sticky-keys backdoor detected (cmd.exe shell at logon screen)"} -``` - -| Field | Description | -|---|---| -| `type` | Always `"finding"` for these records | -| `severity` | `INFO`, `WARN`, `HIGH`, `CRITICAL` | -| `code` | Stable machine identifier — `rdp-nla-missing`, `rdp-stickykeys`, `rdp-stickykeys-inconclusive`, `rdp-nla-required`, `rdp-nla-hybridex`, `ssh-badkey` | -| `service` / `target` | Target identification | -| `message` | Human-readable description | -| `cve` | Present only when a CVE applies (e.g. F5 bad key) | - -## KeyMatch on SSH success - -When SSH authentication succeeds against an embedded bad key, the per-success -JSONL record gains a `key_match` object: - -```json -{"type":"success","service":"ssh","target":"10.0.0.5:22","username":"vagrant","password":"::badkey::0","key_match":{"fingerprint":"sha256:abc...","vendor":"HashiCorp Vagrant","cve":"","description":"Vagrant insecure default key (any Vagrant VM pre-2014)"}} -``` -``` - -- [ ] **Step 2: Commit** - -```bash -git add docs/output.md -git commit -m "docs(output): Finding and KeyMatch JSONL schema" -``` - ---- - -## Task D5: `docs/wordlists.md` — SNMP tiering - -**Files:** -- Modify: `docs/wordlists.md` - -- [ ] **Step 1: Add SNMP tiering subsection** - -```markdown -## SNMP community-string tiers - -The `snmp` module ships three embedded tiers, selected via `-m mode:`: - -| Tier | Size | Contents | -|---|---|---| -| `default` (default) | ~20 | classic public/private/cisco-style community strings | -| `extended` | ~50 | + per-vendor (Cisco / HP / Juniper) enterprise defaults | -| `full` | ~120 | + SCADA controllers, IP cameras, NAS / storage arrays | - -Example: - -``` -brutespray -s snmp -H 10.0.0.0/24 -m mode:full -``` -``` - -- [ ] **Step 2: Commit** - -```bash -git add docs/wordlists.md -git commit -m "docs(wordlists): SNMP tiered community-string lists" -``` - ---- - -## Task D6: `docs/usage.md` — new flags - -**Files:** -- Modify: `docs/usage.md` - -- [ ] **Step 1: Add flag rows** - -Locate the existing flags table (or list) and add: - -```markdown -| `--no-badkeys` | Skip the embedded SSH bad-keys pre-pass | -| `--badkeys-only` | Run the embedded SSH bad-keys pre-pass only; skip passwords | -| `--no-rdp-scan` | Skip pre-auth RDP recon (NLA fingerprint + sticky-keys probe) | -| `-c, --creds STR` | Inline credential pairs, comma-separated: `admin:admin,root:toor` | -``` - -Add a "stdin pipeline" subsection: - -```markdown -### Reading targets from stdin - -When `-f` is not supplied and stdin is a pipe, brutespray reads targets -from stdin and auto-detects the input format (naabu line, Nerva URI, -Nerva JSON, fingerprintx JSON, masscan JSON): - -``` -naabu -host 10.0.0.0/24 -p 22 -silent | brutespray -u root -P wordlist/ssh.txt -masscan -p22,3389 10.0.0.0/24 -oJ - | brutespray -u admin -p admin -``` -``` - -- [ ] **Step 2: Commit** - -```bash -git add docs/usage.md -git commit -m "docs(usage): new flags + stdin pipeline section" -``` - ---- - -## Task D7: `docs/pipeline.md` — end-to-end recon workflow - -**Files:** -- Create: `docs/pipeline.md` - -- [ ] **Step 1: Write the new doc** - -```markdown -# Pipeline integration - -brutespray accepts targets on stdin and auto-detects the format. This makes -it a natural terminator for modern Go recon pipelines. - -## naabu → brutespray - -``` -naabu -host 10.0.0.0/24 -p 22,3306,3389,5984 -silent \ - | brutespray -u root -P wordlist/_base/password -``` - -naabu emits `host:port` lines; brutespray maps each port to a service via -the default-port table (22→ssh, 3306→mysql, 3389→rdp, 5984→couchdb). - -## naabu → fingerprintx → brutespray - -``` -naabu -host 10.0.0.0/24 -silent \ - | fingerprintx --json \ - | brutespray -u root -P wordlist/_base/password -``` - -fingerprintx emits JSON with `service` already classified — brutespray -uses that directly and skips the port-table fallback. - -## masscan → brutespray - -``` -masscan -p22,3389,5984 10.0.0.0/24 -oJ - \ - | brutespray --no-badkeys -u admin -p admin -``` - -masscan's JSON array is decoded; only open ports are forwarded; closed -and filtered are dropped. - -## SSH bad-keys only - -``` -masscan -p22 10.0.0.0/24 -oJ - \ - | brutespray --badkeys-only --output-format json -o results.jsonl -``` - -Skips password attempts entirely. Each successful match emits a -`key_match` record (see `output.md`) carrying the vendor and CVE. - -## RDP recon scan - -``` -naabu -host 10.0.0.0/24 -p 3389 -silent \ - | brutespray -s rdp -u test -p test --output-format json -o rdp-findings.jsonl -``` - -The NLA fingerprint and sticky-keys probe run before any credential -attempts. Findings flow into the same JSONL stream as auth attempts; filter -by `type=="finding"` downstream. -``` - -- [ ] **Step 2: Commit** - -```bash -git add docs/pipeline.md -git commit -m "docs(pipeline): end-to-end recon workflow with naabu/fingerprintx/masscan" -``` - ---- - -## Task D8: CLAUDE.md (local-only) update - -**Files:** -- Modify: `CLAUDE.md` (local only — do NOT `git add`) - -- [ ] **Step 1: Update the Services + Module Parameters sections** - -Add to "Services (36+)" line: - -``` -Stable: ssh, ftp, telnet, smtp, imap, pop3, mysql, postgres, mssql, mongodb, redis, vnc, snmp, smbnt, rdp, http, https, vmauthd, teamspeak, couchdb, elasticsearch, influxdb -Beta: asterisk, nntp, oracle, xmpp, ldap, ldaps, winrm, ftps, smtp-vrfy, rexec, rlogin, rsh, wrapper, http-form, https-form, svn, socks5-auth, neo4j, cassandra -``` - -Add to "Module Parameters": - -``` -- `snmp`: `-m mode:default|extended|full` (community-string tier) -- `influxdb`: `-m mode:v1` (use 1.x basic auth instead of 2.x token) -- `couchdb` / `elasticsearch` / `influxdb`: `-m tls:true` for HTTPS endpoints -``` - -Add new "Pre-auth recon" subsection under Conventions: - -``` -- `--no-badkeys` / `--badkeys-only` — SSH bad-keys pre-pass control -- `--no-rdp-scan` — disable RDP NLA fingerprint + sticky-keys probe -- Stdin targets: when no `-f` and stdin is a pipe, parse-stream auto-detects format (naabu / Nerva URI / Nerva JSON / fingerprintx JSON / masscan JSON) -``` - -- [ ] **Step 2: Verify CLAUDE.md is NOT staged** - -```bash -git status -s CLAUDE.md -``` - -Expected: line begins with ` M` (working-tree modified) NOT `M ` (staged). - -If accidentally staged: - -```bash -git restore --staged CLAUDE.md -``` - -- [ ] **Step 3: No commit** — CLAUDE.md is intentionally not committed per project policy. - ---- - -## Task D9: Full regression + integration sweep + open PR - -**Files:** none modified — verification only. - -- [ ] **Step 1: Full unit test run** - -Run: `go test ./... -count=1` -Expected: all tests pass, including the 106 from prior work plus all new tests in this PR. - -- [ ] **Step 2: Race-detector run (skip the known IMAP race per the codebase convention)** - -Run: `go test ./... -race -count=1` -Expected: race-clean. - -- [ ] **Step 3: Lint** - -Run: `golangci-lint run` -Expected: zero issues. If new issues appear, fix inline; do not silence. - -- [ ] **Step 4: Docker-backed module tests** - -Run: - -```bash -docker run -d --name btest-couchdb -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=admin -p 5984:5984 couchdb:3 -docker run -d --name btest-es -e "discovery.type=single-node" -e "xpack.security.enabled=true" -e "ELASTIC_PASSWORD=changeme" -p 9200:9200 elasticsearch:8.11.0 -docker run -d --name btest-influx -e DOCKER_INFLUXDB_INIT_MODE=setup -e DOCKER_INFLUXDB_INIT_USERNAME=admin -e DOCKER_INFLUXDB_INIT_PASSWORD=adminadmin -e DOCKER_INFLUXDB_INIT_ORG=test -e DOCKER_INFLUXDB_INIT_BUCKET=test -e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=mytoken -p 8086:8086 influxdb:2 -docker run -d --name btest-neo4j -e NEO4J_AUTH=neo4j/testtest -p 7687:7687 neo4j:5 -docker run -d --name btest-cass -p 9042:9042 cassandra:4 -sleep 30 -BRUTESPRAY_DOCKER_TESTS=1 go test ./brute/ -run "TestBrute(CouchDB|Elasticsearch|InfluxDB|Neo4j|Cassandra)Docker" -v -docker rm -f btest-couchdb btest-es btest-influx btest-neo4j btest-cass -``` - -Expected: each docker-gated test passes against its real server. - -- [ ] **Step 5: Stdin pipeline smoke** - -```bash -echo "127.0.0.1:22" | ./brutespray -u root -p test 2>&1 | head -printf '[{"ip":"127.0.0.1","ports":[{"port":22,"proto":"tcp","status":"open"}]}]' | ./brutespray -u root -p test 2>&1 | head -``` - -Expected: both invocations parse and attempt connect (refused is fine; we are testing the parse path). - -- [ ] **Step 6: Comparison-table verification** - -Open the docs/release pages of hydra, medusa, ncrack, brutus. Verify each ✅/⚠️/❌ in the README table against current behavior. Edit the table if any cell is out of date. - -- [ ] **Step 7: Open the PR** - -```bash -gh pr create --base master --head dev \ - --title "feat: pre-auth recon (SSH bad-keys, RDP NLA + sticky-keys), stdin pipeline, 5 DB modules" \ - --body "$(cat <<'EOF' -## Summary - -Borrows four high-value capabilities surveyed in the contemporary cred-test landscape and lands them on brutespray, plus a brutespray-vs-others comparison table for the README so positioning is in sync with the new feature set. - -### Pre-auth recon -- Embedded SSH bad-keys bundle (Rapid7 ssh-badkeys + Vagrant + per-vendor keys, CVE-tagged) -- RDP NLA fingerprint and sticky-keys backdoor probe (coordinated change in sibling grdp repo) -- New `Finding` and `KeyMatch` BruteResult fields, surfaced in text/JSONL output and a new TUI Findings tab -- Flags: `--no-badkeys`, `--badkeys-only`, `--no-rdp-scan` - -### Pipeline integration -- Stdin auto-detect: naabu, Nerva URI, Nerva JSON, fingerprintx JSON, masscan JSON -- Masscan -oJ ingestion via existing port→service mapping - -### New modules -- Neo4j (Bolt v5), Cassandra (CQL), CouchDB, Elasticsearch, InfluxDB (v2 token + v1 basic) -- SNMP wordlist tiering: default / extended / full (SCADA + camera + storage vendors) -- `-c/--creds` inline credential pairs - -### Docs -- README comparison table vs hydra / medusa / ncrack / brutus -- `docs/advanced.md`: bad-keys CVE table + RDP recon details -- `docs/output.md`: Finding + KeyMatch JSONL schemas -- `docs/services.md`: 5 new module rows -- `docs/wordlists.md`: SNMP tiering -- `docs/usage.md`: new flags + stdin section -- `docs/pipeline.md` (new): end-to-end recon walkthrough - -Spec: `docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md` - -## Test plan - -- [x] `go test ./... -count=1` clean -- [x] `go test ./... -race -count=1` clean (IMAP race skip respected) -- [x] `golangci-lint run` clean -- [x] Docker-backed integration tests for couchdb / elasticsearch / influxdb / neo4j / cassandra -- [x] Stdin pipeline smoke (naabu + masscan JSON forms) -- [x] Comparison table verified against each named tool's current docs -EOF -)" -``` - -Expected: PR URL printed; CI starts. - -- [ ] **Step 8: Final commit if any verification surfaced doc/code drift** - -Any small fixes from Steps 6-7 land as a separate commit so the diff stays auditable: - -```bash -git add -A -git commit -m "docs/test: post-verification adjustments before PR" -git push -``` - ---- - -## Spec coverage map - -| Spec section | Covered by | -|---|---| -| Goal 1 — SSH bad-keys bundle | A2, A3, A4 | -| Goal 2 — RDP NLA + sticky-keys recon | A5, A6, A7 | -| Goal 3 — Stdin pipeline auto-detect + masscan JSON | B1, B2, B3 | -| Goal 4 — 5 DB modules | C1, C2, C3, C4, C5 | -| Goal 5 — SNMP tiering | C6 | -| Goal 6 — Inline cred pairs `-c` | C7 | -| Goal 7 — README comparison table + docs sweep | D1, D2, D3, D4, D5, D6, D7, D8 | -| BruteResult Finding/KeyMatch fields | A1 | -| Output text + JSONL rendering | A8 | -| TUI Findings tab | A9 | -| Stable/beta service list updates | C8 | -| Final regression + PR | D9 | diff --git a/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md b/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md deleted file mode 100644 index 1267402..0000000 --- a/docs/superpowers/specs/2026-05-29-recon-pipeline-and-modules-design.md +++ /dev/null @@ -1,207 +0,0 @@ -# Pre-auth Recon, Stdin Pipeline, and New Modules — Design Spec - -**Date:** 2026-05-29 -**Branch:** dev -**Delivery:** single combined release PR (phases A → B → C → D, in order) - -## Context - -Praetorian shipped Brutus in February 2026 — a Go-based, single-binary credential testing tool positioned explicitly as a Hydra alternative. It overlaps brutespray's domain but introduces a few genuinely new ideas: an embedded SSH bad-keys bundle, pre-auth RDP recon (NLA fingerprinting, sticky-keys backdoor detection), native stdin pipelining with `naabu`/`fingerprintx`/`masscan`, and a handful of databases brutespray doesn't yet cover. Brutespray remains stronger overall (TUI, checkpoint/resume, spray-mode lockout-awareness, SOCKS5 + proxy rotation, per-attempt JSONL, 36 services, Nessus/Nexpose imports) but is missing those four specific capabilities. - -This spec borrows the high-value ideas and lands them on `dev` as one release PR. The brutespray-vs-others comparison table on the README is folded in as part of the same release so the marketing surface lines up with the new features. - -## Goals - -1. Match Brutus's SSH bad-key testing with a vendored Rapid7 ssh-badkeys bundle + Vagrant + vendor keys, automatic per-key username pairing, CVE metadata in output. -2. Add pre-auth RDP recon: NLA fingerprinting and Sticky-Keys backdoor detection. Findings flow through normal output channels (text, JSON, TUI). -3. Make brutespray pipeline-friendly: read targets from stdin with format auto-detection (Nerva URI, naabu line, fingerprintx JSON, masscan JSON, bare `host:port`). -4. Add five database modules (Neo4j, Cassandra, CouchDB, Elasticsearch, InfluxDB) following the existing module pattern. -5. SNMP wordlist tiering (`default` / `extended` / `full`) with SCADA / camera / storage vendor strings. -6. Inline credential pairs via `-c user:pass[,user2:pass2…]`. -7. README comparison table positioning brutespray against Hydra, Medusa, Ncrack, Brutus. Full docs sweep. - -## Non-goals - -- Claude Vision web fingerprinting (`--experimental-ai`). Out of scope this release; pulls in headless Chrome + external API dependencies. Revisit separately. -- RDP backdoor `--exec` command execution. Legally fraught even on authorized engagements. -- RDP web terminal viewer. Overlaps grdp scope, big surface area. -- Brutus's subcommand structure (`brutus creds` / `web` / `snmp` / `badkeys`). Diverges from brutespray's flat-CLI philosophy. - -## Architecture - -All four phases follow existing brutespray patterns: `init()`-based module registration via `Register()` in `brute/registry.go`, `brute.ModuleParams` for per-module flags, `ConnectionManager.Dial()` for network I/O with proxy/interface support, `time.NewTimer` + goroutine + `select` for timeouts. Phases are ordered to minimize risk: A is self-contained, B touches the input parser, C is additive modules, D is pure docs. - -### Phase A — Pre-auth recon - -**SSH bad-keys** — new `brute/badkeys/` package using `go:embed` to vendor a snapshot of [Rapid7/ssh-badkeys](https://github.com/rapid7/ssh-badkeys) plus the HashiCorp Vagrant insecure key plus the per-vendor keys Brutus ships (F5 BIG-IP, ExaGrid, Barracuda, Ceragon FibeAir, Array Networks, Quantum DXi, Loadbalancer.org). Layout: - -``` -brute/badkeys/ - embed.go // go:embed keys/*.pem and metadata.yaml - registry.go // KeyEntry{Fingerprint, Username, CVE, Vendor, PEM} - registry_test.go - keys/ // vendored .pem files - metadata.yaml // fingerprint → {username, CVE, vendor} -``` - -`brute/ssh.go` extended: when `params["badkeys"] != "false"` (default on), the bad-keys pass runs **before** password attempts and on first match short-circuits (no further passwords for that host). Each key is paired with its default username from metadata. A successful key auth populates a new `BruteResult.KeyMatch *KeyEntry` field; output layer renders `[+] BADKEY ssh root@10.0.0.5 vagrant-insecure-key (CVE-2015-1338)`. - -Flags: -- `--no-badkeys` — opt-out (the user instruction). Default behavior: on for SSH. -- `--badkeys-only` — skip password attempts entirely. -- `-m badkeys-bundle:default|extended|full` — wordlist-style tiering, mirrors SNMP tiering in Phase C. - -Refresh cadence: monthly `chore(badkeys)` PR mirroring the existing `chore: monthly wordlist update` pattern visible in `git log`. - -**RDP NLA fingerprint** — `brute/rdp.go` adds a pre-auth probe that sends the X.224 Connection Request (RDPneg request) and parses the server's RDPneg response. If `PROTOCOL_HYBRID` (NLA) is required, finding is logged as `[INFO] rdp 10.0.0.5:3389 NLA-required`. If `PROTOCOL_RDP` only (no NLA), `[WARN] rdp 10.0.0.5:3389 NLA-not-enforced (CVE-class)`. Runs once per host before any brute attempts. - -**RDP Sticky-Keys backdoor scan** — after NLA fingerprint, if the target accepts standard RDP, connect to the logon screen via `x90skysn3k/grdp` (existing dep). Send 5× Shift down/up scancodes (sticky-keys trigger) and capture the resulting bitmap region around the active window. Detection is two-stage: (1) the post-trigger framebuffer must change meaningfully versus the pre-trigger frame (rules out servers ignoring input); (2) the top-left active-window region is OCR'd via a small embedded title-bar font matcher looking for `cmd.exe`, `C:\Windows\system32`, or a black console background. Finding emits as `[CRITICAL] rdp 10.0.0.5:3389 sticky-keys backdoor detected`. If detection is inconclusive (framebuffer changed but no console match), emit `[INFO] rdp 10.0.0.5:3389 sticky-keys inconclusive` so the operator can verify manually. Opt-out via `--no-rdp-scan`. - -**Implementation note:** brutespray's `x90skysn3k/grdp` dependency lives at `../grdp/` and is owned by this project. The sticky-keys probe will be implemented by adding `client.RdpClient.CaptureLogonScreen(trigger LogonTrigger) (*image.RGBA, error)` (plus supporting framebuffer plumbing) to grdp, then consuming it from `brute/rdp.go`. Coordinated change across the two repos. - -Result type expansion in `brute/run.go`: - -```go -type BruteResult struct { - // ... existing fields ... - Finding *Finding // pre-auth scan result - KeyMatch *KeyEntry // SSH bad-key match -} -type Finding struct { - Severity string // INFO|WARN|HIGH|CRITICAL - Code string // ssh-badkey|rdp-nla-missing|rdp-stickykeys - Message string - CVE string // optional -} -``` - -Output layer (`modules/output.go`) renders findings into text and JSONL streams; TUI gets a "Findings" tab alongside the existing tabs (see `tui/` for the existing tabbed Model). - -### Phase B — Stdin pipeline + masscan JSON - -`modules/parse.go` currently dispatches on file extension / contents for `-f`. Extend with: - -1. **Stdin detection** in `brutespray/config.go` / `brutespray.go:Execute`: if `-f` is empty and `os.Stdin` is a pipe (not a TTY — use `term.IsTerminal(int(os.Stdin.Fd()))`), read stdin as the target source. - -2. **Format auto-detection** in a new `modules/parse_stream.go`: - - First non-blank line decides format. - - Line starts with `{` → JSON. Probe for `nerva` (has `protocol`+`port`+`ip`), `fingerprintx` (has `host`+`service`), or `masscan` (has `ports[]` array). Dispatch to matching parser. - - Line matches `^[a-z]+://[^:]+:\d+` → Nerva URI. - - Line matches `^[^\s:]+:\d+$` → bare host:port (naabu `-silent` output). - - Anything else: pass through existing `-f` parsers (gnmap/etc.) attempting each. - -3. **Masscan JSON parser** — new file `modules/parse_masscan.go`. Masscan emits an array of `{ip, ports: [{port, proto, status}]}` objects. Only emit targets where `status="open"`. Port→protocol mapping reuses the existing default-port table. - -No new CLI flag. `naabu -silent | brutespray -u root -p rootpass` just works. - -### Phase C — New modules + SNMP tiering + inline cred pairs - -**Five new modules** in `brute/`: - -- `neo4j.go` — Bolt v5 protocol over TCP/7687. Uses `github.com/neo4j/neo4j-go-driver/v5/neo4j` (already widely used, MIT). Test via `docker run -p 7687:7687 neo4j:5`. -- `cassandra.go` — CQL native protocol over TCP/9042. Uses `github.com/gocql/gocql`. `PasswordAuthenticator`. Default-port 9042. Test via `cassandra:4`. -- `couchdb.go` — HTTP basic auth against `_session` endpoint, port 5984. No new dep; reuse existing http auth helpers. -- `elasticsearch.go` — HTTP basic auth against `/_cluster/health`, port 9200. No new dep. -- `influxdb.go` — InfluxDB 2.x token/basic auth against `/api/v2/orgs`, port 8086. No new dep. - -Each module follows the standard pattern from `CLAUDE.md`: `BruteXxx(host, port, user, password, timeout, cm, params) *BruteResult`, `cm.Dial()`, deadline set, timer/select timeout, `init()` registers via `Register()`. Add to stable service list in `brutespray/config.go` (couchdb/elasticsearch/influxdb safe to mark stable; neo4j/cassandra start as beta until docker-tested across versions). - -**SNMP tiering** — `brute/snmp.go` currently tries a single embedded community list. Split into `wordlist/snmp_default.txt` (~20: public, private, cisco, manager…), `wordlist/snmp_extended.txt` (~50: + Cisco/HP/Juniper enterprise), `wordlist/snmp_full.txt` (~120: + SCADA, IP camera defaults, storage array defaults — sourced from publicly-documented vendor docs). Select via `-m mode:default|extended|full` (default = `default`). Embed via `go:embed` in `modules/wordlist.go` alongside existing embedded lists. - -**Inline cred pairs** — `brutespray/dispatch.go` already handles `-C` combo files. Add `-c, --creds` that accepts comma-separated `user:pass` strings: `--creds 'admin:admin,root:toor'`. Splits → builds the same in-memory credential list a combo file would yield. Reuses existing `sanitizeCred` and PwDump auto-detect path off (these are explicit pairs). - -### Phase D — Documentation + comparison table - -**README** — add a positioning table after the existing feature list. Sketch: - -| Feature | brutespray | hydra | medusa | ncrack | brutus | -|---|---|---|---|---|---| -| Single static binary | ✅ | ❌ | ❌ | ❌ | ✅ | -| Interactive TUI | ✅ | ❌ | ❌ | ❌ | ❌ | -| Checkpoint / resume | ✅ | ❌ | ❌ | ✅ | ❌ | -| Spray mode (lockout-aware) | ✅ | ❌ | ❌ | ❌ | ❌ | -| Per-attempt JSONL | ✅ | ⚠️ | ❌ | ❌ | ❌ (success-only) | -| SOCKS5 + proxy rotation | ✅ | ⚠️ | ❌ | ❌ | ❌ | -| Embedded SSH bad-keys | ✅ (new) | ❌ | ❌ | ❌ | ✅ | -| Pipeline stdin (naabu/fingerprintx/masscan) | ✅ (new) | ❌ | ❌ | ❌ | ✅ | -| Pre-auth RDP recon (NLA / sticky-keys) | ✅ (new) | ❌ | ❌ | ❌ | ✅ | -| Nmap gnmap+XML / Nessus / Nexpose import | ✅ | ⚠️ | ❌ | ❌ | ⚠️ (nmap only) | -| Module params (`-m KEY:VAL`) | ✅ | ❌ | ❌ | ❌ | partial | -| Service count | 41 (after this PR) | 50+ | 34 | 14 | 23 | - -(Final symbols verified against tool docs at PR time, not from memory.) - -**docs/** sweep — following the existing `docs/wordlists.md` / `docs/services.md` style: -- `docs/services.md`: add neo4j, cassandra, couchdb, elasticsearch, influxdb rows. -- `docs/advanced.md`: new sections for SSH bad-keys (with CVE table), RDP pre-auth recon, stdin pipeline integration with `naabu | fingerprintx | brutespray` example. -- `docs/output.md`: document new `Finding` and `KeyMatch` JSONL fields. -- `docs/wordlists.md`: SNMP tiering description. -- `docs/usage.md`: `--no-badkeys`, `--badkeys-only`, `--no-rdp-scan`, `-c/--creds` inline pairs, stdin auto-detect. -- New `docs/pipeline.md` walking through the recon workflow end-to-end. - -**CLAUDE.md** — updated locally to reflect new modules, flags, and findings shape. Per memory policy `[[feedback_no_claude_md]]`, **not committed**. - -## Critical files to modify - -- `brute/ssh.go` — bad-keys integration -- `brute/rdp.go` — pre-auth NLA + sticky-keys recon -- `brute/snmp.go` — tier selection -- `brute/run.go` — `BruteResult` extension with `Finding` and `KeyMatch` -- `brute/registry.go` — register 5 new modules -- `brute/badkeys/` (new package) — embedded ssh-badkeys bundle -- `brute/{neo4j,cassandra,couchdb,elasticsearch,influxdb}.go` (new) — each follows the existing module pattern; tests follow `brute/redis_test.go` / `brute/postgres_test.go` shape. `wordlist/{neo4j,influxdb}` already exist with user+password files; `wordlist/{cassandra,couchdb,elasticsearch}` are empty placeholders that get populated as part of this PR. -- `../grdp/client/` — add `CaptureLogonScreen` and `LogonTrigger` types (sibling repo, owned) -- `brutespray/config.go` — new flags (`--no-badkeys`, `--badkeys-only`, `--no-rdp-scan`, `-c/--creds`); stable/beta service lists -- `brutespray/dispatch.go` — inline-pairs expansion -- `brutespray/brutespray.go` — stdin pipeline detection at `Execute()` entry -- `modules/parse.go` — dispatch to new parsers -- `modules/parse_stream.go` (new) — stdin format auto-detection -- `modules/parse_masscan.go` (new) — masscan JSON parser -- `modules/output.go` — render `Finding` / `KeyMatch` in text and JSONL -- `modules/wordlist.go` — embed snmp_default/extended/full -- `tui/` — new findings tab; reuse existing tab pattern in `tui/view_*.go` -- `README.md` — comparison table -- `docs/*.md` — per-feature documentation - -## Reused utilities - -- `Register()` in `brute/registry.go` for new modules -- `ConnectionManager.Dial()` in `modules/connections.go` for all network I/O — gets SOCKS5/proxy/iface for free -- `sanitizeCred()` for text-protocol creds (couchdb, elasticsearch, influxdb) -- `time.NewTimer` + goroutine + `select` timeout pattern (CLAUDE.md mandate) -- `go:embed` infrastructure already used in `modules/wordlist.go` -- `x90skysn3k/grdp` already-imported RDP library for sticky-keys probe -- Existing TUI tabbed `Model` in `tui/` — extend, don't refactor - -## Verification - -**Unit:** new tests for each new module (`brute/neo4j_test.go` etc.) mirroring the docker-based pattern in `brute/redis_test.go`. Bad-keys metadata parsing tested against the vendored YAML. Stdin format auto-detect tested with table-driven fixtures (`modules/parse_stream_test.go`). - -**Integration:** spin docker containers for neo4j, cassandra, couchdb, elasticsearch, influxdb and run `brutespray` end-to-end with a known-good credential. Reuse the existing docker harness referenced in commit `f162ae9`. - -**Pipeline:** `naabu -host 127.0.0.1 -p 22 -silent | ./brutespray -u root -P wordlist/ssh.txt` against a local SSH container — confirm stdin auto-detect, confirm bad-keys phase fires. - -**RDP recon:** stand up a Windows RDP container with NLA off and sticky-keys backdoor (cmd.exe in place of sethc.exe) and confirm both findings emit. Repeat with NLA on / no backdoor — confirm clean output. - -**Comparison table:** verify each ✅/❌/⚠️ against the named tool's current documentation (not from memory) at PR time. - -**Regression:** `go test ./... -race` clean; existing 106 tests still pass; TUI smoke test (`./brutespray -H 127.0.0.1 -s ssh -u root -p test`). - -**Lint:** `golangci-lint run` clean. - -## Documentation deliverables - -Per the user instruction to update documentation for every new feature: - -- README.md — comparison table, mention of new features in feature list, stdin pipeline example near the top -- docs/services.md — 5 new module rows -- docs/usage.md — new flags -- docs/advanced.md — bad-keys CVE table, RDP recon details -- docs/output.md — Finding / KeyMatch JSONL schema -- docs/wordlists.md — SNMP tiering -- docs/pipeline.md (new) — end-to-end recon workflow walk-through -- CLAUDE.md (local only, not committed) — module patterns and flags - -## Out of band - -The pre-existing `dev` branch has scratch artifacts in the working tree (`brutespray-checkpoint.jsonl`, `brutespray-improved`, `brutespray-intelligent`, `brutespray-test`, `medusa/`, `thc-hydra/`, `test-output/`, etc.) per `git status`. These are ignored for this spec — work proceeds from a clean staging area; nothing in this spec touches them. From 725f639116befe58406fbbc0aac970ebb11ec4c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 May 2026 16:12:25 +0000 Subject: [PATCH 49/49] chore(deps): bump golang.org/x/net from 0.53.0 to 0.55.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.53.0 to 0.55.0. - [Commits](https://github.com/golang/net/compare/v0.53.0...v0.55.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.55.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 10 +++++----- go.sum | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 4e449b8..f5bed86 100644 --- a/go.mod +++ b/go.mod @@ -25,9 +25,9 @@ require ( github.com/sijms/go-ora/v2 v2.9.0 github.com/x90skysn3k/grdp v1.0.3 go.mongodb.org/mongo-driver v1.17.9 - golang.org/x/crypto v0.50.0 - golang.org/x/net v0.53.0 - golang.org/x/term v0.42.0 + golang.org/x/crypto v0.51.0 + golang.org/x/net v0.55.0 + golang.org/x/term v0.43.0 gopkg.in/yaml.v3 v3.0.1 gosrc.io/xmpp v0.5.1 ) @@ -92,8 +92,8 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect gopkg.in/inf.v0 v0.9.1 // indirect nhooyr.io/websocket v1.8.17 // indirect diff --git a/go.sum b/go.sum index c405d55..70c121f 100644 --- a/go.sum +++ b/go.sum @@ -309,8 +309,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -328,8 +328,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -360,15 +360,15 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -376,8 +376,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=