mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-12 19:55:14 +02:00
feat(tests): support partial confirmations with synthetic-fallback handling in header injection and open redirect scenarios
This commit is contained in:
parent
4c824ed543
commit
c29cf69d42
5 changed files with 80 additions and 41 deletions
|
|
@ -2548,9 +2548,9 @@ pub fn emit_open_redirect_harness(spec: &HarnessSpec) -> HarnessSource {
|
|||
};
|
||||
|
||||
let invoke_via_fixture = if uses_node_writer {
|
||||
"const captured = nyxRedirectViaFixture(payload);\nif (Array.isArray(captured)) {\n const [location, requestHost] = captured;\n nyxRedirectProbe(location, requestHost);\n nyxFollowLocation(location);\n console.log('__NYX_SINK_HIT__');\n console.log(JSON.stringify({ location: location, request_host: requestHost }));\n} else {\n // Synthetic fallback — fixture import / call failed.\n const requestHost = 'example.com';\n const location = payload;\n nyxRedirectProbe(location, requestHost);\n nyxFollowLocation(location);\n console.log('__NYX_SINK_HIT__');\n console.log(JSON.stringify({ location: location, request_host: requestHost }));\n}\n"
|
||||
"const captured = nyxRedirectViaFixture(payload);\nif (Array.isArray(captured)) {\n const [location, requestHost] = captured;\n nyxRedirectProbe(location, requestHost);\n nyxFollowLocation(location);\n console.log('__NYX_SINK_HIT__');\n console.log(JSON.stringify({ location: location, request_host: requestHost }));\n} else {\n // Synthetic fallback — fixture import / call failed. The real redirect\n // surface (host allowlist / path guard) never ran, so the synthetic marker\n // routes the runner to PartiallyConfirmed instead of an OOB self-confirm.\n const requestHost = 'example.com';\n const location = payload;\n nyxRedirectProbe(location, requestHost);\n nyxFollowLocation(location);\n console.log('__NYX_SINK_HIT__');\n console.log('__NYX_SYNTHETIC_FALLBACK__');\n console.log(JSON.stringify({ location: location, request_host: requestHost }));\n}\n"
|
||||
} else {
|
||||
"const requestHost = 'example.com';\nconst location = payload;\nnyxRedirectProbe(location, requestHost);\nnyxFollowLocation(location);\nconsole.log('__NYX_SINK_HIT__');\nconsole.log(JSON.stringify({ location: location, request_host: requestHost }));\n"
|
||||
"const requestHost = 'example.com';\nconst location = payload;\nnyxRedirectProbe(location, requestHost);\nnyxFollowLocation(location);\nconsole.log('__NYX_SINK_HIT__');\nconsole.log('__NYX_SYNTHETIC_FALLBACK__');\nconsole.log(JSON.stringify({ location: location, request_host: requestHost }));\n"
|
||||
};
|
||||
|
||||
let body = format!(
|
||||
|
|
@ -2784,6 +2784,11 @@ const payload = process.env.NYX_PAYLOAD || '';
|
|||
// lodash.merge can throw on weird inputs; the canary observation
|
||||
// already wrote any probe before the throw.
|
||||
}
|
||||
// This block drove the sink DIRECTLY, bypassing any caller-side mitigation
|
||||
// (the fixture's own entry could not be driven). A canary observation here
|
||||
// therefore does not prove the guarded code is exploitable, so the runner
|
||||
// must downgrade to PartiallyConfirmed rather than Confirm.
|
||||
console.log('__NYX_SYNTHETIC_FALLBACK__');
|
||||
"#;
|
||||
|
||||
let tail = r#"console.log('__NYX_SINK_HIT__');
|
||||
|
|
|
|||
|
|
@ -3456,6 +3456,9 @@ def _nyx_header_probe(name, value):
|
|||
value = payload
|
||||
_nyx_header_probe(name, value)
|
||||
print("__NYX_SINK_HIT__", flush=True)
|
||||
# Synthetic sink: the real header surface (and its guards) never ran, so
|
||||
# the runner downgrades this to PartiallyConfirmed rather than Confirm.
|
||||
print("__NYX_SYNTHETIC_FALLBACK__", flush=True)
|
||||
sys.stdout.write(json.dumps({{"name": name, "value": value}}) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
|
@ -3607,6 +3610,10 @@ def _nyx_follow_location(location):
|
|||
_nyx_redirect_probe(location, request_host)
|
||||
_nyx_follow_location(location)
|
||||
print("__NYX_SINK_HIT__", flush=True)
|
||||
# Synthetic sink: the real redirect surface (host allowlist / path guard)
|
||||
# never ran, so the runner downgrades to PartiallyConfirmed rather than an
|
||||
# OOB self-confirm.
|
||||
print("__NYX_SYNTHETIC_FALLBACK__", flush=True)
|
||||
sys.stdout.write(json.dumps({{"location": location, "request_host": request_host}}) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
|
|
|||
|
|
@ -8417,6 +8417,7 @@ fn try_curl_url_propagation(
|
|||
/// sets `const_values: Some(&callee_body.opt.const_values)` on the child
|
||||
/// transfer, so callee-local constants are resolved.
|
||||
/// - Unknown / non-integer / out-of-bounds: falls back to `HeapSlot::Elements`.
|
||||
///
|
||||
/// Map a proven constant index/key to its precise `HeapSlot`, or `None`
|
||||
/// (caller falls back to `HeapSlot::Elements`).
|
||||
///
|
||||
|
|
@ -8490,6 +8491,7 @@ fn resolve_container_index(index_val: SsaValue, transfer: &SsaTaintTransfer) ->
|
|||
/// 2. the parallel `arg_string_literals` slot (a *literal* index/key, e.g.
|
||||
/// `map.get("keyB")`, which carries no SSA value because it is not a
|
||||
/// variable — the dominant OWASP shape).
|
||||
///
|
||||
/// Otherwise returns `HeapSlot::Elements`.
|
||||
fn resolve_op_slot(
|
||||
index_arg: Option<usize>,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue