[pitboss] phase 11: Track J.9 + Track L.9 — CRYPTO, JSON_PARSE, UNAUTHORIZED_ID, DATA_EXFIL corpora

This commit is contained in:
pitboss 2026-05-18 09:37:37 -05:00
parent 61a9e4e5df
commit 6784d73e25
85 changed files with 2508 additions and 30 deletions

View file

@ -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,
}
}