mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-21 20:18:06 +02:00
feat(tests): support partial confirmations with synthetic-fallback handling in header injection and open redirect scenarios
This commit is contained in:
parent
4c824ed543
commit
c29cf69d42
5 changed files with 80 additions and 41 deletions
|
|
@ -627,6 +627,19 @@ mod e2e_phase_08 {
|
|||
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
|
||||
}
|
||||
|
||||
/// Accepts Confirmed OR PartiallyConfirmed. A fixture whose real entry
|
||||
/// imports a framework dependency absent from the harness build env (e.g.
|
||||
/// Flask/Werkzeug) cannot be driven through its real guarded path, so the
|
||||
/// harness reaches only its synthetic sink — PartiallyConfirmed after the
|
||||
/// synthetic-fallback over-confirm fix. With the dependency present (CI
|
||||
/// image) the real drive still Confirms. Both are valid positive detections.
|
||||
fn assert_confirmed_or_partial(lang: Lang, outcome: &RunOutcome) {
|
||||
assert!(
|
||||
outcome.triggered_by.is_some() || outcome.sink_reached_no_oracle,
|
||||
"{lang:?} HEADER_INJECTION vuln must Confirm or PartiallyConfirm; got {outcome:?}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_vuln_confirms_via_run_spec() {
|
||||
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
|
||||
|
|
@ -640,7 +653,9 @@ mod e2e_phase_08 {
|
|||
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed(Lang::Python, &outcome);
|
||||
// Flask/Werkzeug absent in the harness build env → synthetic path →
|
||||
// PartiallyConfirmed (Confirmed when the dep is present in CI).
|
||||
assert_confirmed_or_partial(Lang::Python, &outcome);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -563,6 +563,48 @@ mod e2e_phase_09 {
|
|||
assert_eq!(diff.verdict, DifferentialVerdict::Confirmed);
|
||||
}
|
||||
|
||||
/// Accepts Confirmed OR PartiallyConfirmed. A fixture whose real entry
|
||||
/// imports a framework dependency absent from the harness build env
|
||||
/// (Symfony / Flask / express, …) cannot be driven through its real guarded
|
||||
/// path, so the harness reaches only its synthetic sink. After the
|
||||
/// synthetic-fallback over-confirm fix that yields PartiallyConfirmed
|
||||
/// (sink-reachable, exploit unproven) rather than a Confirmed claiming
|
||||
/// exploitation of guarded code that never ran. With the dependency present
|
||||
/// (CI image) the real drive still Confirms. Both are valid positive
|
||||
/// detections; only a clean NotConfirmed/Unsupported is a miss.
|
||||
fn assert_confirmed_or_partial(lang: Lang, outcome: &RunOutcome) {
|
||||
assert!(
|
||||
outcome.triggered_by.is_some() || outcome.sink_reached_no_oracle,
|
||||
"{lang:?} OPEN_REDIRECT vuln must Confirm or PartiallyConfirm; got {outcome:?}",
|
||||
);
|
||||
}
|
||||
|
||||
/// OOB-loopback variant tolerant of the synthetic fallback: the nonce
|
||||
/// callback is still followed and recorded (infra signal), but when the
|
||||
/// real entry could not be driven (dependency absent → synthetic path) the
|
||||
/// verdict is PartiallyConfirmed rather than the self-confirming
|
||||
/// ConfirmedProvenOob — the synthetic sink cannot prove the guarded code is
|
||||
/// exploitable. With the dependency present the real drive promotes to
|
||||
/// ConfirmedProvenOob.
|
||||
fn assert_oob_recorded_or_partial(outcome: &RunOutcome, label: &str) {
|
||||
let oob_attempt = outcome
|
||||
.attempts
|
||||
.iter()
|
||||
.find(|a| a.payload_label == label)
|
||||
.unwrap_or_else(|| panic!("OOB payload {label:?} must run; outcome={outcome:?}"));
|
||||
assert!(
|
||||
oob_attempt.outcome.oob_callback_seen,
|
||||
"harness must follow captured Location URL so OOB listener records the nonce; got {oob_attempt:?}",
|
||||
);
|
||||
match outcome.differential.as_ref() {
|
||||
Some(diff) => assert_eq!(diff.verdict, DifferentialVerdict::ConfirmedProvenOob),
|
||||
None => assert!(
|
||||
outcome.sink_reached_no_oracle,
|
||||
"synthetic-fallback OOB run must PartiallyConfirm (not self-confirm); got {outcome:?}",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_vuln_confirms_via_run_spec() {
|
||||
let Some(outcome) = run(Lang::Java, "Vuln.java", "run") else {
|
||||
|
|
@ -576,7 +618,7 @@ mod e2e_phase_09 {
|
|||
let Some(outcome) = run(Lang::Python, "vuln.py", "run") else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed(Lang::Python, &outcome);
|
||||
assert_confirmed_or_partial(Lang::Python, &outcome);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -584,17 +626,7 @@ mod e2e_phase_09 {
|
|||
let Some(outcome) = run(Lang::Php, "vuln.php", "run") else {
|
||||
return;
|
||||
};
|
||||
// The fixture's real entry imports Symfony `RedirectResponse`, which is
|
||||
// absent from the harness build env, so the eval/invoke fails and the
|
||||
// harness reaches only its synthetic sink. After the synthetic-fallback
|
||||
// over-confirm fix that yields PartiallyConfirmed (sink-reachable,
|
||||
// exploit unproven) rather than a Confirmed claiming exploitation of
|
||||
// guarded code that never executed. With Symfony present (CI image) the
|
||||
// real drive still Confirms. Both are valid positive detections.
|
||||
assert!(
|
||||
outcome.triggered_by.is_some() || outcome.sink_reached_no_oracle,
|
||||
"PHP OPEN_REDIRECT vuln must Confirm or PartiallyConfirm; got {outcome:?}",
|
||||
);
|
||||
assert_confirmed_or_partial(Lang::Php, &outcome);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -610,7 +642,7 @@ mod e2e_phase_09 {
|
|||
let Some(outcome) = run(Lang::JavaScript, "vuln.js", "run") else {
|
||||
return;
|
||||
};
|
||||
assert_confirmed(Lang::JavaScript, &outcome);
|
||||
assert_confirmed_or_partial(Lang::JavaScript, &outcome);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -734,7 +766,7 @@ mod e2e_phase_09 {
|
|||
let Some(outcome) = run_oob(Lang::Python, "vuln.py", "run") else {
|
||||
return;
|
||||
};
|
||||
assert_oob_recorded(&outcome, "open-redirect-python-oob-nonce");
|
||||
assert_oob_recorded_or_partial(&outcome, "open-redirect-python-oob-nonce");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -742,7 +774,7 @@ mod e2e_phase_09 {
|
|||
let Some(outcome) = run_oob(Lang::JavaScript, "vuln.js", "run") else {
|
||||
return;
|
||||
};
|
||||
assert_oob_recorded(&outcome, "open-redirect-js-oob-nonce");
|
||||
assert_oob_recorded_or_partial(&outcome, "open-redirect-js-oob-nonce");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -758,29 +790,7 @@ mod e2e_phase_09 {
|
|||
let Some(outcome) = run_oob(Lang::Php, "vuln.php", "run") else {
|
||||
return;
|
||||
};
|
||||
// The OOB nonce URL is still followed and recorded (infra signal), but
|
||||
// because the fixture's real entry (Symfony `RedirectResponse`) can't be
|
||||
// driven in this env, the harness reaches only its synthetic sink. After
|
||||
// the over-confirm fix a synthetic sink hit no longer self-confirms via
|
||||
// the OOB nonce (that would confirm code whose guard never ran) — it
|
||||
// PartiallyConfirms instead. With Symfony present the real drive promotes
|
||||
// to ConfirmedProvenOob.
|
||||
let oob_attempt = outcome
|
||||
.attempts
|
||||
.iter()
|
||||
.find(|a| a.payload_label == "open-redirect-php-oob-nonce")
|
||||
.unwrap_or_else(|| panic!("OOB payload must run; outcome={outcome:?}"));
|
||||
assert!(
|
||||
oob_attempt.outcome.oob_callback_seen,
|
||||
"harness must follow captured Location URL so OOB listener records the nonce; got {oob_attempt:?}",
|
||||
);
|
||||
match outcome.differential.as_ref() {
|
||||
Some(diff) => assert_eq!(diff.verdict, DifferentialVerdict::ConfirmedProvenOob),
|
||||
None => assert!(
|
||||
outcome.sink_reached_no_oracle,
|
||||
"synthetic-fallback OOB run must PartiallyConfirm (not self-confirm); got {outcome:?}",
|
||||
),
|
||||
}
|
||||
assert_oob_recorded_or_partial(&outcome, "open-redirect-php-oob-nonce");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue