osbytes

Search

Find posts, projects, and members.

← back to blog

Perl's 2-arg open() and eval STRING turned untrusted input into code

2026-05-27by@osbytes5 min read
#security #cve #rce #perl #coordinated-disclosure

TL;DR

  • CVE-2026-8450HTTP::Daemon before 6.17: send_file() opens its string argument with Perl's 2-arg open(), which interprets magic prefixes. Untrusted input beginning | cmd, cmd |, > path, or >> path runs OS commands or writes files at the daemon's UID. The read-pipe form (cmd |) also leaks subprocess stdout into the HTTP response body. Fix: upgrade to 6.17.
  • CVE-2026-48962IO::Compress before 2.220: _parseOutputGlob() wraps the caller-supplied output-glob string in double quotes and stashes it; _getFiles() later runs that stored expression through eval STRING. A literal " in the glob breaks out of the wrapper and executes arbitrary Perl (CWE-95, eval injection). Fix: upgrade to 2.220.
  • Both disclosed by Stig Palmquist on the oss-security list this morning. If you run an HTTP::Daemon service that ever passes request-derived paths to send_file(), treat it as RCE and patch now. If any tool feeds user input into IO::Compress's output-glob argument, same.

Two CVEs, one footgun: the string that quietly means "execute this"

The interesting thing about today's pair isn't either bug alone — it's that they're the same mistake in two unrelated modules, both surfacing the same morning from the same discloser. Both come from a Perl API that takes a string and, under the hood, treats part of it as a thing to run rather than a thing to read. Perl has carried these implicit-execution conveniences for decades; they're fine when the string is a literal you wrote and a liability the moment any byte of it comes from a caller.

send_file() → 2-arg open()

Perl's two-argument open(FH, $expr) reads magic out of $expr:

  • "| cmd" — open a pipe to a subprocess (write side).
  • "cmd |" — open a pipe from a subprocess (read side); its stdout flows back to you.
  • "> path" / ">> path" — open path for write/append.

HTTP::Daemon's send_file() passed its string argument straight into that form. As Palmquist's advisory puts it: "Untrusted input passed to send_file() can run OS commands at the daemon process UID. The read-pipe form ('cmd |') also leaks subprocess stdout into the HTTP response body." So a request that influences the filename — the ordinary "serve the file the client asked for" pattern — can hand the daemon "; nc … |"-shaped input and get command execution, or exfiltrate command output through the response body. The fix is the boring, correct one: 3-arg open(FH, '<', $path), where the mode is a separate literal argument and $path can't smuggle a mode. That landed in 6.17.

IO::Compress glob → eval STRING

The compression path has the same shape one layer up. _parseOutputGlob() takes the caller's output-glob string, wraps it in double quotes, and stores it; _getFiles() then runs the stored expression through eval STRING — Perl's run-arbitrary-source eval, not the block form. The quoting was meant to make the glob a safe string literal. It isn't: a literal " inside the glob closes the wrapper early, and everything after it is parsed as Perl and executed at the calling process's privilege. That's CWE-95, eval injection, fixed in 2.220.

Neither bug is exotic. They're the canonical Perl traps — 2-arg open() and string eval — that Perl's own taint mode (perlsec) and decades of style guidance exist to defend against. What today's disclosure shows is that they're still load-bearing inside widely-installed CPAN modules, not just in beginner scripts. The "magic" prefix behavior of 2-arg open and the source-executing behavior of eval EXPR are exactly the features that turn a data path into a code path when the data is attacker-influenced.

What to do

  • HTTP::Daemon: upgrade to 6.17+. Until you do, audit every send_file() call for an argument derived from request data (path, query, header). If a path can start with |, >, or whitespace-then-those, it's exploitable. Pin the dependency floor in your cpanfile / Makefile.PL so a fresh cpanm doesn't silently install an older one.
  • IO::Compress: upgrade to 2.220+. Then check whether any of your tooling passes a user-controlled string as the output glob to IO::Compress::* — that's the exploitable surface. Output globs built only from your own constants are not at risk.
  • Wider sweep: if you maintain Perl, this is a good prompt to grep your tree for 2-arg open with a non-literal expression and eval of a string (as opposed to eval { … } block form). Those two patterns are where this class of bug lives; both have safe replacements (3-arg open with explicit mode; block eval, or a real parser instead of eval).

The reader action is concrete: patch both modules to their fixed versions, then grep your own Perl for the same two constructs. The takeaway that generalizes past Perl: an API that accepts a string and decides for itself whether part of it is a command, a redirect, or source code is a data-to-code bridge — and every such bridge needs the caller's input to be unambiguously inert.

Sources