mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-07-07 03:47:56 +02:00
decision-driven deep exploitation: DECISION doctrine, multi-role /auth, +6 agents
- DECISION_DOCTRINE injected into exploit/grey/chain prompts: analyse responses to pick the technique; map & connect routes (endpoint output → next endpoint input); hunt sensitive flows; mine parameters (incl. hidden from JS/source maps) and test per-param; mock realistic (non-PII) data to reach deeper logic; exploit the authenticated surface after login and compare roles; build PoCs when a proof needs an artifact; bypass 401/403/redirect controls. - REPL /auth now supports multiple named identities (/auth admin <hdr>, /auth user <hdr>; bare token → Bearer). With >=2 roles the run gets the access-control directive (IDOR/BOLA/BFLA/privesc, authorized-vs-unauthorized) and tests both. - +6 decision agents (library 389): param_miner, endpoint_flow_linker, authenticated_surface_exploit, clickjacking_poc (HTML PoC), csrf_poc (HTML PoC), access_control_bypass. - Docs: counts 383->389, RELEASE + /auth help updated.
This commit is contained in:
@@ -225,6 +225,8 @@ struct Session {
|
||||
target: Option<String>,
|
||||
repo: Option<String>,
|
||||
auth: Option<String>,
|
||||
/// Named identities for multi-role access-control testing (name, header line).
|
||||
roles: Vec<(String, String)>,
|
||||
creds: Option<String>,
|
||||
instructions: Option<String>,
|
||||
attachments: Vec<String>,
|
||||
@@ -247,6 +249,7 @@ impl Default for Session {
|
||||
target: None,
|
||||
repo: None,
|
||||
auth: None,
|
||||
roles: Vec::new(),
|
||||
creds: None,
|
||||
instructions: None,
|
||||
attachments: Vec::new(),
|
||||
@@ -500,9 +503,32 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
"/auth" => {
|
||||
if arg.is_empty() { println!(" auth: {}", s.auth.clone().unwrap_or_else(|| "(none) — set with /auth <header>, clear with /auth clear".into())); }
|
||||
else if arg == "clear" { s.auth = None; println!(" auth cleared"); }
|
||||
else { s.auth = Some(arg.to_string()); println!(" auth set: {arg}"); }
|
||||
if arg.is_empty() {
|
||||
match s.auth.clone() {
|
||||
Some(a) => println!(" auth: {a}"),
|
||||
None => println!(" auth: (none) — /auth <header> · or roles: /auth admin <hdr> · /auth user <hdr>"),
|
||||
}
|
||||
for (n, v) in &s.roles { println!(" role {n}: {v}"); }
|
||||
if s.roles.len() >= 2 { println!(" \x1b[2m{} identities → access-control testing (IDOR/BOLA/BFLA) on /run\x1b[0m", s.roles.len()); }
|
||||
}
|
||||
else if arg == "clear" { s.auth = None; s.roles.clear(); println!(" auth + roles cleared"); }
|
||||
else {
|
||||
// "<role> <value>" if the first token is a bare identifier (no ':').
|
||||
let mut it = arg.splitn(2, char::is_whitespace);
|
||||
let first = it.next().unwrap_or("");
|
||||
let rest = it.next().unwrap_or("").trim();
|
||||
let is_role = !first.contains(':') && !rest.is_empty()
|
||||
&& first.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-');
|
||||
if is_role {
|
||||
let val = normalize_auth(rest);
|
||||
s.roles.retain(|(n, _)| n != first);
|
||||
s.roles.push((first.to_string(), val.clone()));
|
||||
if s.auth.is_none() { s.auth = Some(val); } // first role also = primary session
|
||||
println!(" role '{first}' set ({} identit{}) — test both scenarios on /run", s.roles.len(), if s.roles.len() == 1 { "y" } else { "ies" });
|
||||
} else {
|
||||
s.auth = Some(arg.to_string()); println!(" auth set: {arg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
"/creds" => {
|
||||
if arg.is_empty() { println!(" creds file: {}", s.creds.clone().unwrap_or_else(|| "(none) — set with /creds <file.yaml>".into())); }
|
||||
@@ -847,6 +873,11 @@ async fn run(base: &Path, s: &Session, history: &mut Vec<RunRecord>) {
|
||||
}
|
||||
};
|
||||
cfg.auth = s.auth.clone();
|
||||
// Multiple /auth identities → prepend the access-control (IDOR/BOLA/BFLA) directive.
|
||||
if let Some(rd) = roles_directive(&s.roles) {
|
||||
let base = cfg.instructions.clone().unwrap_or_default();
|
||||
cfg.instructions = Some(format!("{rd}{base}"));
|
||||
}
|
||||
if let M::Grey { repo, .. } = &m {
|
||||
cfg.repo = Some(repo.clone());
|
||||
}
|
||||
@@ -1374,7 +1405,7 @@ fn help() {
|
||||
println!("\n \x1b[2mTARGET & SCOPE\x1b[0m");
|
||||
h("/target <url[,..]>", "black-box target URL (comma-separated = multi-target, sequential)");
|
||||
h("/repo <path|url>", "analyse a repo — path or GitHub URL (repo + target = greybox)");
|
||||
h("/auth <value>", "auth header, e.g. 'Authorization: Bearer <jwt>' (no arg = show)");
|
||||
h("/auth <value>", "auth header (Bearer/cookie/key). Roles: /auth admin <hdr> · /auth user <hdr>");
|
||||
h("/creds <file.yaml>", "creds: jwt/header/cookie/login + ssh/windows + aws/gcp/azure + roles");
|
||||
h("/focus <text>", "steer the tests (or just type the instruction)");
|
||||
h("@path @dir @f:1-20", "attach a file/folder/line-range to context (Tab → menu)");
|
||||
@@ -1503,6 +1534,30 @@ const PROMPT: &str = "neurosploit› ";
|
||||
/// exiting (instead of losing an active run to a stray interrupt).
|
||||
const CTRL_C: &str = "\u{0}__ctrl_c__";
|
||||
|
||||
/// Turn a role value into a header line: a full `Header: value` is used as-is;
|
||||
/// a bare token becomes `Authorization: Bearer <token>`.
|
||||
fn normalize_auth(v: &str) -> String {
|
||||
let v = v.trim();
|
||||
if v.contains(':') { v.to_string() } else { format!("Authorization: Bearer {v}") }
|
||||
}
|
||||
|
||||
/// Build the multi-role access-control directive from the session roles (mirrors
|
||||
/// creds.yaml roles). Empty when fewer than 2 identities.
|
||||
fn roles_directive(roles: &[(String, String)]) -> Option<String> {
|
||||
if roles.len() < 2 { return None; }
|
||||
let list = roles.iter().map(|(n, v)| format!(" - {n} → send `{v}`")).collect::<Vec<_>>().join("\n");
|
||||
Some(format!(
|
||||
"MULTI-ROLE ACCESS CONTROL — you have {} identities:\n{list}\n\
|
||||
Authenticate as EACH identity (send its header on every request). Test broken access control ACROSS roles and \
|
||||
compare authorized vs unauthorized:\n\
|
||||
- BOLA/IDOR: as a low-privilege role capture your own object IDs, then read/modify another role's objects by ID.\n\
|
||||
- BFLA: call admin-only functions/endpoints/HTTP methods with a low-privilege role's session.\n\
|
||||
- Privilege escalation: mass-assignment of role/permission fields, or reaching admin routes.\n\
|
||||
Prove each with the two requests (authorized role succeeds, unauthorized role should be denied but isn't). \
|
||||
Read-only proof; mask any PII.\n\n",
|
||||
roles.len()))
|
||||
}
|
||||
|
||||
/// Split the session target into one or more URLs (comma-separated list).
|
||||
fn session_targets(s: &Session) -> Vec<String> {
|
||||
s.target.as_deref().map(|t| t.split(',').map(|x| x.trim().to_string()).filter(|x| !x.is_empty()).collect())
|
||||
|
||||
@@ -201,6 +201,18 @@ const DEPTH_DOCTRINE: &str = "DEPTH (exploit, don't just expose):\n\
|
||||
- Audit tokens: for any JWT, check alg-confusion (RS→HS), alg:none, kid/jku injection, whether the signature is actually verified, and weak/guessable HS256 secrets.\n\
|
||||
- Calibrate honestly: claim High/Critical ONLY when impact is DEMONSTRATED; unproven DoS/abuse is Low/Info or a lead, never inflated.\n\n";
|
||||
|
||||
/// DECISION doctrine (v3.5.5): make the agent REASON about where to attack from
|
||||
/// the observed responses, map & connect routes, mine parameters, test both auth
|
||||
/// levels, and build PoCs — instead of blindly firing a fixed payload list.
|
||||
const DECISION_DOCTRINE: &str = "DECIDE WHERE TO ATTACK (analyse, then act):\n\
|
||||
- Analyse responses FIRST: read status, headers, content-type, body, redirects and TIMING; let the evidence pick the technique (e.g. SQL error → SQLi; reflected input → XSS; numeric id in JSON → IDOR; missing X-Frame-Options → clickjacking; state-changing POST without a token → CSRF). Don't run payloads that the response makes irrelevant.\n\
|
||||
- Map & CONNECT routes: build the route/endpoint graph and link one endpoint to another — an id/token/filename returned by endpoint A is the input to endpoint B; follow multi-step flows (login → profile → order → admin) and hunt the SENSITIVE ones (auth, password reset, payment, file upload/download, account/role changes, admin, export).\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\
|
||||
- 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";
|
||||
|
||||
/// Black-box web engagement: recon → parallel exploit → N-model vote → report.
|
||||
pub async fn run(cfg: RunConfig, lib: &Library, pool: &ModelPool, tx: Sender<String>) -> RunOutput {
|
||||
pool.set_progress(tx.clone());
|
||||
@@ -313,13 +325,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}{safety}{doctrine}{body}\n\nWhen done, reply with ONLY a JSON array of confirmed findings (may be empty []). \
|
||||
{directives}{react}{depth}{decision}{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, safety = SAFETY_DOCTRINE,
|
||||
depth = DEPTH_DOCTRINE, decision = DECISION_DOCTRINE, safety = SAFETY_DOCTRINE,
|
||||
doctrine = tool_doctrine(mcp_on),
|
||||
body = ag.user.replace("{target}", &target).replace("{recon_json}", &recon),
|
||||
);
|
||||
@@ -535,11 +547,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}{safety}{doctrine}{body}\n\n\
|
||||
Proceed and PROVE each issue against the LIVE app.\n\n{directives}{leads}{react}{depth}{decision}{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, safety = SAFETY_DOCTRINE, doctrine = tool_doctrine(mcp_on),
|
||||
react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, decision = DECISION_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 {
|
||||
@@ -683,13 +695,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}{safety}{doctrine}\
|
||||
"AUTHORIZED engagement on {target}.\n\n{directives}{react}{depth}{decision}{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, safety = SAFETY_DOCTRINE, doctrine = tool_doctrine(pool.mcp_config.is_some()),
|
||||
react = REACT_DOCTRINE, depth = DEPTH_DOCTRINE, decision = DECISION_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 {
|
||||
|
||||
Reference in New Issue
Block a user