mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-07-13 06:26:33 +02:00
misconfig/CVE/PoC/rate-limit agents, data-safety guardrail, Burp proxy, PoC dir
Agents (+10 → library 375): absurd-misconfig hunters (exposed .git/.env/backups, debug/actuator, default creds, dir listing, ops dashboards, permissive CORS, verbose errors), a CVE Hunter (fingerprint → correlate → safe PoC), a PoC Developer (writes runnable scripts to the run's pocs/), and a Rate-Limit tester. Doctrine (pipeline): - SAFETY_DOCTRINE injected into every exploit/chain/host prompt: no modify/delete/ exfiltrate/state-change without permission; on PII prove with a masked sample + count, never dump. - tool_doctrine adds: smart targeted nuclei (fingerprint-first, -tags/-id, rate/ timeouts), misconfig hunting, rate-limit control checks, authorized tool download (git clone PoC repos / fetch scanners), Burp/ZAP proxy routing, and a per-run PoC workspace. Harness/CLI/REPL: - RunConfig.proxy; spawn_engagement creates <workdir>/pocs and exports NEUROSPLOIT_POCS + NEUROSPLOIT_PROXY (proxy from cfg or the env var). - REPL /proxy <url> and /burp (Session.proxy); /show shows proxy. Docs: README highlights + Cloud/counts (375), RELEASE v3.5.5 sections.
This commit is contained in:
@@ -550,6 +550,19 @@ pub(crate) fn spawn_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, mode:
|
||||
std::fs::create_dir_all(&workdir).ok();
|
||||
cfg.workdir = Some(workdir.display().to_string());
|
||||
cfg.rl_path = Some(base.join("data").join("rl_state_rs.json").display().to_string());
|
||||
// PoC scratch dir: agents write custom exploit scripts here (see doctrine).
|
||||
let pocs = workdir.join("pocs");
|
||||
std::fs::create_dir_all(&pocs).ok();
|
||||
std::env::set_var("NEUROSPLOIT_POCS", pocs.display().to_string());
|
||||
// Local intercepting proxy (Burp/ZAP): agents route HTTP through it. Comes
|
||||
// from cfg.proxy (REPL /proxy) or the NEUROSPLOIT_PROXY env var (CLI).
|
||||
let proxy = cfg.proxy.clone()
|
||||
.or_else(|| std::env::var("NEUROSPLOIT_PROXY").ok())
|
||||
.filter(|p| !p.trim().is_empty());
|
||||
if let Some(p) = proxy {
|
||||
std::env::set_var("NEUROSPLOIT_PROXY", &p);
|
||||
println!(" │ proxy : {p} (traffic routed to Burp/ZAP for inspection)");
|
||||
}
|
||||
write_status(&workdir, "running", &format!("\"target\":{:?}", cfg.target));
|
||||
|
||||
println!(" ┌─ NeuroSploit v3.5.5 · by Joas A Santos & Red Team Leaders");
|
||||
|
||||
@@ -119,7 +119,7 @@ struct LiveCheckpoint {
|
||||
const COMMANDS: &[&str] = &[
|
||||
"/help", "/show", "/config", "/providers", "/model", "/key", "/sub", "/target",
|
||||
"/repo", "/auth", "/creds", "/focus", "/attach", "/context", "/mcp", "/offline",
|
||||
"/votes", "/chain", "/timeout", "/agents", "/theme", "/clear", "/run", "/stop", "/continue", "/runs", "/results", "/report",
|
||||
"/votes", "/chain", "/timeout", "/proxy", "/burp", "/agents", "/theme", "/clear", "/run", "/stop", "/continue", "/runs", "/results", "/report",
|
||||
"/status", "/diff", "/retest", "/integrations", "/quit",
|
||||
];
|
||||
|
||||
@@ -217,6 +217,8 @@ struct Session {
|
||||
/// Idle guardrail: stop a run if no NEW finding lands in this many seconds
|
||||
/// (0 = disabled). Set in minutes via `/timeout <mins>`.
|
||||
idle_secs: u64,
|
||||
/// Local intercepting proxy (Burp/ZAP), e.g. http://127.0.0.1:8080.
|
||||
proxy: Option<String>,
|
||||
offline: bool,
|
||||
target: Option<String>,
|
||||
repo: Option<String>,
|
||||
@@ -237,6 +239,7 @@ impl Default for Session {
|
||||
max_agents: 0,
|
||||
chain_depth: 2,
|
||||
idle_secs: 300, // 5-minute idle guardrail by default
|
||||
proxy: None,
|
||||
offline: false,
|
||||
target: None,
|
||||
repo: None,
|
||||
@@ -438,6 +441,15 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
|
||||
else { println!(" idle guardrail: stop if no new finding in {mins} min"); }
|
||||
}
|
||||
}
|
||||
"/proxy" | "/burp" => {
|
||||
match arg {
|
||||
"" => println!(" proxy: {}", s.proxy.clone().unwrap_or_else(|| "(none) — route traffic to Burp/ZAP with /proxy <url>, e.g. /proxy http://127.0.0.1:8080".into())),
|
||||
"off" | "clear" | "none" => { s.proxy = None; println!(" proxy cleared — traffic goes direct"); }
|
||||
"on" => { s.proxy = Some("http://127.0.0.1:8080".into()); println!(" proxy: http://127.0.0.1:8080 (default Burp) — agents route curl through it"); }
|
||||
u => { let p = if u.starts_with("http") { u.to_string() } else { format!("http://{u}") };
|
||||
s.proxy = Some(p.clone()); println!(" proxy: {p} — agents route HTTP through it so you can inspect/replay in Burp"); }
|
||||
}
|
||||
}
|
||||
"/repo" => {
|
||||
if arg.is_empty() { println!(" repo: {}", s.repo.clone().unwrap_or_else(|| "(none) — set with /repo <path | github-url | owner/repo>, clear with /repo clear".into())); }
|
||||
else if arg == "clear" { s.repo = None; println!(" repo cleared"); }
|
||||
@@ -742,6 +754,7 @@ async fn run(base: &Path, s: &Session, history: &mut Vec<RunRecord>) {
|
||||
cfg.subscription = s.subscription;
|
||||
cfg.vote_n = s.vote_n;
|
||||
cfg.chain_depth = s.chain_depth;
|
||||
cfg.proxy = s.proxy.clone();
|
||||
cfg.max_agents = s.max_agents;
|
||||
cfg.verbose = true;
|
||||
cfg.offline = s.offline;
|
||||
@@ -795,6 +808,7 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
|
||||
cfg.subscription = s.subscription;
|
||||
cfg.vote_n = s.vote_n;
|
||||
cfg.chain_depth = s.chain_depth;
|
||||
cfg.proxy = s.proxy.clone();
|
||||
cfg.max_agents = s.max_agents;
|
||||
cfg.verbose = true;
|
||||
cfg.offline = s.offline;
|
||||
@@ -1228,6 +1242,7 @@ fn show(s: &Session) {
|
||||
println!(" │ repo : {}", s.repo.clone().unwrap_or_else(|| "(none)".into()));
|
||||
println!(" │ auth : {}", s.auth.clone().unwrap_or_else(|| "(none)".into()));
|
||||
println!(" │ creds : {}", s.creds.clone().unwrap_or_else(|| "(none)".into()));
|
||||
println!(" │ proxy : {}", s.proxy.clone().unwrap_or_else(|| "(none — /proxy for Burp/ZAP)".into()));
|
||||
println!(" │ focus : {}", s.instructions.clone().unwrap_or_else(|| "(none — tests everything)".into()));
|
||||
println!(" │ opts : mcp={} offline={} votes={} chain-depth={} max-agents={} idle-stop={}",
|
||||
onoff(s.mcp), onoff(s.offline), s.vote_n, s.chain_depth, s.max_agents,
|
||||
@@ -1289,6 +1304,7 @@ fn help() {
|
||||
h("/mcp on|off", "Playwright MCP browser /offline on|off self-test");
|
||||
h("/votes <n>", "validator votes /chain <n> attack-chain depth");
|
||||
h("/timeout <min>", "idle guardrail: stop if no new finding in <min> (0 = off)");
|
||||
h("/proxy <url>|off", "route agent HTTP through Burp/ZAP (/burp = default :8080)");
|
||||
h("/agents <n>|list", "cap agents · list counts /theme color|mono");
|
||||
h("/show (config)", "/clear /quit");
|
||||
|
||||
|
||||
@@ -68,11 +68,61 @@ fn tool_doctrine(mcp_on: bool) -> String {
|
||||
Prefer `linkfinder`/`gau`/`katana` to harvest more URLs when present, else regex with `grep -Eo`.\n\
|
||||
- REQUEST/RESPONSE ANALYSIS: read status codes, every header, Set-Cookie flags, content-type, body length \
|
||||
and response timing; use DIFFERENTIALS (authenticated vs anonymous, valid vs invalid input, existing vs \
|
||||
missing resource) and reflected input / verbose errors to infer behavior and CONFIRM issues with evidence.\n\
|
||||
missing resource) and reflected input / verbose errors to infer behavior and CONFIRM issues with evidence. \
|
||||
Save full request/response pairs when they matter for the PoC.\n\
|
||||
- NUCLEI (fast, targeted — never a blind full scan): first fingerprint the stack, then run nuclei ONLY on \
|
||||
relevant templates, e.g. `nuclei -u <target> -tags <detected-tech,cve> -severity critical,high,medium \
|
||||
-rl 50 -timeout 8 -retries 1` (or `-t <specific-template>` for a suspected CVE). Prefer targeted \
|
||||
`-id`/`-tags` over the whole template set so it stays quick; confirm any hit manually with curl.\n\
|
||||
- MISCONFIG HUNTING: probe for absurd-but-common misconfigurations — exposed `.git`/`.env`/backup & config \
|
||||
files, directory listing, debug/actuator/trace endpoints, default & weak credentials, open admin panels, \
|
||||
permissive CORS, verbose stack traces, exposed dashboards (Kibana/Grafana/Jenkins/phpMyAdmin), and cloud \
|
||||
metadata (169.254.169.254) via SSRF.\n\
|
||||
- RATE-LIMIT / ANTI-AUTOMATION: on auth, password-reset, OTP and sensitive endpoints, send a controlled \
|
||||
burst (e.g. ~20-30 requests) and check for 429/lockout/Retry-After/backoff; report absence as a finding. \
|
||||
Keep bursts small and non-disruptive — this is a control check, not a DoS.\n\
|
||||
- TOOL DOWNLOAD (authorized): when a public PoC or scanner is needed you MAY `git clone` a specific PoC/exploit \
|
||||
repo or download a tool (`git clone`, `wget`, `pip install`, `go install`, `cargo install`) — use pinned, \
|
||||
reputable sources; review before running; never run destructive payloads.\n\
|
||||
- {browser}\n\
|
||||
Use only what is installed; degrade gracefully. Never run destructive or DoS actions.\n\n"
|
||||
- {proxy}{pocs}\
|
||||
Use only what is installed; degrade gracefully. Never run destructive or DoS actions.\n\n",
|
||||
proxy = proxy_line(),
|
||||
pocs = pocs_line(),
|
||||
)
|
||||
}
|
||||
|
||||
/// If a local proxy is configured (Burp/ZAP), tell agents to route HTTP through
|
||||
/// it so the operator can inspect/replay traffic in Burp Suite.
|
||||
fn proxy_line() -> String {
|
||||
match std::env::var("NEUROSPLOIT_PROXY").ok().filter(|v| !v.trim().is_empty()) {
|
||||
Some(p) => format!(
|
||||
"PROXY: route ALL HTTP through the local intercepting proxy at {p} (Burp/ZAP) so the operator can \
|
||||
inspect & replay in Burp — add `--proxy {p} -k` to every curl (and set http(s)_proxy for other tools). \
|
||||
Send anything noteworthy through it for manual review.\n "),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// If a PoC directory is set, tell agents to save custom exploit scripts there.
|
||||
fn pocs_line() -> String {
|
||||
match std::env::var("NEUROSPLOIT_POCS").ok().filter(|v| !v.trim().is_empty()) {
|
||||
Some(d) => format!(
|
||||
"POCS: when an issue needs a custom multi-step exploit/script to prove it, WRITE a runnable PoC \
|
||||
(curl/python/bash) to {d}/ with a short header comment (target, what it proves, usage), run it to \
|
||||
confirm, and reference the file path in the finding evidence.\n "),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Data-safety guardrail prepended to every exploit/chain prompt.
|
||||
const SAFETY_DOCTRINE: &str = "DATA SAFETY (strict): prove impact WITHOUT harming data. Do NOT modify, delete, \
|
||||
overwrite, encrypt or exfiltrate data, create/alter/escalate accounts, or change configuration/state — unless the \
|
||||
operator has explicitly authorized that specific action. Read-only, minimal proof. If you encounter PII (names, \
|
||||
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";
|
||||
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
|
||||
@@ -195,13 +245,13 @@ pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<Str
|
||||
let user = format!(
|
||||
"AUTHORIZED engagement — you have explicit permission to test {target}. \
|
||||
Do not ask for confirmation — proceed and PROVE each issue.\n\n\
|
||||
{directives}{react}{depth}{doctrine}{body}\n\nWhen done, reply with ONLY a JSON array of confirmed findings (may be empty []). \
|
||||
{directives}{react}{depth}{safety}{doctrine}{body}\n\nWhen done, reply with ONLY a JSON array of confirmed findings (may be empty []). \
|
||||
Each item: {{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}. \
|
||||
`evidence` must contain the concrete proof (request/response excerpt).",
|
||||
target = target,
|
||||
directives = directives,
|
||||
react = REACT_DOCTRINE,
|
||||
depth = DEPTH_DOCTRINE,
|
||||
depth = DEPTH_DOCTRINE, safety = SAFETY_DOCTRINE,
|
||||
doctrine = tool_doctrine(mcp_on),
|
||||
body = ag.user.replace("{target}", &target).replace("{recon_json}", &recon),
|
||||
);
|
||||
@@ -413,11 +463,11 @@ 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}{doctrine}{body}\n\n\
|
||||
Proceed and PROVE each issue against the LIVE app.\n\n{directives}{leads}{react}{depth}{safety}{doctrine}{body}\n\n\
|
||||
Reply ONLY a JSON array of confirmed findings (may be []): \
|
||||
{{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}.",
|
||||
target = target, directives = directives, leads = leads,
|
||||
react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, doctrine = tool_doctrine(mcp_on),
|
||||
react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, safety = SAFETY_DOCTRINE, 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 {
|
||||
@@ -561,13 +611,13 @@ async fn chain_from_seed(pool: &ModelPool, target: &str, directives: &str, recon
|
||||
};
|
||||
let short: String = seed.title.chars().take(28).collect();
|
||||
let user = format!(
|
||||
"AUTHORIZED engagement on {target}.\n\n{directives}{react}{depth}{doctrine}\
|
||||
"AUTHORIZED engagement on {target}.\n\n{directives}{react}{depth}{safety}{doctrine}\
|
||||
FOOTHOLD TO EXPAND (round {round}/{max}):\n- [{}] {} @ {} ({})\n payload: {}\n evidence: {}\n\n\
|
||||
LOOT GATHERED (reuse it):\n{loot_block}\n\n{recipe_block}RECON:\n{recon_ctx}\n\n\
|
||||
From THIS foothold, DECIDE the best directions and PROVE new impact — post-exploitation (loot creds/keys/config/source), credential reuse, privilege escalation (horizontal & vertical), lateral movement to adjacent services/hosts, data exfiltration, and NEW attack surface it exposes. Every claim needs a real tool receipt.\n\n\
|
||||
Reply ONLY JSON: {{\"findings\":[{{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}],\"loot\":[\"cred:user:pass@host\",\"token:...\",\"host:10.0.0.5\",\"endpoint:/internal/api\"]}} (empty arrays are fine).",
|
||||
seed.severity, seed.title, seed.endpoint, seed.cwe, seed.payload, seed.evidence,
|
||||
react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, doctrine = tool_doctrine(pool.mcp_config.is_some()),
|
||||
react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, safety = SAFETY_DOCTRINE, doctrine = tool_doctrine(pool.mcp_config.is_some()),
|
||||
);
|
||||
let label = format!("chain:{short}");
|
||||
match pool.complete_routed(Task::Exploit, &label, CHAIN_SYS, &user).await {
|
||||
@@ -1124,8 +1174,8 @@ pub async fn run_host(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sende
|
||||
let _ = txc.send(format!(" ▶ launching agent: {} ({})", ag.name, ag.title.replace(" Agent", ""))).await;
|
||||
}
|
||||
let user = format!(
|
||||
"AUTHORIZED host engagement on {target}. Proceed and PROVE each issue with raw tool output.\n\n{directives}{tooling}{react}{body}\n\nReply ONLY a JSON array of confirmed findings (may be []): {{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}.",
|
||||
target = target, directives = directives, tooling = HOST_TOOLING, react = REACT_DOCTRINE,
|
||||
"AUTHORIZED host engagement on {target}. Proceed and PROVE each issue with raw tool output.\n\n{directives}{tooling}{react}{safety}{body}\n\nReply ONLY a JSON array of confirmed findings (may be []): {{id,title,severity,cwe,endpoint,payload,evidence,impact,remediation,confidence}}.",
|
||||
target = target, directives = directives, tooling = HOST_TOOLING, react = REACT_DOCTRINE, safety = SAFETY_DOCTRINE,
|
||||
body = ag.user.replace("{target}", &target).replace("{recon_json}", &recon),
|
||||
);
|
||||
match pool.complete_routed(Task::Exploit, &ag.name, &ag.system, &user).await {
|
||||
|
||||
@@ -128,6 +128,11 @@ pub struct RunConfig {
|
||||
/// newest footholds in new directions, carrying discovered loot forward.
|
||||
#[serde(default = "default_chain_depth")]
|
||||
pub chain_depth: usize,
|
||||
/// Optional local intercepting proxy (Burp/ZAP), e.g. http://127.0.0.1:8080.
|
||||
/// When set, agents route HTTP through it so the operator can inspect/replay
|
||||
/// traffic in Burp Suite.
|
||||
#[serde(default)]
|
||||
pub proxy: Option<String>,
|
||||
}
|
||||
|
||||
fn default_vote() -> usize {
|
||||
@@ -159,6 +164,7 @@ impl RunConfig {
|
||||
repo: None,
|
||||
pinned: Vec::new(),
|
||||
chain_depth: 2,
|
||||
proxy: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user