Merge pull request #21 from rapid7/feature/MSP-9687/winrm-loginscanner

Specs and functional steps passing. 

MSP-9687 #land
bug/bundler_fix
Samuel Huckins 2014-05-20 11:32:37 -05:00
commit 62bae8e23b
7 changed files with 184 additions and 14 deletions

View File

@ -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

View File

@ -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

View File

@ -37,6 +37,10 @@ module Metasploit
status == :success
end
def inspect
"#<#{self.class} #{credential.public}:#{credential.private}@#{credential.realm} #{status} >"
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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