mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-09 19:45:13 +02:00
[pitboss/grind] deferred session-0011 (20260520T233019Z-6958)
This commit is contained in:
parent
280121607e
commit
227675021b
5 changed files with 115 additions and 8 deletions
|
|
@ -66,6 +66,29 @@ pub fn build_outcome(
|
|||
}
|
||||
}
|
||||
|
||||
/// Build a self-confirming [`DifferentialOutcome`] for OOB-nonce payloads.
|
||||
///
|
||||
/// When a payload carries
|
||||
/// [`crate::dynamic::corpus::CuratedPayload::oob_nonce_slot`] = `true` and
|
||||
/// the [`crate::dynamic::oob::OobListener`] observed the per-finding nonce
|
||||
/// callback, the OOB observation is independent network-level evidence
|
||||
/// that the sink fired. A benign URL structurally cannot hit a per-
|
||||
/// finding nonce, so no paired benign control is required. The runner
|
||||
/// emits this outcome with [`DifferentialVerdict::ConfirmedProvenOob`]
|
||||
/// in place of the usual two-payload differential rule.
|
||||
pub fn build_oob_self_confirmed_outcome(
|
||||
vuln_label: &str,
|
||||
vuln_probes: &[SinkProbe],
|
||||
) -> DifferentialOutcome {
|
||||
DifferentialOutcome {
|
||||
verdict: DifferentialVerdict::ConfirmedProvenOob,
|
||||
vuln_label: vuln_label.to_owned(),
|
||||
benign_label: String::new(),
|
||||
vuln_probes: vuln_probes.iter().map(sink_probe_to_record).collect(),
|
||||
benign_probes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sink_probe_to_record(p: &SinkProbe) -> DifferentialProbeRecord {
|
||||
use crate::dynamic::probe::ProbeArg;
|
||||
DifferentialProbeRecord {
|
||||
|
|
@ -108,6 +131,25 @@ mod tests {
|
|||
assert_eq!(evaluate(false, true), DifferentialVerdict::ReversedDifferential);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oob_self_confirmed_outcome_carries_only_vuln_trace() {
|
||||
use crate::dynamic::probe::{ProbeArg, ProbeKind, ProbeWitness, SinkProbe};
|
||||
let vuln = vec![SinkProbe {
|
||||
sink_callee: "lxml.etree.XMLParser.parse".into(),
|
||||
args: vec![ProbeArg::String("<!DOCTYPE … &xxe;".into())],
|
||||
captured_at_ns: 1,
|
||||
payload_id: "xxe-python-oob-nonce".into(),
|
||||
kind: ProbeKind::Normal,
|
||||
witness: ProbeWitness::empty(),
|
||||
}];
|
||||
let outcome = build_oob_self_confirmed_outcome("xxe-python-oob-nonce", &vuln);
|
||||
assert_eq!(outcome.verdict, DifferentialVerdict::ConfirmedProvenOob);
|
||||
assert_eq!(outcome.vuln_label, "xxe-python-oob-nonce");
|
||||
assert!(outcome.benign_label.is_empty());
|
||||
assert_eq!(outcome.vuln_probes.len(), 1);
|
||||
assert!(outcome.benign_probes.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_outcome_carries_both_traces() {
|
||||
use crate::dynamic::probe::{ProbeArg, ProbeKind, ProbeWitness, SinkProbe};
|
||||
|
|
|
|||
|
|
@ -485,8 +485,25 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result<RunOutcome,
|
|||
};
|
||||
match resolved {
|
||||
None => {
|
||||
no_benign_control = true;
|
||||
false
|
||||
// Phase 05 OOB closure: OOB-nonce payloads with
|
||||
// `benign_control = None` are structurally self-
|
||||
// confirming when the listener observed the callback.
|
||||
// A benign URL cannot hit a per-finding nonce, so the
|
||||
// OOB observation is independent network-level
|
||||
// evidence the sink fired. Skip the no-benign-control
|
||||
// downgrade and emit
|
||||
// [`DifferentialVerdict::ConfirmedProvenOob`].
|
||||
if payload.oob_nonce_slot && outcome.oob_callback_seen {
|
||||
let outcome_record = differential::build_oob_self_confirmed_outcome(
|
||||
payload.label,
|
||||
&vuln_probes,
|
||||
);
|
||||
differential_outcome = Some(outcome_record);
|
||||
true
|
||||
} else {
|
||||
no_benign_control = true;
|
||||
false
|
||||
}
|
||||
}
|
||||
Some(benign) => {
|
||||
let benign_bytes = materialise_bytes(benign, None)
|
||||
|
|
@ -512,7 +529,7 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result<RunOutcome,
|
|||
&benign_probes,
|
||||
&benign_stub_events,
|
||||
);
|
||||
let outcome_record = differential::build_outcome(
|
||||
let mut outcome_record = differential::build_outcome(
|
||||
payload.label,
|
||||
vuln_fired,
|
||||
&vuln_probes,
|
||||
|
|
@ -520,7 +537,23 @@ pub fn run_spec(spec: &HarnessSpec, opts: &SandboxOptions) -> Result<RunOutcome,
|
|||
benign_fired,
|
||||
&benign_probes,
|
||||
);
|
||||
let confirmed = outcome_record.verdict == DifferentialVerdict::Confirmed;
|
||||
// Phase 05 OOB closure: when an OOB-nonce payload also
|
||||
// carries a paired benign control, promote
|
||||
// `Confirmed` → `ConfirmedProvenOob` whenever the
|
||||
// listener observed the per-finding nonce. The
|
||||
// upgrade preserves the differential trace (benign
|
||||
// run still recorded) and surfaces the stronger
|
||||
// network-level evidence to operators.
|
||||
if outcome_record.verdict == DifferentialVerdict::Confirmed
|
||||
&& payload.oob_nonce_slot
|
||||
&& outcome.oob_callback_seen
|
||||
{
|
||||
outcome_record.verdict = DifferentialVerdict::ConfirmedProvenOob;
|
||||
}
|
||||
let confirmed = matches!(
|
||||
outcome_record.verdict,
|
||||
DifferentialVerdict::Confirmed | DifferentialVerdict::ConfirmedProvenOob
|
||||
);
|
||||
differential_outcome = Some(outcome_record);
|
||||
confirmed
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1117,6 +1117,7 @@ fn build_verdict(
|
|||
}
|
||||
}
|
||||
crate::evidence::DifferentialVerdict::Confirmed
|
||||
| crate::evidence::DifferentialVerdict::ConfirmedProvenOob
|
||||
| crate::evidence::DifferentialVerdict::NotConfirmed => VerifyResult {
|
||||
finding_id: finding_id.to_owned(),
|
||||
status: VerifyStatus::NotConfirmed,
|
||||
|
|
|
|||
|
|
@ -764,6 +764,17 @@ pub struct AttemptSummary {
|
|||
pub enum DifferentialVerdict {
|
||||
/// Vulnerable payload fired the oracle and the benign control did not.
|
||||
Confirmed,
|
||||
/// Stronger tier of [`DifferentialVerdict::Confirmed`]: in addition to
|
||||
/// the in-process oracle firing, an out-of-band callback to the
|
||||
/// per-finding nonce was observed by the
|
||||
/// [`crate::dynamic::oob::OobListener`]. Emitted when the runner
|
||||
/// exercised a payload with
|
||||
/// [`crate::dynamic::corpus::CuratedPayload::oob_nonce_slot`] = `true`
|
||||
/// and the listener saw the nonce. Such payloads are structurally
|
||||
/// self-confirming (a benign URL cannot hit a per-finding nonce), so
|
||||
/// the verdict is treated as terminal positive evidence even when
|
||||
/// `benign_control` is `None`.
|
||||
ConfirmedProvenOob,
|
||||
/// Both vulnerable and benign payloads fired the oracle — the oracle
|
||||
/// cannot discriminate; downgrade to
|
||||
/// [`InconclusiveReason::OracleCollisionSuspected`].
|
||||
|
|
|
|||
|
|
@ -557,10 +557,12 @@ mod e2e_phase_05 {
|
|||
/// is attached and the runner exercises the `xxe-<lang>-oob-nonce`
|
||||
/// payload, the parser's external-entity hook performs a real HTTP
|
||||
/// GET against the loopback nonce URL and the listener records the
|
||||
/// hit. Asserts the observation half of the Phase 05 OOB closure;
|
||||
/// the verdict-tier promotion (Confirmed → Confirmed+ProvenOob) is
|
||||
/// broader runner-rework tracked separately in
|
||||
/// `.pitboss/play/deferred.md`.
|
||||
/// hit. Asserts both halves of the Phase 05 OOB closure: the
|
||||
/// callback observation AND the verdict-tier promotion from
|
||||
/// `Confirmed` to `ConfirmedProvenOob` (the runner's
|
||||
/// `build_oob_self_confirmed_outcome` path treats the OOB-nonce
|
||||
/// payload as self-confirming since a benign URL structurally
|
||||
/// cannot hit a per-finding nonce).
|
||||
fn run_oob(lang: Lang, fixture: &str, entry_name: &str) -> Option<RunOutcome> {
|
||||
use nyx_scanner::dynamic::oob::OobListener;
|
||||
use nyx_scanner::dynamic::sandbox::NetworkPolicy;
|
||||
|
|
@ -633,6 +635,24 @@ mod e2e_phase_05 {
|
|||
oob_attempt.outcome.oob_callback_seen,
|
||||
"parser external-entity hook must fetch loopback URL so OOB listener records the nonce; got attempt={oob_attempt:?}",
|
||||
);
|
||||
// Phase 05 OOB closure: the listener observation must promote the
|
||||
// verdict tier from `Confirmed` to `ConfirmedProvenOob`. The
|
||||
// payload carries `oob_nonce_slot: true` + `benign_control: None`
|
||||
// so the runner's self-confirming path emits the upgraded verdict
|
||||
// and sets `triggered_by` on the OOB attempt itself.
|
||||
assert!(
|
||||
oob_attempt.triggered,
|
||||
"OOB attempt must mark triggered=true under the self-confirming OOB path; got attempt={oob_attempt:?}",
|
||||
);
|
||||
let diff = outcome
|
||||
.differential
|
||||
.as_ref()
|
||||
.expect("self-confirming OOB run must carry a DifferentialOutcome");
|
||||
assert_eq!(
|
||||
diff.verdict,
|
||||
DifferentialVerdict::ConfirmedProvenOob,
|
||||
"OOB callback observation must promote verdict tier; got diff={diff:?}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue