Module: Msf::Exploit::Remote::HTTP::NagiosXi::Login

Includes:
URIs
Included in:
Msf::Exploit::Remote::HTTP::NagiosXi, Install
Defined in:
lib/msf/core/exploit/remote/http/nagios_xi/login.rb

Instance Method Summary collapse

Methods included from URIs

#nagios_xi_backend_url, #nagios_xi_install_url, #nagios_xi_login_url

Instance Method Details

#clean_cookies(pre_auth_cookies, auth_cookies) ⇒ String?

Compares cookies obtained before and after authentication and modifies the latter to remove cookies that may cause session timeouts

Parameters:

  • pre_auth_cookies (String)

    Cookies obtained before authenticating to Nagios XI

  • auth_cookies (String)

    Cookies obtained while authenticating to Nagios XI

Returns:

  • (String, nil)

    String containing the cookies required for authentication, stripped off unnecessary/unwanted cookies, nil if one or both of the parameters passed to this method are nil


90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 90

def clean_cookies(pre_auth_cookies, auth_cookies)
  if pre_auth_cookies.nil? || auth_cookies.nil?
    return nil
  end
  # Nagios XI may sometimes send the cookie `nagiosxi=deleted;` as part of the cookies after authentication.
  # This was observed when trying to access Nagios XI 5.3.0 when the license agreement had not been accepted yet.
  # The `nagiosxi=deleted;` cookie should be filtered out, since it may break authentication.
  if auth_cookies.include?('nagiosxi=deleted;')
    auth_cookies = auth_cookies.gsub('nagiosxi=deleted;', '').strip
  end

  # Remove duplicate cookies, necessary to make the next check work in case multiple
  # identical cookies were set (as observed on older Nagios versions)
  auth_cookies_array = auth_cookies.split(' ')
  auth_cookies_array.uniq!
  auth_cookies = auth_cookies_array.join(' ')

  # For newer Nagios XI versions, we need to remove the pre_auth cookies from the auth_cookies
  # string, otherwise the session will timeout. However, older Nagios XI versions use a single cookie
  # which has the same name both before and after authentication.
  unless pre_auth_cookies == auth_cookies
    if auth_cookies.include?(pre_auth_cookies)
      auth_cookies = auth_cookies.gsub(pre_auth_cookies, '').strip
    end
  end

  auth_cookies
end

#get_nsp(res) ⇒ String?

Grabs the nsp_str value from an HTTP response using regex

Parameters:

Returns:

  • (String, nil)

    nsp_str value, nil if not found


166
167
168
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 166

def get_nsp(res)
  nsp = res.body.scan(/nsp_str = "([a-z0-9]+)/)&.flatten&.first
end

#login_after_install_or_license(username, password, finish_install) ⇒ Array

Performs an authentication attempt. If the server does not return a response, a second attempt is made after a delay of 5 seconds

Parameters:

  • username (String)

    Username required for authentication

  • password (String)

    Password required for authentication

  • finish_install (Boolean)

    Boolean indicating if the module should finish installing Nagios XI on target hosts if the installation hasn't been completed or the license agreement is not signed

Returns:

  • (Array)

    Array containing the HTTP response body and session cookies if the Nagios XI backend was accessed successfully, otherwise Array containing an error code and an error message


177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 177

def (username, password, finish_install)
  # After installing Nagios XI or signing the license agreement, we sometimes don't receive a server response.
  # This loop ensures that at least 2 login attempts are performed if this happens, as the second one usually works.
  second_attempt = false
  while true
    , error_message = (username, password, finish_install)

    break unless error_message == ['Connection failed']

    if second_attempt
      print_warning('The server is still not responding. If you wait a few seconds and rerun the module, it might still work.')
      break
    else
      print_warning('No response received from the server. This can happen after installing Nagios XI or signing the license agreement')
      print_status('The module will wait for 5 seconds and retry.')
      second_attempt = true
      sleep 5
    end
  end

  return [, error_message]

end

#nagios_xi_login(user, pass, finish_install) ⇒ Array

performs a Nagios XI login

Parameters:

  • user (String)

    Username

  • pass (String)

    Password

  • finish_install (Boolean)

    Boolean indicating if the module should finish installing Nagios XI on target hosts if the installation hasn't been completed or the license agreement is not signed

Returns:

  • (Array)

    Array containing a response code and an Array containing one of four possibilities: the HTTP response body and session cookies if the Nagios XI backend was accessed successfully; nil if Nagios XI hasn't been fully installed; cookies and the nsp token if the license agreement is not signed; otherwise an error message


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 11

def (user, pass, finish_install)
  print_status('Attempting to authenticate to Nagios XI...')

  # Visit the login page in order to obtain the cookies and the `nsp_str` token required for authentication
   = send_request_cgi({
    'method' => 'GET',
    'uri' => ,
  })

  unless 
    return [1, ['Connection failed']]
  end

  unless .code == 200 && .body.include?('>Nagios XI<')
    # Check if we are perhaps dealing with a Nagios XI app that hasn't been fully installed yet
    unless .code == 302 && .body.include?('>Nagios XI<') && .headers['Location'].end_with?('/install.php')
      return [3, ['Target is not a Nagios XI application']]
    end

    print_warning('The target seems to be a Nagios XI application that has not been fully installed yet.')
    unless finish_install
      return [2, ['You can let the module complete the installation by setting `FINISH_INSTALL` to true.']]
    end

    return [4, [nil]]
  end

  # Grab the necessary tokens required for authentication
  nsp = get_nsp()
  if nsp.nil?
    return [2, ['Unable to obtain the value of the `nsp_str` token required for authentication']]
  end

  pre_auth_cookies = .get_cookies
  if pre_auth_cookies.blank?
    return [2, ['Unable to obtain the cookies required for authentication']]
  end

  # authenticate
   = send_request_cgi({
    'method' => 'POST',
    'uri' => ,
    'cookie' => pre_auth_cookies,
    'vars_post' => {
      'nsp' => nsp,
      'pageopt' => 'login',
      'username' => user,
      'password' => pass
    }
  })

  unless 
    return [1, ['Connection failed']]
  end

  unless .code == 302 && .headers['Location'] == 'index.php'
    return [2, ['Received unexpected reply while trying to authenticate. Please check your credentials.']]
  end

  # Grab the cookies
  auth_cookies = .get_cookies

  if auth_cookies.blank?
    return [2, ['Unable to obtain the cookies required for authentication']]
  end

  # Make sure we only use the cookies we need, otherwise we may encounter a session timeout
  auth_cookies = clean_cookies(pre_auth_cookies, auth_cookies)

  # Try to visit the dasboard
  visit_nagios_dashboard(auth_cookies, finish_install)
end

#visit_nagios_dashboard(auth_cookies, finish_install) ⇒ Array

Performs an HTTP GET request to the Nagios XI backend to verify if authentication succeeded

Parameters:

  • auth_cookies (String)

    Cookies required for authentication

  • finish_install (Boolean)

    Boolean indicating if the module should finish installing Nagios XI on target hosts if the installation hasn't been completed or the license agreement is not signed

Returns:

  • (Array)

    Array containing a result code and an Array containing one of three possibilities: an HTTP response body and cookies if the Nagios XI backend was accessed successfully; cookies and the nsp token if the license agreement is not signed; otherwise an error message


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/msf/core/exploit/remote/http/nagios_xi/login.rb', line 124

def visit_nagios_dashboard(auth_cookies, finish_install)
  # Visit the index page to verify we successfully authenticated
  res_index = send_request_cgi({
    'method' => 'GET',
    'uri' => nagios_xi_backend_url,
    'cookie' => auth_cookies
  })

  unless res_index
    return [1, ['Connection failed']]
  end

  unless res_index.code == 200 && res_index.body.include?('>Home Dashboard<')
    # Check if we need to sign the license agreement
    unless res_index.code == 302 && res_index.headers['Location'].end_with?('login.php?showlicense')
      return [2, ['Received unexpected reply while trying to acess the NagiosXI home dashboard after authenticating.']]
    end

    print_warning('The Nagios XI license agreement has not yet been signed on the target.')
    unless finish_install
      return [2, ['You can let the module sign the Nagios XI license agreement by setting `FINISH_INSTALL` to true.']]
    end

    nsp = get_nsp(res_index)
    if nsp.nil?
      return [2, ['Failed to obtain the nsp token required for signing the license agreement.']]
    end

    return [5, [auth_cookies, nsp]]
  end

  # Return the HTTP resonse body and the authentication cookies.
  # The response body can be used to obtain the version number.
  # The cookies can be used by exploit modules to send authenticated requests.
  [0, [res_index.body, auth_cookies]]

end