mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-12 19:55:14 +02:00
[pitboss] phase 06: Track J.4 + Track L.4 — LDAP_INJECTION corpus + LdapTemplate / python-ldap / php-ldap adapters
This commit is contained in:
parent
993bfabe28
commit
b2eeaabb09
27 changed files with 2189 additions and 18 deletions
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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan`]
|
||||
//! 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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan { 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::LdapResultCountGreaterThan { 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,
|
||||
},
|
||||
];
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use super::{cmdi, deserialize, fmt_string, path_trav, sqli, ssrf, ssti, xss, xxe};
|
||||
use super::{cmdi, deserialize, fmt_string, ldap, path_trav, sqli, ssrf, ssti, xss, xxe};
|
||||
use super::{CapCorpus, CuratedPayload, Oracle};
|
||||
use crate::dynamic::oracle::ProbePredicate;
|
||||
use crate::labels::Cap;
|
||||
|
|
@ -40,7 +40,6 @@ pub const CORPUS_UNSUPPORTED_LANG_NEUTRAL: u32 = Cap::ENV_VAR.bits()
|
|||
| Cap::CRYPTO.bits()
|
||||
| Cap::UNAUTHORIZED_ID.bits()
|
||||
| Cap::DATA_EXFIL.bits()
|
||||
| Cap::LDAP_INJECTION.bits()
|
||||
| Cap::XPATH_INJECTION.bits()
|
||||
| Cap::HEADER_INJECTION.bits()
|
||||
| Cap::OPEN_REDIRECT.bits()
|
||||
|
|
@ -69,6 +68,9 @@ const ENTRIES: &[(Cap, Lang, &[CuratedPayload])] = &[
|
|||
(Cap::XXE, Lang::Php, xxe::php::PAYLOADS),
|
||||
(Cap::XXE, Lang::Ruby, xxe::ruby::PAYLOADS),
|
||||
(Cap::XXE, Lang::Go, xxe::go::PAYLOADS),
|
||||
(Cap::LDAP_INJECTION, Lang::Java, ldap::java::PAYLOADS),
|
||||
(Cap::LDAP_INJECTION, Lang::Python, ldap::python::PAYLOADS),
|
||||
(Cap::LDAP_INJECTION, Lang::Php, ldap::php::PAYLOADS),
|
||||
];
|
||||
|
||||
/// Reserved for per-cap oracle defaults. Empty in Phase 02; populated by
|
||||
|
|
@ -278,6 +280,7 @@ mod tests {
|
|||
assert!(!payloads_for(Cap::DESERIALIZE).is_empty());
|
||||
assert!(!payloads_for(Cap::SSTI).is_empty());
|
||||
assert!(!payloads_for(Cap::XXE).is_empty());
|
||||
assert!(!payloads_for(Cap::LDAP_INJECTION).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -290,7 +293,6 @@ mod tests {
|
|||
Cap::CRYPTO,
|
||||
Cap::UNAUTHORIZED_ID,
|
||||
Cap::DATA_EXFIL,
|
||||
Cap::LDAP_INJECTION,
|
||||
Cap::XPATH_INJECTION,
|
||||
Cap::HEADER_INJECTION,
|
||||
Cap::OPEN_REDIRECT,
|
||||
|
|
@ -325,6 +327,7 @@ mod tests {
|
|||
Cap::DESERIALIZE,
|
||||
Cap::SSTI,
|
||||
Cap::XXE,
|
||||
Cap::LDAP_INJECTION,
|
||||
] {
|
||||
let has_vuln = payloads_for(cap).iter().any(|p| !p.is_benign);
|
||||
assert!(has_vuln, "{cap:?} must have at least one vuln payload");
|
||||
|
|
@ -374,6 +377,7 @@ mod tests {
|
|||
Cap::DESERIALIZE,
|
||||
Cap::SSTI,
|
||||
Cap::XXE,
|
||||
Cap::LDAP_INJECTION,
|
||||
];
|
||||
for cap in caps {
|
||||
for p in payloads_for(cap) {
|
||||
|
|
@ -398,6 +402,7 @@ mod tests {
|
|||
Cap::DESERIALIZE,
|
||||
Cap::SSTI,
|
||||
Cap::XXE,
|
||||
Cap::LDAP_INJECTION,
|
||||
];
|
||||
for cap in caps {
|
||||
for p in payloads_for(cap) {
|
||||
|
|
@ -509,6 +514,7 @@ mod tests {
|
|||
Cap::DESERIALIZE,
|
||||
Cap::SSTI,
|
||||
Cap::XXE,
|
||||
Cap::LDAP_INJECTION,
|
||||
];
|
||||
for cap in caps {
|
||||
for p in payloads_for(cap).iter().filter(|p| p.is_benign) {
|
||||
|
|
@ -677,6 +683,49 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldap_has_per_lang_slices_for_phase_06() {
|
||||
// Phase 06 (Track J.4) acceptance: LDAP_INJECTION registers
|
||||
// payloads in Java / Python / PHP and the lang-aware lookup
|
||||
// never returns empty for any of them.
|
||||
for lang in [Lang::Java, Lang::Python, Lang::Php] {
|
||||
assert!(
|
||||
!payloads_for_lang(Cap::LDAP_INJECTION, lang).is_empty(),
|
||||
"LDAP_INJECTION must have at least one payload for {lang:?}",
|
||||
);
|
||||
}
|
||||
// Rust / C / Cpp / Ruby / Go / JS / TS not yet covered.
|
||||
for lang in [
|
||||
Lang::Rust,
|
||||
Lang::C,
|
||||
Lang::Cpp,
|
||||
Lang::Ruby,
|
||||
Lang::Go,
|
||||
Lang::JavaScript,
|
||||
Lang::TypeScript,
|
||||
] {
|
||||
assert!(
|
||||
payloads_for_lang(Cap::LDAP_INJECTION, lang).is_empty(),
|
||||
"LDAP_INJECTION has unexpected payloads for {lang:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ldap_payloads_pair_benign_controls_per_lang() {
|
||||
for lang in [Lang::Java, Lang::Python, Lang::Php] {
|
||||
let slice = payloads_for_lang(Cap::LDAP_INJECTION, lang);
|
||||
let vuln = slice
|
||||
.iter()
|
||||
.find(|p| !p.is_benign)
|
||||
.expect("each lang must have an LDAP vuln payload");
|
||||
let resolved =
|
||||
super::resolve_benign_control_lang(vuln, Cap::LDAP_INJECTION, lang)
|
||||
.expect("lang-aware benign control must resolve");
|
||||
assert!(resolved.is_benign);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_payloads_pair_benign_controls_per_lang() {
|
||||
// The lang-aware resolver must find the paired benign control
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue