mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-18 21:21:03 +02:00
feat(dynamic): enhance corpus sync script with improved payload parsing, registry checks, and expanded validation logic
This commit is contained in:
parent
467d41dcfb
commit
8ee6e3af7c
22 changed files with 810 additions and 230 deletions
|
|
@ -2544,6 +2544,37 @@ pub(super) fn def_use(
|
|||
}
|
||||
}
|
||||
}
|
||||
// Java `enhanced_for_statement` binds the loop variable on the
|
||||
// `name` field and the iterable on the `value` field; Ruby's
|
||||
// `for x in coll` uses `pattern`/`value`. Neither uses the
|
||||
// JS/Python `left`/`right` convention, so without this mapping
|
||||
// the loop binding was never recorded as a define and taint on
|
||||
// the iterable could not reach the loop variable (OWASP's
|
||||
// dominant `for (Cookie c : req.getCookies())` shape).
|
||||
if left.is_none() && right.is_none() {
|
||||
if let Some(v) = ast.child_by_field_name("value") {
|
||||
left = ast
|
||||
.child_by_field_name("name")
|
||||
.or_else(|| ast.child_by_field_name("pattern"));
|
||||
right = Some(v);
|
||||
}
|
||||
}
|
||||
// PHP `foreach ($coll as $v)` / `foreach ($coll as $k => $v)`:
|
||||
// the iterable and binding are unnamed children separated by the
|
||||
// `as` keyword (only `body` is a named field). Map the binding
|
||||
// onto `left` and the iterable onto `right` so the shared
|
||||
// define/use logic below records the loop variable.
|
||||
if left.is_none() && right.is_none() && ast.kind() == "foreach_statement" {
|
||||
let mut cursor = ast.walk();
|
||||
let kids: Vec<Node> = ast.children(&mut cursor).collect();
|
||||
if let Some(as_pos) = kids.iter().position(|c| c.kind() == "as") {
|
||||
right = kids[..as_pos].iter().rev().find(|c| c.is_named()).copied();
|
||||
left = kids[as_pos + 1..]
|
||||
.iter()
|
||||
.find(|c| c.is_named() && lookup(lang, c.kind()) != Kind::Block)
|
||||
.copied();
|
||||
}
|
||||
}
|
||||
if left.is_none() && right.is_none() {
|
||||
// C-style for, defer to default ident collection.
|
||||
let mut idents = Vec::new();
|
||||
|
|
|
|||
|
|
@ -2067,6 +2067,32 @@ fn is_binary_expr_kind(kind: &str, lang: &str) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Classification text for a for-each loop's iterable expression.
|
||||
///
|
||||
/// Subscript / index iterables (`$_GET['x']`, `params[:list]`, `arr[i]`)
|
||||
/// classify on their **base object**: taint sources are keyed on the base
|
||||
/// name (`$_GET`, `params`), and the trailing index would otherwise break
|
||||
/// the word-boundary suffix match in `classify`. Non-subscript iterables
|
||||
/// (method calls, member chains, bare identifiers) use their full text.
|
||||
fn iterable_label_text(iter: Node, code: &[u8]) -> Option<String> {
|
||||
if matches!(
|
||||
iter.kind(),
|
||||
"subscript_expression" | "subscript" | "index_expression" | "element_reference"
|
||||
) {
|
||||
let base = iter
|
||||
.child_by_field_name("object")
|
||||
.or_else(|| iter.child_by_field_name("operand"))
|
||||
.or_else(|| iter.child_by_field_name("value"))
|
||||
.or_else(|| iter.child(0));
|
||||
if let Some(b) = base
|
||||
&& let Some(t) = text_of(b, code)
|
||||
{
|
||||
return Some(t);
|
||||
}
|
||||
}
|
||||
text_of(iter, code)
|
||||
}
|
||||
|
||||
/// Create a node in one short borrow and optionally attach a taint label.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) fn push_node<'a>(
|
||||
|
|
@ -2208,6 +2234,51 @@ pub(super) fn push_node<'a>(
|
|||
text = iter_text;
|
||||
}
|
||||
|
||||
// Java `for (T x : iter)`: tree-sitter-java emits `enhanced_for_statement`
|
||||
// with the iterable on the `value` field. Classify against the iterable
|
||||
// text so a source-returning call (`req.getCookies()`,
|
||||
// `req.getParameterValues(..)`) lights up a Source on the loop node and
|
||||
// the loop binding inherits its taint — the same loop-binding-inherits-
|
||||
// iterator-taint contract the JS/Python rewrites above provide. The
|
||||
// loop variable itself is recorded as a define by `def_use`'s Kind::For
|
||||
// arm (via the `name`/`value` mapping), so the Source-labeled loop node
|
||||
// taints the binding directly.
|
||||
if lang == "java"
|
||||
&& ast.kind() == "enhanced_for_statement"
|
||||
&& let Some(value) = ast.child_by_field_name("value")
|
||||
&& let Some(iter_text) = iterable_label_text(value, code)
|
||||
{
|
||||
text = iter_text;
|
||||
}
|
||||
|
||||
// PHP `foreach ($iter as $v)` / `foreach ($iter as $k => $v)`: the
|
||||
// iterable is the named child immediately preceding the `as` keyword
|
||||
// (only `body` is a named field). Classify against the iterable text so
|
||||
// a superglobal/source iterable (`$_GET[..]`, `$_POST[..]`) taints the
|
||||
// loop binding, matching the JS/Python/Java rewrites.
|
||||
if lang == "php" && ast.kind() == "foreach_statement" {
|
||||
let mut cursor = ast.walk();
|
||||
let kids: Vec<Node> = ast.children(&mut cursor).collect();
|
||||
if let Some(as_pos) = kids.iter().position(|c| c.kind() == "as")
|
||||
&& let Some(iter_node) = kids[..as_pos].iter().rev().find(|c| c.is_named()).copied()
|
||||
&& let Some(iter_text) = iterable_label_text(iter_node, code)
|
||||
{
|
||||
text = iter_text;
|
||||
}
|
||||
}
|
||||
|
||||
// Ruby `for x in coll`: tree-sitter-ruby's `for` node carries the
|
||||
// iterable on the `value` field. (The idiomatic `coll.each { |x| }`
|
||||
// form is a method call with a block and is handled by the call/block
|
||||
// machinery, not here.)
|
||||
if lang == "ruby"
|
||||
&& ast.kind() == "for"
|
||||
&& let Some(value) = ast.child_by_field_name("value")
|
||||
&& let Some(iter_text) = iterable_label_text(value, code)
|
||||
{
|
||||
text = iter_text;
|
||||
}
|
||||
|
||||
// If this is a declaration/expression wrapper or an assignment that
|
||||
// *contains* a call, prefer the first inner call identifier instead of
|
||||
// the whole line. Track the inner call's byte span so we can populate
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue