High

Python eval() Sink Exploitation - RunGptLLM Command Injection

Some AI agent frameworks pass text returned by a language model straight into Python's eval() function. If an attacker controls that model output, they can smuggle in code disguised as a normal expression and get it to run with the same privileges as the agent.

How the attack works

The LlamaIndex RunGptLLM class reads streaming (SSE) responses from a RunGpt model host and passes them to eval() without checking their content. Because eval() only accepts a single expression, not full statements, an attacker cannot use ordinary code like 'import os; os.system(...)'. Instead they use expression-only tricks such as __import__('os').system(...), getattr-based lookups, class-hierarchy walking to reach subclasses, or a base64-decode-then-exec chain. When that payload also launches a shell or second interpreter, its distinctive gadget syntax shows up in the resulting process command line. This rule looks for that specific syntax pattern together with evidence that an OS-level process was actually spawned.

Netzilo detection

Netzilo reports this behaviour when it is observed.

Signature ID
b3f7a91c-4d2e-4a06-9f18-6c5d0e83a1b7
Severity
High

Why it matters

An attacker who can influence the model's response text can achieve arbitrary command execution on the system running the agent, using whatever privileges that process has.

What you can do

  • Never pass model-generated text directly to eval() or exec(); use a restricted parser or a strict output schema instead.
  • Update or patch LlamaIndex components that use eval() on RunGpt (or similar) SSE responses, per GHSA-pw38-xv9x-h8ch.
  • Run agent processes with the minimum privileges needed, so a successful injection has limited reach.
  • Treat any hit from this detection as a starting point for investigation, not a blocked attack — confirm whether it is a real exploit, a test fixture, or code search activity before acting.

Known benign look-alikes

  • Developers or CI jobs grepping a repository for exploit gadget strings (e.g. `grep -rn "__import__('os').system" .`, `semgrep --pattern ...`) - suppressed by filter_code_search unless the search flag itself contains an '=' sign.
  • Security regression suites for LlamaIndex/RunGpt or for eval-sink hardening that execute the exploit string as a test fixture - partially suppressed by filter_test_runner; residual hits are expected and should be baselined per repository.
  • Hand-golfed build or bootstrap one-liners such as `python -c "__import__('subprocess').run(['ls'])"`. This is a genuine residual false positive; the rule reports rather than blocks so these can be enumerated before any enforcement change.
  • Vendor installers and packagers that base64-decode and exec a bootstrap payload (matched by sel_decode_then_eval). Legitimate but worth a one-time review - the same shape is used by loader malware.
  • Shell scripts that echo an exploit string into a corpus/fixture file - suppressed by filter_echo_literal only when no pipe follows, so `echo <payload> | python` is still reported.

References