mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-06 19:35:13 +02:00
Added Cap::DATA_EXFIL and taint fp and fn fixes on real repos (#59)
* feat: Enhance data exfiltration detection with source sensitivity gating for cookies and headers * feat: Implement cross-file data exfiltration detection with parameter-specific gate filters * feat: Add calibration tests and refine DATA_EXFIL severity scoring logic * feat: Introduce per-detector configuration for data exfiltration suppression * feat: Enhance DATA_EXFIL findings with destination field tracking in diagnostics and SARIF output * feat: Add tainted body and URL handling for data exfiltration detection * feat: Add integration tests and fixtures for DATA_EXFIL and SSRF detection in Go * feat: Add Java integration tests and fixtures for DATA_EXFIL detection across multiple HTTP clients * feat: Add synthetic externals handling for closure-captured variables in SSA * feat: Implement closure-based suppression for resource leak findings * feat: Add regression guards for shell-injection and taint propagation in for-of destructure patterns * feat: Implement constructor cap narrowing for data exfiltration detection in HTTP request builders * feat: Add gated sinks for data exfiltration detection in C and C++ using curl_easy_setopt * feat: Implement DATA_EXFIL cap parity for backwards analysis and add integration tests * feat: Add data exfiltration sinks for various languages and enhance documentation * refactor: Simplify formatting and improve readability in various files * refactor: Improve readability by simplifying conditional statements and adding clippy linting * docs: Update CHANGELOG and comments for data exfiltration features and configuration * docs: Clarify configuration instructions for data exfiltration trusted destinations * docs: Enhance comments for evidence routing logic in data exfiltration
This commit is contained in:
parent
a438886217
commit
58f1794a4e
189 changed files with 8421 additions and 383 deletions
138
tests/data_exfil_java_integration_tests.rs
Normal file
138
tests/data_exfil_java_integration_tests.rs
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
//! Integration tests for the Java bindings of the `Cap::DATA_EXFIL`
|
||||
//! detector class.
|
||||
//!
|
||||
//! Mirrors the JS `fetch_data_exfil_integration_tests` and Go
|
||||
//! `data_exfil_go_integration_tests` shapes. Each chained-API HTTP
|
||||
//! client (java.net.http, Spring RestTemplate / WebClient, OkHttp,
|
||||
//! Apache HttpClient) gets its own fixture: a Sensitive source flows
|
||||
//! through the body-binding chain into a fixed-URL outbound call, and
|
||||
//! the regression fixture proves SSRF still fires on a tainted URL
|
||||
//! without leaking into DATA_EXFIL.
|
||||
//!
|
||||
//! Body-binding chain propagators (`BodyPublishers.ofString`,
|
||||
//! `RequestBody.create`, `StringEntity` ctor, builder `.uri()` /
|
||||
//! `.POST()` / `.bodyValue()`) carry taint through the chain via the
|
||||
//! transfer engine's default arg → return smear, so no per-callee
|
||||
//! propagator rules are needed; the sink at the network call sees the
|
||||
//! end-of-chain request value carrying body taint.
|
||||
|
||||
mod common;
|
||||
|
||||
use common::scan_fixture_dir;
|
||||
use nyx_scanner::commands::scan::Diag;
|
||||
use nyx_scanner::utils::config::AnalysisMode;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn java_fixture_dir() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("fixtures")
|
||||
.join("java")
|
||||
}
|
||||
|
||||
fn diags_for(file: &str) -> Vec<Diag> {
|
||||
let dir = java_fixture_dir();
|
||||
let all = scan_fixture_dir(&dir, AnalysisMode::Full);
|
||||
all.into_iter().filter(|d| d.path.ends_with(file)).collect()
|
||||
}
|
||||
|
||||
fn assert_data_exfil_fires_no_ssrf(file: &str) {
|
||||
let diags = diags_for(file);
|
||||
let exfil = diags
|
||||
.iter()
|
||||
.filter(|d| d.id.starts_with("taint-data-exfiltration"))
|
||||
.count();
|
||||
let plain_taint = diags
|
||||
.iter()
|
||||
.filter(|d| d.id.starts_with("taint-unsanitised-flow"))
|
||||
.count();
|
||||
assert!(
|
||||
exfil >= 1,
|
||||
"{file}: expected at least one taint-data-exfiltration finding, got 0.\n\
|
||||
Diags: {:#?}",
|
||||
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
|
||||
);
|
||||
assert_eq!(
|
||||
plain_taint,
|
||||
0,
|
||||
"{file}: fixed-URL call with tainted body must NOT emit SSRF \
|
||||
(taint-unsanitised-flow), got {plain_taint}.\n\
|
||||
Diags: {:#?}",
|
||||
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jdk_http_client_chain_emits_data_exfil_not_ssrf() {
|
||||
// java.net.http: cookie → BodyPublishers.ofString → builder chain →
|
||||
// client.send(req). Type-qualified resolution rewrites
|
||||
// client.send → HttpClient.send so the new flat DATA_EXFIL rule
|
||||
// and the existing flat SSRF rule both attach; only DATA_EXFIL
|
||||
// should surface because the URL is hardcoded.
|
||||
assert_data_exfil_fires_no_ssrf("data_exfil_jdk_httpclient.java");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rest_template_post_for_object_emits_data_exfil_not_ssrf() {
|
||||
// Spring RestTemplate: header → restTemplate.postForObject(url,
|
||||
// body, type). RestTemplate subtypes HttpClient via the
|
||||
// JAVA_HIERARCHY so type-qualified resolution finds the same flat
|
||||
// rule that the JDK client uses.
|
||||
assert_data_exfil_fires_no_ssrf("data_exfil_resttemplate.java");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn web_client_body_value_emits_data_exfil_not_ssrf() {
|
||||
// Spring WebClient: env var → webClient.post().uri(u).bodyValue(p)
|
||||
// .retrieve(). The body-bind step `bodyValue` carries a flat
|
||||
// DATA_EXFIL sink rule — a bare-name suffix matcher independent of
|
||||
// receiver typing, since the chain receiver type is RequestBodySpec.
|
||||
assert_data_exfil_fires_no_ssrf("data_exfil_webclient.java");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ok_http_new_call_execute_emits_data_exfil_not_ssrf() {
|
||||
// OkHttp two-step: session attribute → RequestBody.create →
|
||||
// builder chain → client.newCall(req).execute(). Chain
|
||||
// normalization strips `()` between dots so the suffix
|
||||
// `newCall.execute` matches.
|
||||
assert_data_exfil_fires_no_ssrf("data_exfil_okhttp.java");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apache_http_client_execute_emits_data_exfil_not_ssrf() {
|
||||
// Apache HttpClient: cookie → StringEntity → HttpPost.setEntity →
|
||||
// httpClient.execute(req). CloseableHttpClient subtypes HttpClient
|
||||
// so type-qualified resolution rewrites client.execute →
|
||||
// HttpClient.execute and reuses the same flat rule.
|
||||
assert_data_exfil_fires_no_ssrf("data_exfil_apache_httpclient.java");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ssrf_url_only_emits_ssrf_not_data_exfil() {
|
||||
// Tainted URL with hardcoded body: SSRF must fire on the URL flow,
|
||||
// DATA_EXFIL must NOT fire because no Sensitive source reaches the
|
||||
// body. Guards against the new flat DATA_EXFIL rule over-firing.
|
||||
let diags = diags_for("ssrf_url_only_no_data_exfil.java");
|
||||
let ssrf = diags
|
||||
.iter()
|
||||
.filter(|d| d.id.starts_with("taint-unsanitised-flow"))
|
||||
.count();
|
||||
let exfil = diags
|
||||
.iter()
|
||||
.filter(|d| d.id.starts_with("taint-data-exfiltration"))
|
||||
.count();
|
||||
assert!(
|
||||
ssrf >= 1,
|
||||
"expected at least one taint-unsanitised-flow (SSRF) finding, got 0.\n\
|
||||
Diags: {:#?}",
|
||||
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
|
||||
);
|
||||
assert_eq!(
|
||||
exfil,
|
||||
0,
|
||||
"tainted-URL HttpClient.send must NOT emit DATA_EXFIL, got {exfil}.\n\
|
||||
Diags: {:#?}",
|
||||
diags.iter().map(|d| &d.id).collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue