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: DecryptionError, 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', 'teamcity' ]
- 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
-
#check_setup ⇒ Boolean
Checks if the target is JetBrains TeamCity.
-
#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?, #send_request
Instance Method Details
#attempt_login(credential) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 265 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 |
#check_setup ⇒ Boolean
Checks if the target is JetBrains TeamCity. The login module should call this.
147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 147 def check_setup request_params = { 'method' => 'GET', 'uri' => normalize_uri(@uri.to_s, LOGIN_PAGE) } res = send_request(request_params) if res && res.code == 200 && res.body&.include?('Log in to TeamCity') return false end "Unable to locate \"Log in to TeamCity\" in body. (Is this really TeamCity?)" end |
#create_login_request(username, password, public_key) ⇒ Hash
Create a login request for the provided credentials.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 191 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.
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 163 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.
251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 251 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.
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/metasploit/framework/login_scanner/teamcity.rb', line 212 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 "#{@host}:#{@port} - User '#{username}:#{password}' locked out for #{timeout} seconds. Sleeping, and retrying..." if framework_module sleep(timeout + 1) return try_login(username, password, public_key, retry_counter + 1) end return { status: ::Metasploit::Model::Login::Status::INCORRECT, proof: res } if res.body.match?('Incorrect username or password') raise DecryptionError, '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') # After filtering out known failures, default to retuning the credential as working. # This way, people are more likely to notice any incorrect credential reporting going forward and report them, # the scenarios for which can then be correctly implemented and handled similar to the above. { status: :success, proof: res } end |