The Exploit
The attacker only needs the ability to submit a PNG through an ExifTool-powered processing pipeline; no authentication is required on the ExifTool side itself.
#!/bin/bash
cat > /tmp/exploit.png <<'EOF'
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGNgYAAAAAMAAVYYqqQAAAAASUVORK5CYII=
EOF
base64 -d /tmp/exploit.png > /tmp/exploit.png.raw
mv /tmp/exploit.png.raw /tmp/exploit.png
rm -f /tmp/exiftool-rce
exiftool "-DateTimeOriginal=2026-01-01'; touch /tmp/exiftool-rce; echo '" /tmp/exploit.png
ls -l /tmp/exiftool-rce
When this runs on a vulnerable macOS ExifTool, exiftool reports success and the file /tmp/exiftool-rce appears, proving that the DateTimeOriginal metadata value was injected into an OS command and executed.
What the Patch Did
Before
($f = $file) =~ s/'/'\\''/g;
...
$cmd = "/usr/bin/setfile -d '${val}' '${f}'";
...
$err = system "/usr/local/bin/tag -r '${del}' '${f}'>/dev/null 2>&1";
...
$cmd = "/usr/local/bin/tag $opt '${val}' '${f}'";
...
$cmd = "/usr/bin/xattr -d $delXAttr{$tag} '${f}'";
...
$cmd = qq(/usr/bin/osascript -e 'set fp to POSIX file "$f" as alias' -e \
'tell application "Finder" to set $attr of file fp to "$v"');
...
$err = system $cmd . '>/dev/null 2>&1';
After
sub System
{
my ($oldout, $olderr);
open($oldout, ">&STDOUT");
open($olderr, ">&STDERR");
open(STDOUT, '>', '/dev/null');
open(STDERR, '>', '/dev/null');
my $result = system(@_);
open(STDOUT, ">&", $oldout);
open(STDERR, ">&", $olderr);
return $result;
}
...
push @cmd, '/usr/bin/setfile', '-d', $val, $file;
...
$err = System('/usr/local/bin/tag', '-r', $del, $file);
...
push @cmd, '/usr/local/bin/tag', $opt, $val, $file;
...
push @cmd, '/usr/bin/xattr', '-d', $delXAttr{$tag}, $file;
...
($f = $file) =~ s/([\\"])/\\$1/g;
...
push @cmd, '/usr/bin/osascript', '-e', qq(set fp to POSIX file "$f" as alias),
'-e', qq(tell application "Finder" to set $attr of file fp to "$v");
...
$err = System(@cmd) if @cmd;
The patch replaced shell-string command execution with Perl's argument-list form of system(@cmd), removing the shell parsing layer that allowed attacker-controlled metadata in DateTimeOriginal to become executable shell syntax.
Root Cause
This is an OS command injection issue (CWE-78). The DateTimeOriginal tag value from a PNG file was passed into SetMacOSTags and concatenated directly into shell command lines such as /usr/bin/setfile -d '${val}' '${f}'. Because the code used system($cmd) and string-based command construction, shell metacharacters inside the attacker-controlled DateTimeOriginal value were interpreted by /bin/sh, crossing the boundary from untrusted image metadata to a trusted OS command execution context.
Why It Works
The single load-bearing change is the switch from shell command strings to argument-array execution: System(@cmd) / system(@_). That is what prevents DateTimeOriginal from being treated as shell syntax. The added System wrapper itself is mostly for suppressing stdout/stderr during execution, and the quote escaping for osascript fixes a separate path where file names containing quotes could break the AppleScript arguments. If the patch had only added output redirection but still called system($cmd), the injection would remain exploitable.
Hardening Checklist
- Use argument-array execution for external processes in Perl:
system(@args)orexec @argsinstead ofsystem($cmd). - When shell execution is unavoidable, escape user-controlled values with a shell-escaping utility such as
String::ShellQuote::shell_quote. - In WordPress plugins, sanitize any metadata or filename values before passing them to system commands with
sanitize_text_field(). - Enforce strict format validation for metadata tags like
DateTimeOriginalrather than accepting arbitrary strings. - Prefer native library calls over external helper binaries when handling file metadata, and restrict command arguments to whitelisted values.
References
- https://nvd.nist.gov/vuln/detail/CVE-2026-3102