Class: Metasploit::Framework::LoginScanner::Ivanti

Inherits:
HTTP
  • Object
show all
Defined in:
lib/metasploit/framework/login_scanner/ivanti_login.rb

Overview

Ivanti Login Scanner supporting

  • User Login

  • Admin Login

Constant Summary collapse

DEFAULT_SSL_PORT =
443
LIKELY_PORTS =
[443]
LIKELY_SERVICE_NAMES =
[
  'Ivanti Connect Secure'
]
PRIVATE_TYPES =
[:password]
REALM_KEY =
nil

Constants inherited from HTTP

HTTP::AUTHORIZATION_HEADER, HTTP::DEFAULT_HTTP_NOT_AUTHED_CODES, HTTP::DEFAULT_HTTP_SUCCESS_CODES, HTTP::DEFAULT_PORT, HTTP::DEFAULT_REALM

Instance Attribute Summary

Attributes inherited from HTTP

#digest_auth_iis, #evade_header_folding, #evade_method_random_case, #evade_method_random_invalid, #evade_method_random_valid, #evade_pad_fake_headers, #evade_pad_fake_headers_count, #evade_pad_get_params, #evade_pad_get_params_count, #evade_pad_method_uri_count, #evade_pad_method_uri_type, #evade_pad_post_params, #evade_pad_post_params_count, #evade_pad_uri_version_count, #evade_pad_uri_version_type, #evade_shuffle_get_params, #evade_shuffle_post_params, #evade_uri_dir_fake_relative, #evade_uri_dir_self_reference, #evade_uri_encode_mode, #evade_uri_fake_end, #evade_uri_fake_params_start, #evade_uri_full_url, #evade_uri_use_backslashes, #evade_version_random_invalid, #evade_version_random_valid, #http_password, #http_success_codes, #http_username, #keep_connection_alive, #kerberos_authenticator_factory, #method, #ntlm_domain, #ntlm_send_lm, #ntlm_send_ntlm, #ntlm_send_spn, #ntlm_use_lm_key, #ntlm_use_ntlmv2, #ntlm_use_ntlmv2_session, #uri, #user_agent, #vhost

Instance Method Summary collapse

Methods inherited from HTTP

#authentication_required?, #send_request

Constructor Details

#initialize(scanner_config, admin) ⇒ Ivanti

Returns a new instance of Ivanti.



19
20
21
22
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 19

def initialize(scanner_config, admin)
  @admin = admin
  super(scanner_config)
end

Instance Method Details

#attempt_login(credential) ⇒ Result

Attempts to login to the server.

Parameters:

Returns:

  • (Result)

    A Result object indicating success or failure



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 171

def (credential)
  # focus on creating Result object, pass it to #login routine and return Result object
  result_options = {
    credential: credential,
    host: @host,
    port: @port,
    protocol: 'tcp',
    service_name: 'ivanti'
  }

  if @admin
     = (credential.public, credential.private)
  else
     = (credential.public, credential.private)
  end

  result_options.merge!()
  Result.new(result_options)
end

#check_setupObject



24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 24

def check_setup
  request_params = {
    'method' => 'GET',
    'uri' => normalize_uri('/dana-na/auth/url_default/welcome.cgi')
  }

  res = send_request(request_params)

  if res && res.code == 200 && res.body&.include?('Ivanti Connect Secure')
    return false
  end

  'Application might not be Ivanti Connect Secure, please check'
end

#create_admin_request(username, password, token, protocol, peer) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 39

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

#create_user_request(username, password, protocol, peer) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 111

def create_user_request(username, password, protocol, peer)
  {
    'method' => 'POST',
    'uri' => normalize_uri('/dana-na/auth/url_default/login.cgi'),
    'ctype' => 'application/x-www-form-urlencoded',
    'headers' =>
    {
      'Origin' => "#{protocol}://#{peer}",
      'Referer' => "#{protocol}://#{peer}/dana-na/auth/url_default/welcome.cgi"
    },
    'vars_post' =>
      {
        tz_offset: '',
        win11: '',
        clientMAC: '',
        username: username,
        password: password,
        realm: 'Users',
        btnSubmit: 'Sign+In'
      },
    'encode_params' => false
  }
end

#do_admin_login(username, password) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 83

def (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::UNABLE_TO_CONNECT, proof: 'Unexpected response' } if !res.headers&.key?('location')

  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

#do_admin_logout(cookies) ⇒ Object



62
63
64
65
66
67
68
69
70
71
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 62

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)

  return nil if xsauth.nil?

  send_request({ 'method' => 'GET', 'uri' => normalize_uri('/dana-na/auth/logout.cgi?' + xsauth[0]), 'cookie' => cookies })
end

#do_login(username, password) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 139

def (username, password)
  protocol = ssl ? 'https' : 'http'
  peer = "#{host}:#{port}"
  user_req = create_user_request(username, password, protocol, peer)
  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 }
  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.headers&.key?('location')

  if res.headers['location'] == '/dana-na/auth/url_default/welcome.cgi?p=ip%2Dblocked'
    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'

  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 }
  else
    return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res.to_s }
  end
end

#do_logout(cookies) ⇒ Object



135
136
137
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 135

def do_logout(cookies)
  send_request({ 'uri' => normalize_uri('/dana-na/auth/logout.cgi?delivery=psal'), 'cookie' => cookies })
end

#get_tokenObject



73
74
75
76
77
78
79
80
81
# File 'lib/metasploit/framework/login_scanner/ivanti_login.rb', line 73

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