mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-10-19 05:57:45 +02:00
fix(scope): don't treat an endpoint's HTTP method as its host
Agents report endpoints as "METHOD /path" (e.g. "GET /api/search"). host_of
split on whitespace/slash and returned the method ("get"/"post") as the host,
so host-relative findings were wrongly quarantined as out-of-scope and the
attack graph grew bogus HOST "get"/HOST "post" nodes — real findings vanished
from the report.
Add scope::strip_leading_method and apply it in host_of and in the graph
finding projection. A host-relative endpoint now resolves to an empty host
(kept in scope, relative to the authorized target) and a method+URL endpoint
resolves to the real host. Covered by new scope tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
99d91101ac
commit
2502907160
2 files changed
+50
-2
No files matched your search
@@ -626,7 +626,9 @@ pub fn project_findings(b: &mut GraphBatch, target: &str, run: &str, findings: &
|
||||
let conf = f.confidence.clamp(0.0, 1.0).max(if st == Status::Confirmed { 0.8 } else { 0.3 });
|
||||
let attack = f.evidence_data.as_ref().and_then(|e| e.attack.as_ref().or(e.identity_b.as_ref()));
|
||||
let url = if !f.endpoint.trim().is_empty() {
|
||||
f.endpoint.trim().to_string()
|
||||
// Endpoints are often "METHOD /path" — drop the method so it isn't
|
||||
// mistaken for the host (which created bogus "get"/"post" HOST nodes).
|
||||
crate::scope::strip_leading_method(&f.endpoint).to_string()
|
||||
} else if let Some(a) = attack {
|
||||
a.url.clone()
|
||||
} else {
|
||||
|
||||
@@ -179,9 +179,28 @@ fn u32_to_ipv4(v: u32) -> String {
|
||||
format!("{}.{}.{}.{}", v >> 24, (v >> 16) & 255, (v >> 8) & 255, v & 255)
|
||||
}
|
||||
|
||||
/// Strip a leading HTTP method token from an endpoint string.
|
||||
///
|
||||
/// Agent endpoints are frequently written as "METHOD /path" or
|
||||
/// "METHOD https://host/path" (e.g. "GET /api/search"). Without stripping it,
|
||||
/// the method is mistaken for the host — a host-relative finding like
|
||||
/// "GET /api/search" resolves to host "get" and gets wrongly quarantined as
|
||||
/// out-of-scope or graphed as a bogus HOST node. Returns the input unchanged
|
||||
/// when it does not begin with a known method.
|
||||
pub fn strip_leading_method(endpoint: &str) -> &str {
|
||||
let s = endpoint.trim_start();
|
||||
if let Some((first, rest)) = s.split_once(char::is_whitespace) {
|
||||
const METHODS: [&str; 9] = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "TRACE", "CONNECT"];
|
||||
if METHODS.contains(&first.to_ascii_uppercase().as_str()) {
|
||||
return rest.trim_start();
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
/// Host of a URL or bare authority, lowercased, without port or userinfo.
|
||||
pub fn host_of(url: &str) -> String {
|
||||
let s = url.trim().to_lowercase();
|
||||
let s = strip_leading_method(url).to_lowercase();
|
||||
let s = s.split_once("://").map(|(_, r)| r).unwrap_or(&s);
|
||||
let s = s.split(['/', '?', '#']).next().unwrap_or(s);
|
||||
let s = s.rsplit_once('@').map(|(_, h)| h).unwrap_or(s);
|
||||
@@ -753,6 +772,33 @@ mod tests {
|
||||
p
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_of_strips_a_leading_http_method() {
|
||||
// "METHOD /path" endpoints (how agents usually report them) must not
|
||||
// resolve the method as the host — the path is host-relative, so the
|
||||
// host is empty and the finding stays in scope.
|
||||
assert_eq!(host_of("GET /api/search?q=x"), "");
|
||||
assert_eq!(host_of("POST /api/auth/login"), "");
|
||||
assert_eq!(host_of("get /admin"), "");
|
||||
// Method + absolute URL still yields the real host.
|
||||
assert_eq!(host_of("GET https://www.evidelo.com.br/api/x"), "evidelo.com.br");
|
||||
// A bare host or URL is unchanged.
|
||||
assert_eq!(host_of("https://app.example.com/a"), "app.example.com");
|
||||
assert_eq!(host_of("app.example.com:8443"), "app.example.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_relative_findings_are_kept_in_scope() {
|
||||
let p = policy();
|
||||
let f = |ep: &str| Finding { endpoint: ep.to_string(), ..Default::default() };
|
||||
let (kept, out) = p.audit_findings(vec![
|
||||
f("GET /api/search?q=x"),
|
||||
f("POST /api/auth/login"),
|
||||
]);
|
||||
assert_eq!(kept.len(), 2, "host-relative findings must not be quarantined");
|
||||
assert!(out.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_default_scope_is_the_target_and_nothing_else() {
|
||||
let p = policy();
|
||||
|
||||
Reference in new issue
Block a user