diff --git a/lib/metasploit/framework/login_scanner/octopusdeploy.rb b/lib/metasploit/framework/login_scanner/octopusdeploy.rb new file mode 100644 index 0000000000..1569a8534d --- /dev/null +++ b/lib/metasploit/framework/login_scanner/octopusdeploy.rb @@ -0,0 +1,64 @@ +require 'metasploit/framework/login_scanner/http' +require 'json' + +module Metasploit + module Framework + module LoginScanner + + # Octopus Deploy login scanner + class OctopusDeploy < HTTP + + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = true + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + uri = '/api/users/login' if uri.nil? + method = 'POST' if method.nil? + + super + end + + def attempt_login(credential) + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + begin + json_post_data = JSON.pretty_generate({ Username: credential.public, Password: credential.private }) + cli = Rex::Proto::Http::Client.new(host, port, { 'Msf' => framework, 'MsfExploit' => framework_module }, ssl, ssl_version, http_username, http_password) + configure_http_client(cli) + cli.connect + req = cli.request_cgi( + 'method' => 'POST', + 'uri' => uri, + 'ctype' => 'application/json', + 'data' => json_post_data + ) + res = cli.send_recv(req) + body = JSON.parse(res.body) + if res && res.code == 200 && body.key?('IsActive') && body['IsActive'] + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: res.body) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res) + end + rescue ::JSON::ParserError + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: res.body) + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + Result.new(result_opts) + end + end + end + end +end diff --git a/modules/auxiliary/scanner/http/octopusdeploy_login.rb b/modules/auxiliary/scanner/http/octopusdeploy_login.rb new file mode 100644 index 0000000000..0003bf3942 --- /dev/null +++ b/modules/auxiliary/scanner/http/octopusdeploy_login.rb @@ -0,0 +1,77 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/octopusdeploy' + +class MetasploitModule < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Octopus Deploy Login Utility', + 'Description' => %q{ + This module simply attempts to login to a Octopus Deploy server using a specific + username and password. It has been confirmed to work on version 3.4.4 + }, + 'Author' => [ 'James Otten ' ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [true, 'URI for login. Default is /api/users/login', '/api/users/login']) + ], self.class) + end + + def run_host(ip) + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + scanner = Metasploit::Framework::LoginScanner::OctopusDeploy.new( + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + http_username: datastore['HttpUsername'], + http_password: datastore['HttpPassword'], + uri: datastore['TARGETURI'] + ) + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential}" + else + invalidate_login(credential_data) + print_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential} (#{result.status})" + end + end + end +end diff --git a/spec/lib/metasploit/framework/login_scanner/octopusdeploy_spec.rb b/spec/lib/metasploit/framework/login_scanner/octopusdeploy_spec.rb new file mode 100644 index 0000000000..735e3c2477 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/octopusdeploy_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/octopusdeploy' + +RSpec.describe Metasploit::Framework::LoginScanner::OctopusDeploy do + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP' + +end