CVE-2026-63656
flyimg — Unauthenticated OS command injection (RCE)
The CVE identifier is assigned; the CVE record is not yet published.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NSummary
flyimg is a self-hosted, on-the-fly image resizer. It assembles an ImageMagick command line as a string and runs it through a shell, and one option reaches that command line without shell escaping.
Technical analysis
Every value on the command line is passed through shell escaping except the webp-method option, which is appended verbatim and then executed by /bin/sh. That option is set from an unauthenticated URL segment, and the default configuration ships with no URL signing and no domain allow-list, so the whole request is attacker-controlled.
The value is taken raw from the URL. The option parser splits each URL segment on _ and stores the right-hand side with no validation: webpm_$(id) becomes webp-method = $(id). There is no numeric cast and no allow-list, even though the option is meant to be an integer 0–6. src/Core/Entity/OptionsBag.php:48-50
Quality and the output path are escaped — webp-method is appended verbatim. On the very lines that build the webp arguments, quality and the output path are wrapped in escapeshellarg(), but $webpMethod is concatenated directly. Command::addArgument() appends the string raw and __toString() joins it into one command. src/Core/Processor/ImageProcessor.php:233-238 · src/Core/Entity/Command.php:31-47
The command string is run through a shell. Processor.php passes the assembled string to proc_open(). Given a string (not an argv array), proc_open() executes it via /bin/sh -c on Linux — so any shell metacharacter in webp-method is interpreted, not treated as literal argument text. src/Core/Processor/Processor.php:66
// quality and the output path go through escapeshellarg()…
$parameter = "-quality " . escapeshellarg($quality) .
" -define webp:thread-level=" . $webpThreads .
" -define webp:method=" . $webpMethod . // <-- appended RAW, no escapeshellarg()
" -define webp:lossless=" . $lossLess .
" " . escapeshellarg($outputImage->getOutputTmpPath());Proof of concept
Renesoft confirmed this against the real package in an isolated environment, with no live systems. The attack path, then the recorded test output:
- Send a single unauthenticated GET with the payload in the
webp-methodsegment —GET /upload/o_webp,webpm_$(id)/https://attacker.example/1x1.png.o_webpforces webp output regardless of theAcceptheader, and the defaultrestricted_domains: falselets flyimg fetch any source image URL the request names. - flyimg parses
webpm_$(id)intowebp-method = $(id)— and builds the ImageMagick command containing… -define webp:method=$(id) …, with the attacker's value unescaped. proc_open()runs the string through/bin/sh -c— the shell expands$(id)and executes it on the server. None of the injection characters are the parser's own_/,delimiters, so they pass through cleanly.
$ php poc/poc.php
ATTACK webp-method = "4 & echo INJECTED… > FLYIMG_PWNED.txt"
-> injected command executed (marker created): YES
CONTROL webp-method = "4" (benign)
-> marker created: no
exit 0 · VERDICT: CONFIRMEDImpact
An unauthenticated attacker runs arbitrary operating-system commands as the flyimg service process with a single request, out of the box on the official Docker image. What follows from that depends on the deployment's permissions and network controls.
Affected versions
- Affected
- 0.1.2 – 1.12.2
- Fixed
- 1.12.3
- CVSS
- 9.3 (4.0)
- Weakness
- CWE-77 · CWE-78
- Ecosystem
- PHP · Composer, Docker
- Disclosed
- 2026-07-30
Remediation
Fixed in 1.12.3, which escapes the value like the others. Deployments should upgrade and, as defence in depth, set a URL signing key.
Timeline
- Discovered, proven, and reported privately through GitHub private vulnerability reporting.
- Fixed in 1.12.3; advisory published and CVE-2026-63656 assigned.