diff --git a/lib/metasploit/framework/login_scanner/ivanti_admin_login.rb b/lib/metasploit/framework/login_scanner/ivanti_admin_login.rb deleted file mode 100644 index a06ea31a0bd6..000000000000 --- a/lib/metasploit/framework/login_scanner/ivanti_admin_login.rb +++ /dev/null @@ -1,95 +0,0 @@ -require 'metasploit/framework/login_scanner/http' - -module Metasploit - module Framework - module LoginScanner - class IvantiAdmin < HTTP - - def create_admin_request(username, password, token, protocol, peer) - { - 'method' => 'POST', - 'uri' => normalize_uri('/dana-na/auth/url_admin/login.cgi'), - 'ctype' => 'application/x-www-form-urlencoded', - 'headers' => - { - 'Origin'=> "#{protocol}://#{peer}", - 'Referer'=> "#{protocol}://#{peer}/dana-na/auth/url_admin/welcome.cgi", - }, - 'vars_post' => { - tz_offset: '60', - xsauth_token: token, - username: username, - password: password, - realm: "Admin+Users", - btnSubmit: "Sign+In" - - }, - 'encode_params' => false - } - end - - def do_logout(cookies) - admin_page_res = send_request({ 'method' => 'GET', 'uri' => normalize_uri('/dana-admin/misc/admin.cgi?'), 'cookie' => cookies }) - admin_page_s = admin_page_res.to_s - re = /xsauth\=[a-z0-9]{32}/ - xsauth = re.match(admin_page_s)[0] - logout_res = send_request({ 'method' => 'GET', 'uri' => normalize_uri('/dana-na/auth/logout.cgi?'+xsauth), 'cookie' => cookies }) - logout_res - end - - def get_token - res = send_request({ - 'uri'=>normalize_uri('/dana-na/auth/url_admin/welcome.cgi'), - }) - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? - - html_document = res.get_html_document - token = html_document.xpath('//input[@id="xsauth_token"]/@value').text - token - - end - - def do_login(username, password) - token = get_token - - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the TeamCity service' } if token.blank? - protocol = ssl ? 'https' : 'http' - peer = "#{host}:#{port}" - admin_req = create_admin_request(username, password, token, protocol, peer) - begin - res = send_request(admin_req) - rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError => e - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e } - end - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Received an unexpected status code: #{res.code}" } if res.code != 302 - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Unexpected response"} if res.blank? - - return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s} if res.headers['location'] == '/dana-na/auth/url_admin/welcome.cgi?p=admin%2Dconfirm' - - if res.headers['location'] == '/dana-admin/misc/admin.cgi' - do_logout(res.get_cookies) - return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL , proof: res.to_s} - end - - return {status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res.to_s } - - end - - def attempt_login(credential) - result_options = { - credential: credential, - host: @host, - port: @port, - protocol: 'tcp', - service_name: 'ivanti' - } - login_result = do_login(credential.public, credential.private) - result_options.merge!(login_result) - Result.new(result_options) - end - - end - end - end -end diff --git a/lib/metasploit/framework/login_scanner/ivanti_login.rb b/lib/metasploit/framework/login_scanner/ivanti_login.rb index 6df6ed028de3..62aced4588e7 100644 --- a/lib/metasploit/framework/login_scanner/ivanti_login.rb +++ b/lib/metasploit/framework/login_scanner/ivanti_login.rb @@ -5,6 +5,78 @@ module Framework module LoginScanner class Ivanti < HTTP + def initialize(scanner_config, admin) + @admin = admin + super(scanner_config) + end + + def create_admin_request(username, password, token, protocol, peer) + { + 'method' => 'POST', + 'uri' => normalize_uri('/dana-na/auth/url_admin/login.cgi'), + 'ctype' => 'application/x-www-form-urlencoded', + 'headers' => + { + 'Origin' => "#{protocol}://#{peer}", + 'Referer' => "#{protocol}://#{peer}/dana-na/auth/url_admin/welcome.cgi" + }, + 'vars_post' => { + tz_offset: '60', + xsauth_token: token, + username: username, + password: password, + realm: 'Admin+Users', + btnSubmit: 'Sign+In' + + }, + 'encode_params' => false + } + end + + def do_admin_logout(cookies) + admin_page_res = send_request({ 'method' => 'GET', 'uri' => normalize_uri('/dana-admin/misc/admin.cgi?'), 'cookie' => cookies }) + admin_page_s = admin_page_res.to_s + re = /xsauth=[a-z0-9]{32}/ + xsauth = re.match(admin_page_s)[0] + send_request({ 'method' => 'GET', 'uri' => normalize_uri('/dana-na/auth/logout.cgi?' + xsauth), 'cookie' => cookies }) + end + + def get_token + res = send_request({ + 'uri' => normalize_uri('/dana-na/auth/url_admin/welcome.cgi') + }) + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? + + html_document = res.get_html_document + html_document.xpath('//input[@id="xsauth_token"]/@value')&.text + end + + def do_admin_login(username, password) + token = get_token + + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if token.blank? + + protocol = ssl ? 'https' : 'http' + peer = "#{host}:#{port}" + admin_req = create_admin_request(username, password, token, protocol, peer) + begin + res = send_request(admin_req) + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError => e + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e } + end + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Received an unexpected status code: #{res.code}" } if res.code != 302 + + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } if res.headers['location'] == '/dana-na/auth/url_admin/welcome.cgi?p=admin%2Dconfirm' + + if res.headers['location'] == '/dana-admin/misc/admin.cgi' + do_admin_logout(res.get_cookies) + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } + end + + return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res.to_s } + end + def create_user_request(username, password, protocol, peer) { 'method' => 'POST', @@ -12,21 +84,8 @@ def create_user_request(username, password, protocol, peer) 'ctype' => 'application/x-www-form-urlencoded', 'headers' => { - 'Cache-Control'=> 'max-age=0', - 'Sec-Ch-Ua'=> '"Chromium";v="131", "Not_A Brand";v="24"', - 'Sec-Ch-Ua-Mobile'=> '?0', - 'Sec-Ch-Ua-Platform'=> 'Linux', - 'Accept-Language'=> 'en-US,en;q=0.9', - 'Origin'=> "#{protocol}://#{peer}", - 'Upgrade-Insecure-Requests'=> '1', - 'Accept'=> 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', - 'Sec-Fetch-Site'=> 'same-origin', - 'Sec-Fetch-Mode'=> 'navigate', - 'Sec-Fetch-User'=> '?1', - 'Sec-Fetch-Dest'=> 'document', - 'Referer'=> "#{protocol}://#{peer}/dana-na/auth/url_default/welcome.cgi", - 'Accept-Encoding'=> 'gzip, deflate, br', - 'Priority'=> 'u=0, i' + 'Origin' => "#{protocol}://#{peer}", + 'Referer' => "#{protocol}://#{peer}/dana-na/auth/url_default/welcome.cgi" }, 'vars_post' => { @@ -43,11 +102,9 @@ def create_user_request(username, password, protocol, peer) end def do_logout(cookies) - logout_res = send_request({'uri'=>normalize_uri('/dana-na/auth/logout.cgi?delivery=psal'), 'cookie' => cookies}) - logout_res + send_request({ 'uri' => normalize_uri('/dana-na/auth/logout.cgi?delivery=psal'), 'cookie' => cookies }) end - def do_login(username, password) protocol = ssl ? 'https' : 'http' peer = "#{host}:#{port}" @@ -55,24 +112,24 @@ def do_login(username, password) begin res = send_request(user_req) rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error, ::EOFError => e - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof:e } + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e } end return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unable to connect to the Ivanti service' } if res.nil? return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Received an unexpected status code: #{res.code}" } if res.code != 302 - return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Unexpected response"} if res.blank? - + return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: 'Unexpected response' } if res.blank? + if res.headers['location'] == '/dana-na/auth/url_default/welcome.cgi?p=ip%2Dblocked' - sleep(2*60) # 2 minutes + sleep(2 * 60) # 2 minutes res = send_request(user_req) end - - return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof:res.to_s} if res.headers['location'] == '/dana-na/auth/url_default/welcome.cgi?p=user%2Dconfirm' + + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } if res.headers['location'] == '/dana-na/auth/url_default/welcome.cgi?p=user%2Dconfirm' if res.headers['location'] == '/dana/home/starter0.cgi?check=yes' do_logout(res.get_cookies) - return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL , proof: res.to_s} + return { status: ::Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.to_s } else - return {status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res.to_s } + return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res.to_s } end end @@ -89,7 +146,13 @@ def attempt_login(credential) protocol: 'tcp', service_name: 'ivanti' } - login_result = do_login(credential.public, credential.private) + + if @admin + login_result = do_admin_login(credential.public, credential.private) + else + login_result = do_login(credential.public, credential.private) + end + result_options.merge!(login_result) Result.new(result_options) end diff --git a/modules/auxiliary/scanner/ivanti/login_scanner.rb b/modules/auxiliary/scanner/ivanti/login_scanner.rb index 020f76ad7623..8b9c786e3acb 100644 --- a/modules/auxiliary/scanner/ivanti/login_scanner.rb +++ b/modules/auxiliary/scanner/ivanti/login_scanner.rb @@ -1,6 +1,5 @@ require 'metasploit/framework/credential_collection' require 'metasploit/framework/login_scanner/ivanti_login' -require 'metasploit/framework/login_scanner/ivanti_admin_login' class MetasploitModule < Msf::Auxiliary @@ -8,6 +7,7 @@ class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::AuthBrute include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner + include Msf::Auxiliary::ReportSummary def initialize(info = {}) super( @@ -53,12 +53,7 @@ def get_scanner(ip) bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: datastore['HttpClientTimeout'] || 5 ) - if datastore['ADMIN'] - return Metasploit::Framework::LoginScanner::IvantiAdmin.new(configuration) - else - - return Metasploit::Framework::LoginScanner::Ivanti.new(configuration) - end + return Metasploit::Framework::LoginScanner::Ivanti.new(configuration, datastore['ADMIN']) end def process_credential(credential_data) @@ -78,14 +73,11 @@ def process_credential(credential_data) end def run_scanner(scanner) - successful_logins = [] scanner.scan! do |result| credential_data = result.to_h credential_data.merge!(module_fullname: fullname, workspace_id: myworkspace_id) - processed_credential = process_credential(credential_data) - successful_logins << processed_credential[:credential] if processed_credential[:status] == :success + process_credential(credential_data) end - { successful_logins: successful_logins } end def run_host(ip)