Class: Metasploit::Framework::LoginScanner::Teamcity
- Includes:
- Crypto
- Defined in:
- lib/metasploit/framework/login_scanner/teamcity.rb
Overview
This is the LoginScanner class for dealing with JetBrains TeamCity instances. It is responsible for taking a single target, and a list of credentials and attempting them. It then saves the results.
Defined Under Namespace
Modules: Crypto Classes: DecryptionException, NoPublicKeyError, PublicKeyExpiredError, ServerNeedsSetupError, StackLevelTooDeepError, TeamCityError
Constant Summary collapse
- DEFAULT_PORT =
8111
- LIKELY_PORTS =
[8111]
- LIKELY_SERVICE_NAMES =
Comes from nmap 7.95 on MacOS
['skynetflow']
- PRIVATE_TYPES =
[:password]
- REALM_KEY =
nil
- LOGIN_PAGE =
'login.html'
- LOGOUT_PAGE =
'ajax.html?logout=1'
- SUBMIT_PAGE =
'loginSubmit.html'
Constants inherited from HTTP
HTTP::AUTHORIZATION_HEADER, HTTP::DEFAULT_HTTP_NOT_AUTHED_CODES, HTTP::DEFAULT_HTTP_SUCCESS_CODES, HTTP::DEFAULT_REALM, HTTP::DEFAULT_SSL_PORT
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
- #attempt_login(credential) ⇒ Object
-
#create_login_request(username, password, public_key) ⇒ Hash
Create a login request for the provided credentials.
-
#get_public_key ⇒ Hash
Extract the server’s public key from the server.
-
#logout_with_headers(headers) ⇒ Object
Send a logout request for the provided user’s headers.
-
#try_login(username, password, public_key, retry_counter = 0) ⇒ Hash
Try logging in with the provided username, password and public key.
Methods included from Crypto
#encrypt_data, #max_data_size, #pkcs1pad2, #rsa_encrypt, #two_byte_chars?
Methods inherited from HTTP
#authentication_required?, #check_setup, #send_request
Instance Method Details
#attempt_login(credential) ⇒ Object
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 242 def attempt_login(credential) = { credential: credential, host: @host, port: @port, protocol: 'tcp', service_name: 'teamcity' } if @public_key.nil? public_key_result = get_public_key return Result.new(.merge(public_key_result)) if public_key_result[:status] != :success @public_key = public_key_result[:proof] end login_result = try_login(credential.public, credential.private, @public_key) return Result.new(.merge(login_result)) if login_result[:status] != :success # Ensure we log the user out, so that our logged in session does not appear under the user's profile. logout_with_headers(login_result[:proof].headers) [:status] = ::Metasploit::Model::Login::Status::SUCCESSFUL Result.new() end |
#create_login_request(username, password, public_key) ⇒ Hash
Create a login request for the provided credentials.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 170 def create_login_request(username, password, public_key) { 'method' => 'POST', 'uri' => normalize_uri(@uri.to_s, SUBMIT_PAGE), 'ctype' => 'application/x-www-form-urlencoded', 'vars_post' => { username: username, remember: true, _remember: '', submitLogin: 'Log in', publicKey: public_key, encryptedPassword: encrypt_data(password, public_key) } } end |
#get_public_key ⇒ Hash
Extract the server’s public key from the server.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 142 def get_public_key request_params = { 'method' => 'GET', 'uri' => normalize_uri(@uri.to_s, LOGIN_PAGE) } begin res = send_request(request_params) 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 TeamCity service' } if res.nil? raise ServerNeedsSetupError, 'The server has not performed the initial setup' if res.code == 503 html_doc = res.get_html_document public_key = html_doc.xpath('//input[@id="publicKey"]/@value').text raise NoPublicKeyError, 'Could not find the TeamCity public key in the HTML document' if public_key.empty? { status: :success, proof: public_key } end |
#logout_with_headers(headers) ⇒ Object
Send a logout request for the provided user’s headers. This header stores the user’s cookie.
228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 228 def logout_with_headers(headers) logout_params = { 'method' => 'POST', 'uri' => normalize_uri(@uri.to_s, LOGOUT_PAGE), 'headers' => headers } begin send_request(logout_params) rescue Rex::ConnectionError => _e # ignore end end |
#try_login(username, password, public_key, retry_counter = 0) ⇒ Hash
Try logging in with the provided username, password and public key.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 191 def try_login(username, password, public_key, retry_counter = 0) raise StackLevelTooDeepError, 'try_login stack level too deep!' if retry_counter >= 2 login_request = create_login_request(username, password, public_key) begin res = send_request(login_request) 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 TeamCity service' } if res.nil? return { status: ::Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Received an unexpected status code: #{res.code}" } if res.code != 200 # Check if the current username is timed out. Sleep if so. # TODO: This can be improved. The `try_login` method should not block until it can retry credentials. # This responsibility should fall onto the caller, and the caller should keep track of the tried, locked out and untried sets of credentials, # and it should be up to the caller and its scheduler algorithm to retry credentials, rather than force this method to block. # Currently, those building blocks are not available, so this is the approach I have implemented. timeout = res.body.match(/login only in (?<timeout>\d+)s/)&.named_captures&.dig('timeout')&.to_i if timeout framework_module.print_status "User '#{username}' locked out for #{timeout} seconds. Sleeping, and retrying..." sleep(timeout + 1) # + 1 as TeamCity is off-by-one when reporting the lockout timer. result = try_login(username, password, public_key, retry_counter + 1) return result end return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res } if res.body.match?('Incorrect username or password') raise DecryptionException, 'The server failed to decrypt the encrypted password' if res.body.match?('DecryptionFailedException') raise PublicKeyExpiredError, 'The server public key has expired' if res.body.match?('publicKeyExpired') { status: :success, proof: res } end |