Replace eval/exec in dependent value resolution with safe resolver - #1689
Replace eval/exec in dependent value resolution with safe resolver#1689fthsrbst wants to merge 1 commit into
Conversation
Module YAML content reached eval() in dependent-value resolution (find_and_replace_dependent_values, replace_dependent_response) and exec() in responsetime condition matching. A shared resolver now accepts only chains of integer or quoted-string subscripts into one root object and rejects everything else; responsetime comparisons use an operator map. Fixes OWASP#1651 Signed-off-by: Fatih Serbest <fatihxserbest@gmail.com>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Summary by CodeRabbit
WalkthroughThe change removes dynamic evaluation from dependent-value and response-time handling. It adds restricted AST-based expression resolution, recursive nested-value replacement, explicit response-time operator mapping, and tests for valid, invalid, and hostile inputs. ChangesSafe dependency and response handling
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: ✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Contribution validation failed:
|
There was a problem hiding this comment.
Pull request overview
This pull request removes unsafe YAML-driven eval()/exec() usage through validated resolution and explicit operator dispatch.
Changes:
- Adds AST-validated dependent-expression resolution.
- Replaces dynamic response-time comparisons with an operator map.
- Adds security, regression, compatibility, grammar, and operator tests.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
tests/test_yaml_schema_and_regex.py |
Validates safe module expression grammar. |
tests/core/utils/test_common.py |
Tests resolver behavior and hostile inputs. |
tests/core/lib/test_user_added_http_headers.py |
Tests response-time operators. |
tests/core/lib/test_base.py |
Tests dependent-value resolution and regressions. |
nettacker/core/utils/common.py |
Implements safe expression parsing and resolution. |
nettacker/core/lib/http.py |
Replaces response-time exec() with operator dispatch. |
nettacker/core/lib/base.py |
Integrates safe dependent-value replacement. |
Suppressed comments (2)
nettacker/core/utils/common.py:96
- Using
result.replacefor each match can corrupt longer chains when one expression is a prefix of another. For example, inroot['a'] root['a']['b'], the shorter match is replaced first and turns the second occurrence into<value>['b'], so the longer expression is never resolved. Replace matches in one pass over the original string (optionally caching by match text) so each match is resolved against its original span.
for expression in sorted(set(find_dependent_expressions(text, root_name))):
try:
rendered = renderer(resolve_dependent_value(expression, root_name, root_value))
except Exception:
rendered = default
tests/test_yaml_schema_and_regex.py:305
- This matcher can accept only a safe prefix of an unsafe chain. For example,
dependent_on_temp_event[0][__import__('os').system('true')]matchesdependent_on_temp_event[0];re.subthen removes that prefix, so the assertion below never seesroot[and the unsafe YAML passes. The per-module guard therefore does not enforce the stated grammar for chained hostile payloads; validate complete occurrences (or reject allowed matches followed by another subscript/attribute) and add this regression case.
DEPENDENT_EXPRESSION_PATTERN = re.compile(
r"dependent_on_temp_event(?:\[-?\d+\]|\['[^']*'\]|\[\"[^\"]*\"\])+"
)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Module YAML content reached
eval()during dependent-value resolution (find_and_replace_dependent_valuesincore/lib/base.py,replace_dependent_responseincore/utils/common.py) andexec()built via.format()forresponsetimeconditions. Since\S+captures arbitrary Python source, any module YAML could execute code (the PoC in #1651 runs__import__before failing). Module files are shared data, so data-that-executes-code is worth closing.What changed
nettacker/core/utils/common.py: a strict finder regex accepts only<root>[<int>|'<key'>|"key"]...chains, an AST walk re-validates every slice as anint/strconstant (bools rejected), and resolution is a plain index-path walk. Anything else is never evaluated.evalsites inbase.pyrewritten on top of it; the twoglobals().update(locals())calls are gone.replace_dependent_responseuses the same resolver with the original" ".join(...)rendering and error fallback.responsetimebranch uses anoperatormap instead ofexec.Compatibility
All 15 unique
dependent_on_temp_event/response_dependentexpressions shipped undernettacker/modulesresolve to identical values before and after (verified expression-by-expression), including mid-string contexts insubdomain.yaml,waf.yaml,log4j_cve_2021_44228.yamlandgeoserver_cve_2024_36401.yaml. A per-module test now asserts every dependent-value expression in shipped YAML stays inside the safe grammar.Fixes #1651
Tests
tests/core/lib/test_base.py: shipped shapes, error fallback, hostile-payload inertness, frame-local leak regression.pytest tests/: 511 passed; the 3 failures reproduce identically on a clean checkout under macOS and are unrelated.Commits are DCO-signed.