Merge pull request #21 from rapid7/feature/MSP-9687/winrm-loginscanner
Specs and functional steps passing. MSP-9687 #landbug/bundler_fix
commit
62bae8e23b
|
@ -52,6 +52,9 @@ module Metasploit
|
|||
self.paired = true if self.paired.nil?
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} \"#{self.public}:#{self.private}@#{self.realm}\" >"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,20 +6,30 @@ module Metasploit
|
|||
module Framework
|
||||
module LoginScanner
|
||||
#
|
||||
# HTTP-specific login scananer.
|
||||
# HTTP-specific login scanner.
|
||||
#
|
||||
class HTTP
|
||||
include Metasploit::Framework::LoginScanner::Base
|
||||
include Metasploit::Framework::LoginScanner::RexSocket
|
||||
|
||||
DEFAULT_PORT = 80
|
||||
DEFAULT_SSL_PORT = 443
|
||||
|
||||
# @!attribute uri
|
||||
# @return [String] The path and query string on the server to
|
||||
# authenticate to.
|
||||
attr_accessor :uri
|
||||
|
||||
# @!attribute uri
|
||||
# @return [String] HTTP method, e.g. "GET", "POST"
|
||||
attr_accessor :method
|
||||
|
||||
validates :uri, presence: true, length: { minimum: 1 }
|
||||
|
||||
validates :method,
|
||||
presence: true,
|
||||
length: { minimum: 1 }
|
||||
|
||||
# Attempt a single login with a single credential against the target.
|
||||
#
|
||||
# @param credential [Credential] The credential object to attempt to
|
||||
|
@ -29,9 +39,7 @@ module Metasploit
|
|||
ssl = false if ssl.nil?
|
||||
|
||||
result_opts = {
|
||||
private: credential.private,
|
||||
public: credential.public,
|
||||
realm: nil,
|
||||
credential: credential,
|
||||
status: :failed,
|
||||
proof: nil
|
||||
}
|
||||
|
@ -40,10 +48,16 @@ module Metasploit
|
|||
host, port, {}, ssl, ssl_version,
|
||||
nil, credential.public, credential.private
|
||||
)
|
||||
if credential.realm
|
||||
http_client.set_config('domain' => credential.realm)
|
||||
end
|
||||
|
||||
http_client.connect
|
||||
begin
|
||||
request = http_client.request_cgi('uri' => uri)
|
||||
http_client.connect
|
||||
request = http_client.request_cgi(
|
||||
'uri' => uri,
|
||||
'method' => method
|
||||
)
|
||||
|
||||
# First try to connect without logging in to make sure this
|
||||
# resource requires authentication. We use #_send_recv for
|
||||
|
@ -76,6 +90,23 @@ module Metasploit
|
|||
def set_sane_defaults
|
||||
self.max_send_size = 0 if self.max_send_size.nil?
|
||||
self.send_delay = 0 if self.send_delay.nil?
|
||||
|
||||
# Note that this doesn't cover the case where ssl is unset and
|
||||
# port is something other than a default. In that situtation,
|
||||
# we don't know what the user has in mind so we have to trust
|
||||
# that they're going to do something sane.
|
||||
if !(self.ssl) && self.port.nil?
|
||||
self.port = self.class::DEFAULT_PORT
|
||||
self.ssl = false
|
||||
elsif self.ssl && self.port.nil?
|
||||
self.port = self.class::DEFAULT_SSL_PORT
|
||||
elsif self.ssl.nil? && self.port == self.class::DEFAULT_PORT
|
||||
self.ssl = false
|
||||
elsif self.ssl.nil? && self.port == self.class::DEFAULT_SSL_PORT
|
||||
self.ssl = true
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -37,6 +37,10 @@ module Metasploit
|
|||
status == :success
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{credential.public}:#{credential.private}@#{credential.realm} #{status} >"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
require 'metasploit/framework/login_scanner/base'
|
||||
require 'metasploit/framework/login_scanner/rex_socket'
|
||||
require 'metasploit/framework/login_scanner/http'
|
||||
|
||||
module Metasploit
|
||||
module Framework
|
||||
module LoginScanner
|
||||
|
||||
# Windows Remote Management login scanner
|
||||
class WinRM < HTTP
|
||||
|
||||
# The default port where WinRM listens. This is what you get on
|
||||
# v1.1+ with `winrm quickconfig`. Note that before v1.1, the
|
||||
# default was 80
|
||||
DEFAULT_PORT = 5985
|
||||
|
||||
# The default port where WinRM listens when SSL is enabled. Note
|
||||
# that before v1.1, the default was 443
|
||||
DEFAULT_SSL_PORT = 5986
|
||||
|
||||
validates :method, inclusion: { in: ["POST"] }
|
||||
|
||||
# (see Base#set_sane_defaults)
|
||||
def set_sane_defaults
|
||||
self.uri = "/wsman" if self.uri.nil?
|
||||
@method = "POST".freeze
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# The method *must* be "POST", so don't let the user change it
|
||||
# @raise [RuntimeError]
|
||||
def method=(_)
|
||||
raise RuntimeError, "Method must be POST for WinRM"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -10,6 +10,78 @@ describe Metasploit::Framework::LoginScanner::HTTP do
|
|||
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
|
||||
|
||||
it { should respond_to :uri }
|
||||
it { should respond_to :method }
|
||||
|
||||
context "#set_sane_defaults" do
|
||||
|
||||
end
|
||||
context "without ssl, without port" do
|
||||
it "should default :port to #{described_class::DEFAULT_PORT}" do
|
||||
expect(http_scanner.ssl).to be_false
|
||||
expect(http_scanner.port).to eq(described_class::DEFAULT_PORT)
|
||||
end
|
||||
end
|
||||
|
||||
context "with ssl, without port" do
|
||||
subject(:http_scanner) { described_class.new(ssl:true) }
|
||||
it "should set :port to default ssl port (#{described_class::DEFAULT_SSL_PORT})" do
|
||||
expect(http_scanner.ssl).to be_true
|
||||
expect(http_scanner.port).to eq(described_class::DEFAULT_SSL_PORT)
|
||||
end
|
||||
end
|
||||
|
||||
context "without ssl, with default port" do
|
||||
subject(:http_scanner) { described_class.new(port:described_class::DEFAULT_PORT) }
|
||||
it "should set ssl to false" do
|
||||
expect(http_scanner.port).to eq(described_class::DEFAULT_PORT)
|
||||
expect(http_scanner.ssl).to be_false
|
||||
end
|
||||
end
|
||||
|
||||
context "without ssl, with default SSL port" do
|
||||
subject(:http_scanner) { described_class.new(port:described_class::DEFAULT_SSL_PORT) }
|
||||
it "should set ssl to true" do
|
||||
expect(http_scanner.ssl).to be_true
|
||||
expect(http_scanner.port).to eq(described_class::DEFAULT_SSL_PORT)
|
||||
end
|
||||
end
|
||||
|
||||
context "without ssl, with non-default port" do
|
||||
subject(:http_scanner) { described_class.new(port:0) }
|
||||
it "should not set ssl" do
|
||||
expect(http_scanner.ssl).to be_nil
|
||||
expect(http_scanner.port).to eq(0)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "#attempt_login" do
|
||||
let(:pub_blank) {
|
||||
Metasploit::Framework::LoginScanner::Credential.new(
|
||||
paired: true,
|
||||
public: "public",
|
||||
private: ''
|
||||
)
|
||||
}
|
||||
|
||||
it "Rex::ConnectionError should result in status :connection_error" do
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError)
|
||||
|
||||
expect(http_scanner.attempt_login(pub_blank).status).to eq(:connection_error)
|
||||
end
|
||||
|
||||
it "Timeout::Error should result in status :connection_error" do
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error)
|
||||
|
||||
expect(http_scanner.attempt_login(pub_blank).status).to eq(:connection_error)
|
||||
end
|
||||
|
||||
it "EOFError should result in status :connection_error" do
|
||||
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError)
|
||||
|
||||
expect(http_scanner.attempt_login(pub_blank).status).to eq(:connection_error)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
require 'spec_helper'
|
||||
require 'metasploit/framework/login_scanner/winrm'
|
||||
|
||||
describe Metasploit::Framework::LoginScanner::WinRM do
|
||||
|
||||
subject(:winrm_scanner) { described_class.new }
|
||||
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::Base'
|
||||
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
|
||||
|
||||
it { should respond_to :uri }
|
||||
it { should respond_to :method }
|
||||
|
||||
context "#method=" do
|
||||
it "should raise, warning that the :method can't be changed" do
|
||||
expect { winrm_scanner.method = "GET" }.to raise_error(RuntimeError)
|
||||
expect(winrm_scanner.method).to eq("POST")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -54,11 +54,6 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::Base' do
|
|||
context 'validations' do
|
||||
context 'port' do
|
||||
|
||||
it 'is not valid for not set' do
|
||||
expect(login_scanner).to_not be_valid
|
||||
expect(login_scanner.errors[:port]).to include "is not a number"
|
||||
end
|
||||
|
||||
it 'is not valid for a non-number' do
|
||||
login_scanner.port = "a"
|
||||
expect(login_scanner).to_not be_valid
|
||||
|
@ -84,7 +79,7 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::Base' do
|
|||
end
|
||||
|
||||
it 'is not valid for a number greater than 65535' do
|
||||
login_scanner.port = rand(1000) + 65535
|
||||
login_scanner.port = 65536
|
||||
expect(login_scanner).to_not be_valid
|
||||
expect(login_scanner.errors[:port]).to include "must be less than or equal to 65535"
|
||||
end
|
||||
|
@ -332,4 +327,4 @@ shared_examples_for 'Metasploit::Framework::LoginScanner::Base' do
|
|||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue