mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
111 lines
3.3 KiB
Rust
111 lines
3.3 KiB
Rust
//! Phase 11 (Track J.9) — `Cap::DATA_EXFIL` corpus acceptance.
|
|
//!
|
|
//! Asserts the corpus + outbound-network oracle for all seven
|
|
//! backend-capable languages. The vuln payload supplies an
|
|
//! attacker-controlled host (`attacker.test`); the
|
|
//! [`nyx_scanner::dynamic::oracle::ProbePredicate::OutboundHostNotIn`]
|
|
//! predicate fires when the captured `host` falls outside the
|
|
//! loopback allowlist (`&["127.0.0.1", "localhost"]`). Per-lang
|
|
//! harness dispatchers are deferred — see
|
|
//! `.pitboss/play/deferred.md`.
|
|
//!
|
|
//! `cargo nextest run --features dynamic --test data_exfil_corpus`.
|
|
|
|
#![cfg(feature = "dynamic")]
|
|
|
|
use nyx_scanner::dynamic::corpus::{payloads_for_lang, resolve_benign_control_lang};
|
|
use nyx_scanner::dynamic::oracle::{oracle_fired, Oracle, ProbePredicate};
|
|
use nyx_scanner::dynamic::probe::{ProbeKind, ProbeWitness, SinkProbe};
|
|
use nyx_scanner::dynamic::sandbox::SandboxOutcome;
|
|
use nyx_scanner::labels::Cap;
|
|
use nyx_scanner::symbol::Lang;
|
|
use std::time::Duration;
|
|
|
|
const LANGS: &[Lang] = &[
|
|
Lang::Python,
|
|
Lang::Ruby,
|
|
Lang::Java,
|
|
Lang::Php,
|
|
Lang::JavaScript,
|
|
Lang::Go,
|
|
Lang::Rust,
|
|
];
|
|
|
|
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
|
|
|
fn outcome() -> SandboxOutcome {
|
|
SandboxOutcome {
|
|
exit_code: Some(0),
|
|
stdout: vec![],
|
|
stderr: vec![],
|
|
timed_out: false,
|
|
oob_callback_seen: false,
|
|
sink_hit: false,
|
|
duration: Duration::from_millis(1),
|
|
hardening_outcome: None,
|
|
}
|
|
}
|
|
|
|
fn outbound_probe(host: &str) -> SinkProbe {
|
|
SinkProbe {
|
|
sink_callee: "__nyx_mock_http".into(),
|
|
args: vec![],
|
|
captured_at_ns: 1,
|
|
payload_id: "data-exfil-test".into(),
|
|
kind: ProbeKind::OutboundNetwork { host: host.into() },
|
|
witness: ProbeWitness::empty(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn corpus_registers_data_exfil_for_each_supported_lang() {
|
|
for lang in LANGS {
|
|
let slice = payloads_for_lang(Cap::DATA_EXFIL, *lang);
|
|
assert!(!slice.is_empty(), "DATA_EXFIL missing for {lang:?}");
|
|
assert!(slice.iter().any(|p| !p.is_benign));
|
|
assert!(slice.iter().any(|p| p.is_benign));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn data_exfil_payloads_pair_benign_per_lang() {
|
|
for lang in LANGS {
|
|
let slice = payloads_for_lang(Cap::DATA_EXFIL, *lang);
|
|
let vuln = slice.iter().find(|p| !p.is_benign).expect("vuln");
|
|
let resolved = resolve_benign_control_lang(vuln, Cap::DATA_EXFIL, *lang)
|
|
.expect("benign control resolves");
|
|
assert!(resolved.is_benign);
|
|
match &vuln.oracle {
|
|
Oracle::SinkProbe { predicates } => assert!(predicates.iter().any(|p| matches!(
|
|
p,
|
|
ProbePredicate::OutboundHostNotIn { .. }
|
|
))),
|
|
other => panic!("expected SinkProbe, got {other:?}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn outbound_predicate_fires_off_allowlist() {
|
|
let oracle = Oracle::SinkProbe {
|
|
predicates: &[ProbePredicate::OutboundHostNotIn {
|
|
allowlist: ALLOWLIST,
|
|
}],
|
|
};
|
|
assert!(oracle_fired(
|
|
&oracle,
|
|
&outcome(),
|
|
&[outbound_probe("attacker.test")]
|
|
));
|
|
assert!(!oracle_fired(
|
|
&oracle,
|
|
&outcome(),
|
|
&[outbound_probe("127.0.0.1")]
|
|
));
|
|
assert!(!oracle_fired(
|
|
&oracle,
|
|
&outcome(),
|
|
&[outbound_probe("Localhost")]
|
|
));
|
|
assert!(!oracle_fired(&oracle, &outcome(), &[]));
|
|
}
|