mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-15 20:05:13 +02:00
[pitboss] phase 11: Track J.9 + Track L.9 — CRYPTO, JSON_PARSE, UNAUTHORIZED_ID, DATA_EXFIL corpora
This commit is contained in:
parent
61a9e4e5df
commit
6784d73e25
85 changed files with 2508 additions and 30 deletions
|
|
@ -315,6 +315,54 @@ pub enum ProbePredicate {
|
|||
/// [`ProbeKind::PrototypePollution::property`].
|
||||
canary: &'static str,
|
||||
},
|
||||
/// Phase 11 (Track J.9): CRYPTO weak-key entropy predicate.
|
||||
///
|
||||
/// Fires when at least one drained probe carries
|
||||
/// [`ProbeKind::WeakKey`] whose `key_int` is strictly less than
|
||||
/// `2^max_bits` — i.e. the produced key actually fits inside the
|
||||
/// search-space budget the predicate models. A benign control
|
||||
/// generated by a cryptographically strong RNG produces a
|
||||
/// `key_int` whose magnitude trivially exceeds the budget, so the
|
||||
/// predicate stays clear.
|
||||
///
|
||||
/// Cross-cutting in the same sense as
|
||||
/// [`Self::DeserializeGadgetInvoked`] /
|
||||
/// [`Self::XxeEntityExpanded`] — evaluated across every drained
|
||||
/// probe rather than against a single record.
|
||||
WeakKeyEntropy {
|
||||
/// Maximum effective-entropy budget (in bits) the produced
|
||||
/// key may consume before the oracle fires. 16 matches the
|
||||
/// brief's "fits in a 16-bit search space" oracle.
|
||||
max_bits: u32,
|
||||
},
|
||||
/// Phase 11 (Track J.9): UNAUTHORIZED_ID IDOR boundary predicate.
|
||||
///
|
||||
/// Fires when at least one drained probe carries
|
||||
/// [`ProbeKind::IdorAccess`] whose `caller_id` differs from
|
||||
/// `owner_id`. Cross-cutting in the same sense as
|
||||
/// [`Self::DeserializeGadgetInvoked`] /
|
||||
/// [`Self::XxeEntityExpanded`] — evaluated across every drained
|
||||
/// probe rather than against a single record.
|
||||
IdorBoundaryCrossed,
|
||||
/// Phase 11 (Track J.9): DATA_EXFIL outbound-host predicate.
|
||||
///
|
||||
/// Fires when at least one drained probe carries
|
||||
/// [`ProbeKind::OutboundNetwork`] whose `host` falls outside the
|
||||
/// `allowlist`. Hosts are compared case-insensitively; the
|
||||
/// canonical allowlist for benign controls is `&["127.0.0.1",
|
||||
/// "localhost"]` so a vulnerable host that exfiltrates to
|
||||
/// `attacker.test` resolves off-list and confirms.
|
||||
///
|
||||
/// Cross-cutting in the same sense as
|
||||
/// [`Self::DeserializeGadgetInvoked`] /
|
||||
/// [`Self::XxeEntityExpanded`] — evaluated across every drained
|
||||
/// probe rather than against a single record.
|
||||
OutboundHostNotIn {
|
||||
/// Allowlist of permitted egress hosts (e.g.
|
||||
/// `&["127.0.0.1", "localhost"]`). A probe whose `host`
|
||||
/// matches any entry is treated as same-origin.
|
||||
allowlist: &'static [&'static str],
|
||||
},
|
||||
/// Phase 06 (Track J.4) / Phase 07 (Track J.5): result-count
|
||||
/// predicate shared by LDAP-filter and XPath-expression injection.
|
||||
///
|
||||
|
|
@ -524,6 +572,35 @@ pub fn oracle_fired_with_stubs(
|
|||
if !canary_ok {
|
||||
return false;
|
||||
}
|
||||
// Phase 11 (Track J.9): CRYPTO weak-key, UNAUTHORIZED_ID
|
||||
// IDOR, DATA_EXFIL outbound-host cross-cutting predicates.
|
||||
let weak_key_ok = cross.iter().all(|p| match p {
|
||||
ProbePredicate::WeakKeyEntropy { max_bits } => {
|
||||
probes_satisfy_weak_key(probes, *max_bits)
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
if !weak_key_ok {
|
||||
return false;
|
||||
}
|
||||
let idor_ok = cross.iter().all(|p| match p {
|
||||
ProbePredicate::IdorBoundaryCrossed => {
|
||||
probes_satisfy_idor_crossed(probes)
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
if !idor_ok {
|
||||
return false;
|
||||
}
|
||||
let outbound_ok = cross.iter().all(|p| match p {
|
||||
ProbePredicate::OutboundHostNotIn { allowlist } => {
|
||||
probes_satisfy_outbound_off_list(probes, allowlist)
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
if !outbound_ok {
|
||||
return false;
|
||||
}
|
||||
// Phase 04 (Track J.2): SSTI render-equality cross-cutting
|
||||
// predicates. Each `TemplateEvalEqual { expected }` consults
|
||||
// the captured stdout body — see [`stdout_template_equals`].
|
||||
|
|
@ -558,7 +635,10 @@ pub fn oracle_fired_with_stubs(
|
|||
| ProbeKind::Xpath { .. }
|
||||
| ProbeKind::HeaderEmit { .. }
|
||||
| ProbeKind::Redirect { .. }
|
||||
| ProbeKind::PrototypePollution { .. } => false,
|
||||
| ProbeKind::PrototypePollution { .. }
|
||||
| ProbeKind::WeakKey { .. }
|
||||
| ProbeKind::IdorAccess { .. }
|
||||
| ProbeKind::OutboundNetwork { .. } => false,
|
||||
}),
|
||||
Oracle::OutputContains(needle) => {
|
||||
let nb = needle.as_bytes();
|
||||
|
|
@ -588,6 +668,9 @@ fn is_cross_cutting(pred: &ProbePredicate) -> bool {
|
|||
| ProbePredicate::HeaderInjected { .. }
|
||||
| ProbePredicate::RedirectHostNotIn { .. }
|
||||
| ProbePredicate::PrototypeCanaryTouched { .. }
|
||||
| ProbePredicate::WeakKeyEntropy { .. }
|
||||
| ProbePredicate::IdorBoundaryCrossed
|
||||
| ProbePredicate::OutboundHostNotIn { .. }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -624,6 +707,11 @@ fn cross_cutting_satisfied(pred: &ProbePredicate, stub_events: &[StubEvent]) ->
|
|||
// log* rather than stub events; evaluated separately in
|
||||
// [`probes_satisfy_prototype_canary`] below.
|
||||
ProbePredicate::PrototypeCanaryTouched { .. } => true,
|
||||
// Phase 11 (Track J.9) cross-cutters are all probe-log
|
||||
// backed and evaluated by their dedicated helpers below.
|
||||
ProbePredicate::WeakKeyEntropy { .. } => true,
|
||||
ProbePredicate::IdorBoundaryCrossed => true,
|
||||
ProbePredicate::OutboundHostNotIn { .. } => true,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
|
@ -744,6 +832,60 @@ fn probes_satisfy_prototype_canary(probes: &[SinkProbe], canary: &str) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
/// True when at least one drained probe is a [`ProbeKind::WeakKey`]
|
||||
/// record whose `key_int` is strictly less than `2^max_bits`. Powers
|
||||
/// [`ProbePredicate::WeakKeyEntropy`] (Phase 11 — Track J.9).
|
||||
///
|
||||
/// `max_bits >= 64` is treated as "never fires" — a 64-bit key
|
||||
/// trivially exceeds any sub-search-space budget once you cap the
|
||||
/// integer view at `u64`. The brief calls for a 16-bit search-space
|
||||
/// oracle, so the real threshold sits far below `2^64`.
|
||||
fn probes_satisfy_weak_key(probes: &[SinkProbe], max_bits: u32) -> bool {
|
||||
if max_bits == 0 {
|
||||
return false;
|
||||
}
|
||||
if max_bits >= 64 {
|
||||
return probes
|
||||
.iter()
|
||||
.any(|p| matches!(p.kind, ProbeKind::WeakKey { .. }));
|
||||
}
|
||||
let budget = 1u64 << max_bits;
|
||||
probes.iter().any(|p| match &p.kind {
|
||||
ProbeKind::WeakKey { key_int } => *key_int < budget,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// True when at least one drained probe is a
|
||||
/// [`ProbeKind::IdorAccess`] record whose `caller_id` differs from
|
||||
/// `owner_id`. Powers
|
||||
/// [`ProbePredicate::IdorBoundaryCrossed`] (Phase 11 — Track J.9).
|
||||
fn probes_satisfy_idor_crossed(probes: &[SinkProbe]) -> bool {
|
||||
probes.iter().any(|p| match &p.kind {
|
||||
ProbeKind::IdorAccess { caller_id, owner_id } => caller_id != owner_id,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// True when at least one drained probe is a
|
||||
/// [`ProbeKind::OutboundNetwork`] record whose `host` falls outside
|
||||
/// `allowlist` (case-insensitive). Powers
|
||||
/// [`ProbePredicate::OutboundHostNotIn`] (Phase 11 — Track J.9).
|
||||
fn probes_satisfy_outbound_off_list(probes: &[SinkProbe], allowlist: &[&str]) -> bool {
|
||||
probes.iter().any(|p| match &p.kind {
|
||||
ProbeKind::OutboundNetwork { host } => {
|
||||
let h = host.trim().to_ascii_lowercase();
|
||||
if h.is_empty() {
|
||||
return false;
|
||||
}
|
||||
!allowlist
|
||||
.iter()
|
||||
.any(|a| h == a.trim().to_ascii_lowercase())
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` when `location` redirects to a host that is neither
|
||||
/// `request_host` nor any entry of `allowlist`. Crate-visible so the
|
||||
/// in-crate predicate above and the colocated tests can share one
|
||||
|
|
@ -851,7 +993,10 @@ fn probe_satisfies_one(probe: &SinkProbe, pred: &ProbePredicate) -> bool {
|
|||
| ProbePredicate::QueryResultCountGreaterThan { .. }
|
||||
| ProbePredicate::HeaderInjected { .. }
|
||||
| ProbePredicate::RedirectHostNotIn { .. }
|
||||
| ProbePredicate::PrototypeCanaryTouched { .. } => true,
|
||||
| ProbePredicate::PrototypeCanaryTouched { .. }
|
||||
| ProbePredicate::WeakKeyEntropy { .. }
|
||||
| ProbePredicate::IdorBoundaryCrossed
|
||||
| ProbePredicate::OutboundHostNotIn { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -880,7 +1025,10 @@ pub fn probe_crash_signal(probe: &SinkProbe) -> Option<Signal> {
|
|||
| ProbeKind::Xpath { .. }
|
||||
| ProbeKind::HeaderEmit { .. }
|
||||
| ProbeKind::Redirect { .. }
|
||||
| ProbeKind::PrototypePollution { .. } => None,
|
||||
| ProbeKind::PrototypePollution { .. }
|
||||
| ProbeKind::WeakKey { .. }
|
||||
| ProbeKind::IdorAccess { .. }
|
||||
| ProbeKind::OutboundNetwork { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue