From a5cdd32a0a3435f2ebb7a1d6af9662927d5d4fef Mon Sep 17 00:00:00 2001 From: CyberSecurityUP Date: Thu, 30 Jul 2026 16:20:58 -0300 Subject: [PATCH] feat: account registration, form analysis, credential vault + cleanup (v3.6.5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New agent account_registration_and_forms (+1 → 430): analyzes the app's forms and self-registers a benign test account (curl or Playwright) to reach the authenticated surface when no creds are given. - Probe extracts form details (action/method/fields/kind/CSRF) so form analysis is grounded; shown in the probe summary and recon JSON. - Hard anti-flood guardrail in SAFETY_DOCTRINE + the agent: at most 2 accounts per engagement, never loop/script/batch the register endpoint or flood the DB; reuse the account made; a test needing many sign-ups is a lead, not mass-creation. - Credential vault: engagement_ops directive tells agents to append created accounts to /vault.jsonl; finish() consolidates to vault.json, masks secrets in the report, and adds a 'Test accounts created (DELETE after)' cleanup finding listing each account and how it was created. - Finding tagging: new auth_context (authenticated/unauthenticated) and account fields, rendered per-finding in the HTML report. - Opt-in disposable email (off by default): /tempmail on + RunConfig.temp_email; agents may use the free mail.tm API to read a registration confirmation code. - Tests: parse_forms unit tests; docs updated (README/TUTORIAL/RELEASE), counts 430. --- README.md | 12 +- RELEASE.md | 22 +++ TUTORIAL.md | 45 +++++- .../vulns/account_registration_and_forms.md | 43 ++++++ neurosploit-rs/app/src/repl.rs | 19 ++- neurosploit-rs/crates/harness/src/pipeline.rs | 145 +++++++++++++++++- neurosploit-rs/crates/harness/src/probe.rs | 120 ++++++++++++++- neurosploit-rs/crates/harness/src/report.rs | 11 +- neurosploit-rs/crates/harness/src/types.rs | 23 +++ 9 files changed, 420 insertions(+), 20 deletions(-) create mode 100644 agents_md/vulns/account_registration_and_forms.md diff --git a/README.md b/README.md index 29ca383..bd742fa 100755 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - + @@ -38,7 +38,13 @@ > runs an attacker→**LLM-judge** loop — capture the baseline refusal, apply the > technique across variants, judge whether the guardrail was truly bypassed — > proving it with a **benign, redacted** receipt. `neurosploit aitest `. -> Also adds **Claude Opus 5**, **Claude Sonnet 5**, and **Kimi K3** (new Moonshot +> Also: **self-service test-account registration** — analyzes the app's forms and +> creates a benign account (curl or Playwright) to reach the **authenticated** +> surface, with a hard **anti-flood guardrail** (≤2 accounts), a per-run +> **credential vault** (`vault.json`, secrets masked in the report), a **"delete +> these accounts" cleanup** section, findings tagged **authenticated / +> unauthenticated**, and **opt-in disposable email** (`/tempmail`, free mail.tm). +> New models: **Claude Opus 5**, **Claude Sonnet 5**, **Kimi K3** (new Moonshot > provider → 15 providers). Full history in [RELEASE.md](RELEASE.md). --- @@ -49,7 +55,7 @@ LLMs** — via **API key** or local **subscription** (Claude Code / Codex / Gemi Grok) — recons the target, **intelligently selects only the agents that match the discovered surface**, runs them in parallel, **chains** findings into deeper impact, and **validates every claim by cross-model voting + tool-receipt -grounding** before reporting. It ships **429 markdown agents** and a **Mission +grounding** before reporting. It ships **430 markdown agents** and a **Mission Control TUI**. ### Engagement modes diff --git a/RELEASE.md b/RELEASE.md index 7507012..f5c255f 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -37,6 +37,28 @@ as a finder or in the validator voting panel, e.g. `--model anthropic:claude-opus-5 --model moonshot:kimi-k3`. +- **Account registration & form analysis (+1 agent → total 430).** A new + `account_registration_and_forms` agent lets NeuroSploit reach the authenticated + surface on its own: it analyzes the app's forms (the deterministic probe now + extracts each `
`'s action/method/fields/kind/CSRF) and creates a benign + test account with **curl** or the **Playwright browser** when no creds are given. + - **Anti-flood guardrail (hard):** at most **2 accounts per engagement**, never + looping/scripting/batching the register endpoint or flooding the database — + reuse the account made; a test needing many sign-ups is reported as a lead and + stopped. Enforced in `SAFETY_DOCTRINE` (all flows) and the agent. + - **Credential vault:** every generated credential is saved to + **`/vault.json`** for the operator to consult; secrets are **masked in + the report**. The report adds a **"Test accounts created (DELETE after)"** + cleanup section listing each account and how it was created. + - **Finding labels:** findings are tagged **`auth_context`** + (authenticated/unauthenticated) and **`account`** (which test user/role proved + it) — so grey-box shows which findings needed a login, and black-box records how + the user was created. + - **Disposable email (opt-in, off by default):** `/tempmail on` (or `temp_email`) + lets agents use the free **mail.tm** API (no key) to read a registration + confirmation code; off by default, a required confirmation is reported as a + blocker rather than bypassed. + ## Previously in v3.6.4 diff --git a/TUTORIAL.md b/TUTORIAL.md index e5b3a8e..4e6c12b 100644 --- a/TUTORIAL.md +++ b/TUTORIAL.md @@ -40,7 +40,7 @@ You give NeuroSploit a **target** (URL, repo, app, or host/IP). It: 1. **Recons** the target with real tools (curl/nmap/…). 2. **Intelligently selects** only the agents whose preconditions match the recon - (it does *not* blindly run all 429). + (it does *not* blindly run all 430). 3. **Exploits** in parallel — each agent works in a ReAct loop and must prove its claim with a **tool receipt** (raw output). 4. **Validates** every candidate by **cross-model voting** (a different model @@ -99,7 +99,7 @@ Agents **degrade gracefully**: if `rustscan` is absent they use `nmap`; if neith ```bash neurosploit --version # neurosploit 3.6.5 -neurosploit agents # {"vulns":240,...,"ai":30,...,"total":429} +neurosploit agents # {"vulns":241,...,"ai":30,...,"total":430} neurosploit models # all providers & models ``` @@ -311,6 +311,42 @@ execution. All AI testing is **authorized, non-destructive** — demonstrations stay benign and redacted; the goal is to prove the guardrail bypass, not to cause harm. +### 5.6 Test accounts, form analysis & the credential vault + +To reach the high-impact **authenticated** surface, NeuroSploit can **analyze the +app's forms and create its own test account** when you don't supply credentials — +with **curl** (plain HTML/API forms: GET for CSRF+cookies, then POST) or the +**Playwright browser** (JS-rendered / multi-step forms, e.g. Juice Shop). The +deterministic probe now extracts each ``'s action/method/fields/kind, so the +agents know exactly what to submit. + +- **Anti-flood guardrail (hard):** at most **2 accounts per engagement** (1 user; a + 2nd only when a test needs two users, e.g. horizontal IDOR). Agents never loop / + script / batch the register endpoint or flood the database; they reuse the + account they made. A test that would need many sign-ups is reported as a lead and + stopped. +- **Credential vault:** every account/credential the run generates is written to + **`/vault.json`** so you can consult the passwords later. Secrets are + **masked in the report** and live only in the vault. +- **Cleanup list:** the report includes an Info finding **"Test accounts created + (DELETE after)"** listing each account and exactly **how it was created** — so you + can remove them when done. +- **Finding labels:** every finding is tagged **`Auth: authenticated`** / + **`unauthenticated`** and **`Account:`** (which test user/role proved it) — so in + grey-box you see which findings needed a login, and in black-box you see what the + agent did to create the user. +- **Disposable email (opt-in, off by default):** if registration requires an email + confirmation code, enable **`/tempmail on`** (REPL) — agents may then use the free + **mail.tm** API (no key) to create a throwaway inbox and read the code. Off by + default: a required confirmation is otherwise reported as a blocker, not bypassed. + +``` +neurosploit› /target http://localhost:3001 # e.g. a local Juice Shop +neurosploit› /tempmail on # only if signup needs email confirmation +neurosploit› /run # analyzes forms, self-registers, tests authenticated +neurosploit› /report # see the vault-backed "Test accounts (DELETE after)" section +``` + --- ## 6. The interactive REPL @@ -332,6 +368,7 @@ A context bar shows `model auth · cwd · mode▸target`. Key commands: /focus steer the tests (or just type the instruction) @path @dir @f:1-20 attach a file/folder/line-range to context (Tab → menu) /mcp on|off /offline on|off /votes /agents /theme color|mono +/tempmail on|off opt-in disposable inbox (mail.tm) for a register confirmation code /run launch the engagement /runs /results [n] /report [n] /status [n] /diff what changed vs the previous run @@ -581,11 +618,11 @@ built from SAST/dataflow), so uncertainty becomes *path reachability*, not state ## 13. The agent library -`agents_md/` holds **429** markdown agents in categories: +`agents_md/` holds **430** markdown agents in categories: | Category | Dir | Count | Purpose | |----------|-----|-------|---------| -| Vulnerability specialists | `vulns/` | 240 | exploit a specific class | +| Vulnerability specialists | `vulns/` | 241 | exploit a specific class · incl. account registration & form analysis | | Recon | `recon/` | 12 | information gathering | | Code (SAST) | `code/` | 78 | white-box source review | | Infra | `infra/` | 34 | Linux / Windows / AD host testing | diff --git a/agents_md/vulns/account_registration_and_forms.md b/agents_md/vulns/account_registration_and_forms.md new file mode 100644 index 0000000..c020329 --- /dev/null +++ b/agents_md/vulns/account_registration_and_forms.md @@ -0,0 +1,43 @@ +# Account Registration & Form Analysis Agent +## User Prompt +You are testing **{target}**. Your job: ANALYZE the app's forms and, when no credentials were provided, CREATE a legitimate test account so the rest of the engagement can test the AUTHENTICATED surface. Authorized, non-destructive. + +**Recon Context (includes `form_details`: action/method/fields/kind/has_csrf):** +{recon_json} + +**METHODOLOGY:** + +### 1. Analyze every form +- From the probe's `form_details` (and by fetching the page), map each ``: its `action`, `method`, every input `name`/`type`, hidden fields, and any CSRF/anti-forgery token. +- Classify each form: **register / login / search / password-reset / other**. Note required fields (email, username, password, confirm-password, phone, DOB, security question), client-side validation, and the exact POST body shape (`application/x-www-form-urlencoded` vs `application/json`). +- For SPAs (Angular/React/Vue — e.g. Juice Shop) the register/login form posts to a JSON REST endpoint (e.g. `POST /api/Users`, `/rest/user/login`). Discover it from the network calls (browser/MCP) or JS, not just the HTML. + +### 2. Register a test account +- Prefer **curl** for a plain HTML/API form: GET the form first to collect any CSRF token + cookies, then POST the fields. Use a clearly-marked, unique, benign identity — e.g. `nrsplt_@example.test` / username `nrsplt_` / a strong throwaway password. Satisfy validation (matching confirm-password, valid email format, required security question/answer). +- Use the **browser (Playwright MCP)** when the form is JS-rendered / multi-step / has client-side validation or captcha-like flow: navigate, fill fields, submit, and read the result. +- Honor server rules: one account is enough. Do NOT mass-register, brute-force, or spam. If self-registration is disabled, say so and stop (report it as an observation, not a vuln). + +### 3. Verify & capture the session +- Confirm the account exists: log in with it and capture the auth material (Set-Cookie session, JWT/Bearer, CSRF token). Show the exact request + the success response as the receipt. +- Hand the working session forward so authenticated agents (IDOR, access-control, authenticated_surface_exploit, business-logic) can reuse it. Register a SECOND account when a test needs two users (horizontal IDOR). + +### 4. Probe the registration/login logic itself (report real issues only) +- Mass-assignment / privilege escalation at signup: add unexpected fields (`role=admin`, `isAdmin=true`, `type`, `group`) to the register request and check if the server accepts them → account created with elevated role. +- Weak password policy, username/email enumeration (different response for existing vs new), missing rate-limiting on register/login, verbose validation errors, and no email verification when the app implies it. +- CSRF on register/login if no token is required. + +### 5. Report +``` +FINDING: +- Title: [e.g. "Mass-assignment at registration grants admin role" / "Test account self-registration (capability used for authenticated testing)"] +- Severity: [High for privesc/mass-assignment; Info for a benign account created as a testing capability] +- CWE: [CWE-915 mass-assignment / CWE-306 / CWE-620 / CWE-352 as applicable] +- Endpoint: [register/login endpoint] +- Payload: [exact request that created/escalated the account] +- Evidence: [request + response proving the account exists / the role was set] +- Impact: [what the flaw allows] +- Remediation: [allow-list bindable fields; server-set roles; verify email; rate-limit; strong password policy; CSRF tokens] +``` + +## System Prompt +You are an account-provisioning and form-analysis specialist on an AUTHORIZED, non-destructive engagement. Your primary goal is enabling authenticated testing: analyze the target's forms (curl for plain HTML/API forms, the Playwright MCP browser for JS-rendered/multi-step ones), then create ONE clearly-marked benign test account (`nrsplt_*@example.test`) and capture a working session to reuse. HARD GUARDRAIL: create AT MOST 2 accounts for the whole engagement (1 user; a 2nd only if a test needs two users), and REUSE them — never loop/script/batch/fuzz the register endpoint or flood the database with sign-ups. To test the register endpoint itself, send only a few controlled requests. If a test would need many registrations, report it as a lead and stop. If self-registration is disabled, report that as an observation and stop. Separately, report GENUINE registration/login flaws (mass-assignment/privilege escalation, missing rate-limit, user enumeration, CSRF, weak policy) only when proven with a real request+response receipt. A created test account is reported as an Info capability, not a vulnerability. Credits: Joas A Santos and Red Team Leaders. diff --git a/neurosploit-rs/app/src/repl.rs b/neurosploit-rs/app/src/repl.rs index cf8452b..0446fe9 100644 --- a/neurosploit-rs/app/src/repl.rs +++ b/neurosploit-rs/app/src/repl.rs @@ -142,7 +142,7 @@ struct LiveCheckpoint { const COMMANDS: &[&str] = &[ "/help", "/onboard", "/show", "/config", "/providers", "/model", "/key", "/sub", "/target", "/repo", "/auth", "/creds", "/focus", "/attach", "/context", "/mcp", "/offline", - "/votes", "/chain", "/recon", "/timeout", "/proxy", "/burp", "/ua", "/agents", "/theme", "/clear", "/run", "/stop", "/continue", "/runs", "/results", "/report", + "/votes", "/chain", "/recon", "/tempmail", "/timeout", "/proxy", "/burp", "/ua", "/agents", "/theme", "/clear", "/run", "/stop", "/continue", "/runs", "/results", "/report", "/status", "/logs", "/diff", "/retest", "/validate", "/finding", "/expand", "/integrations", "/quit", ]; @@ -238,6 +238,8 @@ struct Session { max_agents: usize, chain_depth: usize, recon_intensity: usize, + /// Opt-in disposable email (mail.tm) for register flows needing a confirmation code. + temp_email: bool, /// Idle guardrail: stop a run if no NEW finding lands in this many seconds /// (0 = disabled). Set in minutes via `/timeout `. idle_secs: u64, @@ -269,6 +271,7 @@ impl Default for Session { max_agents: 0, chain_depth: 2, recon_intensity: 3, + temp_email: false, idle_secs: 300, // 5-minute idle guardrail by default proxy: None, user_agent: None, @@ -602,6 +605,13 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> { if arg.is_empty() { println!(" recon intensity: {} ({}) — set with /recon <1-4> [1 quick · 2 standard · 3 deep · 4 exhaustive]", s.recon_intensity, lvl(s.recon_intensity)); } else { s.recon_intensity = arg.parse::().unwrap_or(s.recon_intensity).clamp(1, 4); println!(" recon intensity: {} ({}) — more rounds, more enumeration, auto-installs tools", s.recon_intensity, lvl(s.recon_intensity)); } } + "/tempmail" | "/temp-email" => { + match arg.trim() { + "on" | "true" | "1" => { s.temp_email = true; println!(" temp-email: \x1b[32mon\x1b[0m — register flows may use the free mail.tm inbox to read a confirmation code"); } + "off" | "false" | "0" => { s.temp_email = false; println!(" temp-email: \x1b[2moff\x1b[0m — a register step that requires email confirmation is reported as a blocker"); } + _ => println!(" temp-email: {} — /tempmail on|off (opt-in disposable inbox for register confirmation)", if s.temp_email { "\x1b[32mon\x1b[0m" } else { "\x1b[2moff\x1b[0m" }), + } + } "/agents" => { if arg == "list" || arg == "ls" { let lib = agents::load(base); @@ -1028,6 +1038,7 @@ async fn run(base: &Path, s: &Session, history: &mut Vec) { cfg.vote_n = s.vote_n; cfg.chain_depth = s.chain_depth; cfg.recon_intensity = s.recon_intensity; + cfg.temp_email = s.temp_email; cfg.proxy = s.proxy.clone(); cfg.user_agent = s.user_agent.clone(); cfg.max_agents = s.max_agents; @@ -1105,6 +1116,7 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader, cfg.vote_n = s.vote_n; cfg.chain_depth = s.chain_depth; cfg.recon_intensity = s.recon_intensity; + cfg.temp_email = s.temp_email; cfg.proxy = s.proxy.clone(); cfg.user_agent = s.user_agent.clone(); cfg.max_agents = s.max_agents; @@ -1599,9 +1611,9 @@ fn show(s: &Session) { println!(" │ proxy : {}", s.proxy.clone().unwrap_or_else(|| "(none — /proxy for Burp/ZAP)".into())); println!(" │ user-agent: {}", s.user_agent.clone().unwrap_or_else(|| "NeuroSploit (default)".into())); println!(" │ focus : {}", s.instructions.clone().unwrap_or_else(|| "(none — tests everything)".into())); - println!(" │ opts : mcp={} offline={} votes={} recon={} chain-depth={} max-agents={} idle-stop={}", + println!(" │ opts : mcp={} offline={} votes={} recon={} chain-depth={} max-agents={} idle-stop={} temp-email={}", onoff(s.mcp), onoff(s.offline), s.vote_n, s.recon_intensity, s.chain_depth, s.max_agents, - if s.idle_secs == 0 { "off".to_string() } else { format!("{}m", s.idle_secs / 60) }); + if s.idle_secs == 0 { "off".to_string() } else { format!("{}m", s.idle_secs / 60) }, onoff(s.temp_email)); // Integrations at a glance (see /integrations for detail). { let ig = harness::integrations::Integrations::load(&proj_dir()); @@ -1669,6 +1681,7 @@ fn help() { h("/votes ", "number of validator votes per finding"); h("/chain ", "attack-chain depth (post-exploitation pivots; 0 = off)"); h("/recon <1-4>", "recon intensity: 1 quick · 2 standard · 3 deep · 4 exhaustive (installs tools)"); + h("/tempmail on|off", "opt-in disposable inbox (mail.tm) to read a register confirmation code"); h("/timeout ", "idle guardrail: stop if no new finding in (0 = off)"); h("/proxy |off", "route agent HTTP through Burp/ZAP (/burp = default :8080)"); h("/ua ", "identifying User-Agent for NeuroSploit traffic (default = NeuroSploit)"); diff --git a/neurosploit-rs/crates/harness/src/pipeline.rs b/neurosploit-rs/crates/harness/src/pipeline.rs index 6cd7b56..1813bd9 100644 --- a/neurosploit-rs/crates/harness/src/pipeline.rs +++ b/neurosploit-rs/crates/harness/src/pipeline.rs @@ -4,7 +4,7 @@ use crate::rl::{severity_reward, RlState}; use crate::types::{Finding, RunConfig}; use crate::report; use futures::stream::{self, StreamExt}; -use serde::Serialize; +use serde::{Serialize, Deserialize}; use std::path::{Path, PathBuf}; use tokio::sync::mpsc::Sender; @@ -181,7 +181,80 @@ operator has explicitly authorized that specific action. Read-only, minimal proo emails, CPF/SSN, phones, cards, tokens): confirm access with the SMALLEST possible sample and REDACT it in the \ report (e.g. show 1 masked record + a count) — never dump, store, or transmit the dataset. Prefer benign markers \ and OOB/echo checks over any state-changing payload. When unsure whether an action is safe, don't do it — report \ -it as reachable and stop.\n\n"; +it as reachable and stop.\n\ +ACCOUNT-CREATION GUARDRAIL (hard limit): creating accounts is state-changing — do it ONLY to enable authenticated \ +testing, and create AT MOST 2 accounts for the ENTIRE engagement (1 normal user; a 2nd only when a test genuinely \ +needs two users, e.g. horizontal IDOR). NEVER loop, script, fuzz, or batch the register endpoint; do not write a \ +tool/PoC that submits it repeatedly; do not flood or stress the database with sign-ups. Each account must be a \ +single, clearly-marked benign identity (`nrsplt_@example.test`). REUSE the account you already made instead of \ +making new ones. To TEST the register endpoint itself (rate-limit, mass-assignment, enumeration, CSRF, weak policy), \ +send only a FEW controlled requests and prove the flaw from those — never a high-volume run. If a test would require \ +many registrations, report it as a LEAD and STOP rather than mass-creating accounts.\n\n"; +/// Per-run operational directive: the credential VAULT path, the account cap, +/// finding-tagging rules, and (opt-in) disposable-email use. Injected into web +/// engagement prompts so created accounts are logged for cleanup and findings +/// are labelled authenticated vs unauthenticated. +fn engagement_ops(cfg: &RunConfig) -> String { + let vault = cfg.workdir.as_deref() + .map(|d| format!("{}/vault.jsonl", d.trim_end_matches('/'))) + .unwrap_or_else(|| "the run directory's vault.jsonl".into()); + let temp = if cfg.temp_email { + "DISPOSABLE EMAIL (enabled): if registration requires an email confirmation code/link, you MAY use the free \ + mail.tm API (no key) — `POST https://api.mail.tm/accounts` {address,password} to create an inbox (get a \ + valid domain from `GET https://api.mail.tm/domains`), `POST https://api.mail.tm/token` for a JWT, then poll \ + `GET https://api.mail.tm/messages` (Bearer JWT) to read the confirmation code/link. Use the mail.tm address \ + as the account email. Guerrilla Mail's API is a fallback. " + } else { + "DISPOSABLE EMAIL (disabled): if registration REQUIRES an email confirmation you cannot receive, stop and \ + report it as a blocker (do not attempt to bypass it). " + }; + format!( + "ENGAGEMENT OPS — TEST ACCOUNTS & VAULT:\n\ + - CREDENTIAL VAULT: whenever you create a test account or generate any credential, APPEND one JSON line to \ + `{vault}` (create the file if missing) of the form \ + {{\"account\":\"\",\"secret\":\"\",\"role\":\"\",\"endpoint\":\"\",\"how\":\"\",\"auth_flow\":\"\"}}. \ + This vault is the single place secrets are stored so you (and the operator) can consult them later; also \ + set the finding's `secret` field so it is captured even if the file write fails.\n\ + - CLEANUP: every account you create MUST be reported so it can be deleted afterwards — the run report lists \ + them from the vault. Do not leave undocumented accounts.\n\ + - LABEL FINDINGS: set `auth_context` to \"authenticated\" (proven while logged in with a test/given account) \ + or \"unauthenticated\" (proven with no session), and `account` to which user/role you used. In grey-box, be \ + explicit about which findings needed a login. In black-box, record in `how`/evidence exactly what you did \ + to create the user.\n\ + - {temp}\n\n" + ) +} +/// One credential the run generated (a created test account). Stored in the run +/// vault so the operator can consult it and later delete the account. +#[derive(Serialize, Deserialize, Default, Clone)] +struct VaultEntry { + #[serde(default)] account: String, + #[serde(default)] secret: String, + #[serde(default)] role: String, + #[serde(default)] endpoint: String, + #[serde(default)] how: String, + #[serde(default)] auth_flow: String, +} + +/// Read the agent-appended `vault.jsonl` (one JSON object per created account) +/// from the run dir. Best-effort: skips malformed lines. `_findings` reserved for +/// future correlation. Deduped by account identity. +fn collect_vault(dir: &str, _findings: &[Finding]) -> Vec { + let path = format!("{}/vault.jsonl", dir.trim_end_matches('/')); + let mut out: Vec = Vec::new(); + if let Ok(txt) = std::fs::read_to_string(&path) { + for line in txt.lines() { + let line = line.trim(); + if line.is_empty() { continue; } + if let Ok(v) = serde_json::from_str::(line) { + if v.account.is_empty() { continue; } + if !out.iter().any(|e| e.account == v.account) { out.push(v); } + } + } + } + out +} + const VOTE_SYS: &str = "You are an adversarial security validator. Decide if the candidate finding is a REAL, reproducible, exploitable vulnerability whose EVIDENCE actually proves impact. Reject common false positives: input merely reflected but not executed; version/banner guesses with no working PoC; self-XSS; theoretical issues; an error message or stack trace mistaken for injection; missing, generic, or non-reproducible evidence; severity inflated beyond what the evidence demonstrates. Confirm only if the provided evidence (request/response) concretely proves the vulnerability. Reply with JSON {\"verdict\":\"confirmed\"|\"rejected\",\"reason\":\"...\"}. Default to rejected when uncertain."; /// Adversarial second pass for High/Critical findings: assume false positive /// until the evidence forces otherwise. A finding that can't withstand the @@ -214,6 +287,7 @@ const DECISION_DOCTRINE: &str = "DECIDE WHERE TO ATTACK (analyse, then act):\n\ - Mine PARAMETERS: enumerate query/body/header/cookie params (incl. hidden ones from JS/source maps); for each, reason about what it does and test the fitting attack (IDOR, injection, path traversal, mass-assignment, open-redirect, SSRF). Add plausible params the API might accept (id, user, role, admin, debug, redirect, file, callback).\n\ - MOCK realistic data: when a request needs valid-looking input to reach deeper logic, synthesize believable test data (emails, names, CPFs/SSNs with valid checksums, phone numbers, UUIDs, tokens, JSON bodies) so the flow proceeds — never use real PII.\n\ - Authenticated testing: if you can authenticate (given creds/roles or a login you performed), REUSE the session and exploit the AUTHENTICATED surface — the endpoints/params only reachable while logged in are where the high-impact bugs live. Test as EACH role you have (e.g. normal user AND admin) and compare.\n\ +- Self-register when no creds are given: if the app allows sign-up, ANALYZE the register form (probe `form_details` has action/method/fields) and CREATE one clearly-marked benign test account (`nrsplt_@example.test`) — with curl (GET for CSRF+cookies, then POST the fields) or the Playwright browser for JS-rendered/multi-step forms — then log in and REUSE that session for authenticated testing. Register a second account only when a test needs two users. Non-destructive: one account, no mass-registration/spam; at signup also try mass-assignment (`role=admin`/`isAdmin`) and report it if accepted.\n\ - Build PoCs when needed: for issues that need an artifact to prove (clickjacking → an HTML page that frames the target; CSRF → an auto-submitting HTML form; a multi-step or timing exploit → a script), WRITE the PoC to the run's PoC dir, run/validate it, and cite the file in the evidence.\n\ - Test control BYPASSES: when something returns 401/403/redirect or is 'blocked', try to bypass it (verb tampering, path/case/encoding normalization, X-Original-URL / X-Rewrite-URL / X-Forwarded-* headers, missing-vs-invalid token, direct object/API access) and confirm the bypass with the two requests.\n\n"; @@ -294,6 +368,7 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender)> = stream::iter(selected.iter().cloned()) @@ -301,6 +376,7 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender)> = stream::iter(selected.iter().cloned()) @@ -523,6 +603,7 @@ pub async fn run_greybox(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Se let target = target.clone(); let recon = recon_ctx.clone(); let directives = directives.clone(); + let ops = ops.clone(); let leads = leads_ctx.clone(); let txc = tx.clone(); async move { @@ -534,11 +615,12 @@ pub async fn run_greybox(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Se } let user = format!( "AUTHORIZED greybox engagement on {target} — you also have the source review below. \ - Proceed and PROVE each issue against the LIVE app.\n\n{directives}{leads}{react}{depth}{decision}{safety}{doctrine}{body}\n\n\ + Proceed and PROVE each issue against the LIVE app.\n\n{directives}{leads}{react}{depth}{decision}{safety}{ops}{doctrine}{body}\n\n\ Reply ONLY a JSON array of confirmed findings (may be []): \ - {{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}.", + {{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence,auth_context,account,secret}}. \ + Set `auth_context` (authenticated/unauthenticated) and `account` (test user/role used); for a created account set `secret`.", target = target, directives = directives, leads = leads, - react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, decision = DECISION_DOCTRINE, safety = SAFETY_DOCTRINE, doctrine = tool_doctrine(mcp_on), + react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, decision = DECISION_DOCTRINE, safety = SAFETY_DOCTRINE, ops = ops, doctrine = tool_doctrine(mcp_on), body = ag.user.replace("{target}", &target).replace("{recon_json}", &recon), ); match pool.complete_routed(Task::Exploit, &ag.name, &ag.system, &user).await { @@ -935,6 +1017,53 @@ async fn finish(cfg: RunConfig, _lib: &Library, recon: String, transcript: Strin // White-box/skills are symbolic → deterministic belief; grey-box carries source too. let whitebox = matches!(gmode, GroundMode::Symbolic | GroundMode::Either); + // --- Credential vault & test-account cleanup --------------------------- + // Consolidate every test account created this run (from the agent-appended + // vault.jsonl AND any finding that carried a generated `secret`) into a single + // vault.json the operator can consult, then MASK the secret in the report and + // add a cleanup summary listing the accounts to delete. + if let Some(dir) = cfg.workdir.clone() { + let mut vault = collect_vault(&dir, &findings); + // Fold in secrets captured on findings (dedup by account identity). + for f in &findings { + if !f.secret.is_empty() && !f.account.is_empty() + && !vault.iter().any(|v| v.account == f.account) { + vault.push(VaultEntry { + account: f.account.clone(), secret: f.secret.clone(), role: String::new(), + endpoint: f.endpoint.clone(), how: f.payload.clone(), auth_flow: String::new(), + }); + } + } + if !vault.is_empty() { + let path = format!("{}/vault.json", dir.trim_end_matches('/')); + if let Ok(j) = serde_json::to_string_pretty(&vault) { let _ = std::fs::write(&path, j); } + let _ = tx.send(format!( + "notify: 🔐 vault: {} test account(s) saved → {} — DELETE these after the engagement", + vault.len(), path)).await; + // Cleanup summary finding (secrets live only in the vault, masked here). + let list = vault.iter() + .map(|v| format!("• {}{} — created via {}", v.account, + if v.role.is_empty() { String::new() } else { format!(" [{}]", v.role) }, + if v.how.is_empty() { "the registration flow".to_string() } else { v.how.chars().take(160).collect::() })) + .collect::>().join("\n"); + findings.push(Finding { + id: "test-accounts".into(), agent: "account_registration_and_forms".into(), + title: "Test accounts created during the engagement (DELETE after)".into(), + severity: "Info".into(), endpoint: cfg.target.clone(), + evidence: format!("{} account(s) created for authenticated testing. Credentials are in vault.json (not shown here).\n{}", vault.len(), list), + impact: "Operational cleanup: remove these accounts once testing is complete.".into(), + remediation: "Delete the listed test accounts; rotate anything they touched.".into(), + validated: true, confidence: 1.0, auth_context: "n/a".into(), + account: format!("{} test account(s)", vault.len()), + ..Default::default() + }); + } + // Mask any generated secret so it never appears in the human report. + for f in findings.iter_mut() { + if !f.secret.is_empty() { f.secret = "•••• (see vault.json)".into(); } + } + } + // --- v3.5.2 report-hygiene & exploitation-depth pass --- // Calibrate inflated/unproven High-Critical to Medium, flag exposures that // were never exploited ("exposed → exploited"), and advise consolidating diff --git a/neurosploit-rs/crates/harness/src/probe.rs b/neurosploit-rs/crates/harness/src/probe.rs index 4ce5ea2..62e8ae7 100644 --- a/neurosploit-rs/crates/harness/src/probe.rs +++ b/neurosploit-rs/crates/harness/src/probe.rs @@ -47,6 +47,20 @@ pub struct PathHit { pub len: usize, } +/// A parsed HTML `` — enough for an agent to auto-submit it (e.g. register +/// an account) with curl or the browser, without re-parsing the page. +#[derive(Serialize, Default, Clone)] +pub struct FormInfo { + pub action: String, + pub method: String, + /// input/select/textarea field names with their type (name → type). + pub fields: Vec<(String, String)>, + /// Best-effort role guess: "register" | "login" | "search" | "other". + pub kind: String, + /// True if a CSRF/anti-forgery hidden token was seen in the form. + pub has_csrf: bool, +} + #[derive(Serialize, Default)] pub struct Probe { pub url: String, @@ -63,6 +77,9 @@ pub struct Probe { pub cors: Cors, pub scripts: Vec, pub forms: usize, + /// Parsed forms (action/method/fields) so an agent can auto-submit them — + /// e.g. register a test account to reach the authenticated surface. + pub form_details: Vec, pub interesting_paths: Vec, /// Baseline for a random non-existent path (status + body length), so agents /// can tell a real hit from a soft-404 catch-all. @@ -97,6 +114,66 @@ fn between<'a>(s: &'a str, a: &str, b: &str) -> Option<&'a str> { Some(&s[i..j]) } +/// Read one HTML attribute value (double- or single-quoted) from a tag slice. +fn attr(tag: &str, name: &str) -> String { + for q in ["\"", "'"] { + if let Some(v) = between(tag, &format!("{name}={q}"), q) { + return v.trim().to_string(); + } + } + String::new() +} + +/// Best-effort parse of the HTML ``s on a page so agents can auto-submit +/// them (e.g. register a test account) without re-parsing. Non-destructive: this +/// only READS the markup. Bounded to the first few forms. +fn parse_forms(body: &str) -> Vec { + let mut out = Vec::new(); + for chunk in body.split("'. + let head = chunk.split('>').next().unwrap_or(""); + let inner = chunk.split(",