Skip to content

Commit

Permalink
Merge branch 'master' into feature/MSP-10877/login-search-associations
Browse files Browse the repository at this point in the history
MSP-10877

Conflicts:
	lib/metasploit/credential/version.rb
	metasploit-credential.gemspec
	spec/dummy/db/schema.rb
  • Loading branch information
limhoff-r7 committed Jul 29, 2014
2 parents eee6ae1 + 5a0af4f commit ffb8434
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 15 deletions.
7 changes: 7 additions & 0 deletions db/migrate/20140722174919_old_creds_to_new_creds.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Implements a one-time migration of `Mdm::Cred` objects to
# appropriate objects from {Metasploit::Credential}
class OldCredsToNewCreds < ActiveRecord::Migration
def up
Metasploit::Credential::Migrator.new.migrate!
end
end
3 changes: 2 additions & 1 deletion lib/metasploit/credential.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ module Credential

autoload :Creation
autoload :EntityRelationshipDiagram
autoload :Importer
autoload :Exporter
autoload :Importer
autoload :Migrator
autoload :Origin
autoload :Text

Expand Down
19 changes: 9 additions & 10 deletions lib/metasploit/credential/creation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ def create_cracked_credential(opts={})
# )
def create_credential(opts={})
return nil unless active_db?
origin = create_credential_origin(opts)

if opts[:origin]
origin = opts[:origin]
else
origin = create_credential_origin(opts)
end

core_opts = {
origin: origin,
Expand Down Expand Up @@ -162,7 +167,7 @@ def create_credential_core(opts={})
# @raise [KeyError] if a required option is missing
# @return [NilClass] if there is no active database connection
# @return [Metasploit::Credential::Login]
def create_credential_login(opts)
def create_credential_login(opts={})
return nil unless active_db?
access_level = opts.fetch(:access_level, nil)
core = opts.fetch(:core)
Expand Down Expand Up @@ -239,21 +244,15 @@ def create_credential_origin_cracked_password(opts={})

# This method is responsible for creating {Metasploit::Credential::Origin::Import} objects.
#
# @option opts [Fixnum,nil] :task_id The ID of the `Mdm::Task` to link this Origin to
# @option opts [String] :filename The filename of the file that was imported
# @raise [KeyError] if a required option is missing
# @return [NilClass] if there is no connected database
# @return [Metasploit::Credential::Origin::Manual] The created {Metasploit::Credential::Origin::Import} object
def create_credential_origin_import(opts={})
return nil unless active_db?
# Required
conditions = {
filename: opts.fetch(:filename)
}
# Optional
conditions[:task_id] = opts[:task_id] if opts.has_key?(:task_id)
filename = opts.fetch(:filename)

Metasploit::Credential::Origin::Import.where(conditions).first_or_create
Metasploit::Credential::Origin::Import.where(filename: filename).first_or_create
end

# This method is responsible for creating {Metasploit::Credential::Origin::Manual} objects.
Expand Down
115 changes: 115 additions & 0 deletions lib/metasploit/credential/migrator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# This class provides migration behavior for converting `Mdm::Cred` objects to their appropriate counterparts in the
# {Metasploit::Credential} namespace.

class Metasploit::Credential::Migrator
include Metasploit::Credential::Creation

# @!attribute origin
# An origin for tracking how these credentials made it into the system.
# Treating this as an Import since there is no migration origin.
#
#
# @return [Metasploit::Credential::Origin::Import]
attr_accessor :origin

# @!attribute workspaces
# Where the migrated creds will originate
#
#
# @return [Array<Mdm::Workspace>]
attr_accessor :workspaces


# Sets up a migrator object with either a single workspace or all workspaces
# @param [Mdm::Workspace] the workspace to act on
# @return [void]
def initialize(workspace=nil)
@origin = Metasploit::Credential::Origin::Import.new(filename: 'MIGRATION')

if workspace.present?
@workspaces = [workspace]
else
@workspaces = Mdm::Workspace.all
end
end

# Returns true if {#workspaces} contains `Mdm::Cred` objects
# @return [Boolean]
def creds_exist?
workspaces.map(&:creds).flatten.present?
end

# Perform the migration
# @return [void]
def migrate!
if creds_exist?
Metasploit::Credential::Core.transaction do
origin.save # we are going to use the one we instantiated earlier, since there is work to be done
workspaces.each do |workspace|
convert_creds_in_workspace(workspace)
end
end
end
end

# Converts the `Mdm::Cred` objects in a single `Mdm::Workspace`
# @param [Mdm::Workspace] the workspace to act on
# @return [void]
def convert_creds_in_workspace(workspace)
workspace.creds.each do |cred|
service = cred.service
core = create_credential(
origin: origin,
private_data: private_data_from_cred(cred),
private_type: cred_type_to_credential_class(cred.ptype),
username: cred.user,
workspace_id: workspace.id
)

create_credential_login(
address: service.host.address,
core: core,
port: service.port,
protocol: service.proto,
service_name: service.name,
status: Metasploit::Model::Login::Status::UNTRIED,
workspace_id: workspace.id
)
end
end


# Converts types in the legacy credentials model to a type in the new one.
# Assumes that anything that isn't 'smb_hash' but contains 'hash' will be a
# NonreplaybleHash.
# @param cred_type [String] the value from the ptype field of `Mdm::Cred`
# @return[Symbol]
def cred_type_to_credential_class(cred_type)
return :ntlm_hash if cred_type == "smb_hash"
return :ssh_key if cred_type == "ssh_key"
return :nonreplayable_hash if cred_type.include? "hash"
return :password
end

# Returns the text of the SSH key as read from the file
# @param path [String] Path to an SSH key file on disk
# @return [String]
def key_data_from_file(path)
File.open(path) do |file|
file.read
end
end

# Returns private data given an `Mdm::Cred`
# @param cred [Mdm::Cred]
# @return[String]
def private_data_from_cred(cred)
case cred.ptype
when 'ssh_key'
key_data_from_file(cred.pass)
else
cred.pass
end
end

end
2 changes: 1 addition & 1 deletion lib/metasploit/credential/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Version
# The minor version number, scoped to the {MAJOR} version number.
MINOR = 7
# The patch number, scoped to the {MINOR} version number.
PATCH = 16
PATCH = 17
# The pre-release version, scoped to the {PATCH} version number.
PRERELEASE = 'login-search-associations'

Expand Down
2 changes: 2 additions & 0 deletions spec/dummy/db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3615,6 +3615,8 @@ INSERT INTO schema_migrations (version) VALUES ('20140702184622');

INSERT INTO schema_migrations (version) VALUES ('20140703144541');

INSERT INTO schema_migrations (version) VALUES ('20140722174919');

INSERT INTO schema_migrations (version) VALUES ('21');

INSERT INTO schema_migrations (version) VALUES ('22');
Expand Down
5 changes: 2 additions & 3 deletions spec/lib/metasploit/credential/creation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
it 'creates a Metasploit::Credential::Origin object' do
opts = {
filename: "test_import.xml",
task_id: task.id
}
expect { test_object.create_credential_origin_import(opts)}.to change{Metasploit::Credential::Origin::Import.count}.by(1)
end
Expand All @@ -43,9 +42,9 @@
end
end

context 'when missing an option' do
context 'when missing a required option' do
it 'throws a KeyError' do
opts = { }
opts = {}
expect{ test_object.create_credential_origin_import(opts)}.to raise_error KeyError
end
end
Expand Down
164 changes: 164 additions & 0 deletions spec/lib/metasploit/credential/migrator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
require 'spec_helper'

describe Metasploit::Credential::Migrator do
include_context 'Mdm::Workspace'

let(:workspace){ FactoryGirl.create(:mdm_workspace) }
let(:host){ FactoryGirl.create(:mdm_host, workspace: workspace)}
let(:service){ FactoryGirl.create(:mdm_service, host: host)}


subject(:migrator){ Metasploit::Credential::Migrator.new(workspace) }

describe "#convert_creds_in_workspace" do
describe "when there are no Mdm::Cred objects in the workspace" do
before(:each) do
workspace.creds = []
end

it 'should not change the Core count' do
expect{migrator.convert_creds_in_workspace(workspace)}.to_not change(Metasploit::Credential::Core, :count)
end

it 'should not change the Login count' do
expect{migrator.convert_creds_in_workspace(workspace)}.to_not change(Metasploit::Credential::Login, :count)
end

it 'should not change the Private count' do
expect{migrator.convert_creds_in_workspace(workspace)}.to_not change(Metasploit::Credential::Private, :count)
end

it 'should not change the Public count' do
expect{migrator.convert_creds_in_workspace(workspace)}.to_not change(Metasploit::Credential::Public, :count)
end
end

describe "when there are Mdm::Cred objects present in the workspace" do

let(:host1){ FactoryGirl.create(:mdm_host, workspace: workspace)}
let(:host2){ FactoryGirl.create(:mdm_host, workspace: workspace)}
let(:host3){ FactoryGirl.create(:mdm_host, workspace: workspace)}

let(:service1){ FactoryGirl.create(:mdm_service, host: host1)}
let(:service2){ FactoryGirl.create(:mdm_service, host: host2)}
let(:service3){ FactoryGirl.create(:mdm_service, host: host3)}

let(:cred1){ FactoryGirl.create(:mdm_cred, service: service1)}
let(:cred2){ FactoryGirl.create(:mdm_cred, service: service2)}
let(:cred3){ FactoryGirl.create(:mdm_cred, service: service3)}

before(:each) do
cred1; cred2; cred3
end

it 'should migrate them into Metasploit::Credential::Core objects' do
expect{migrator.convert_creds_in_workspace(workspace)}.to change(Metasploit::Credential::Core, :count).from(0).to(3)
end

describe "new Publics" do
before(:each) do
migrator.convert_creds_in_workspace(workspace)
end

it "should be created for each Mdm::Cred" do
Metasploit::Credential::Public.where(username: cred1.user).should_not be_blank
Metasploit::Credential::Public.where(username: cred2.user).should_not be_blank
Metasploit::Credential::Public.where(username: cred3.user).should_not be_blank
end
end

describe "new Privates" do
before(:each) do
migrator.convert_creds_in_workspace(workspace)
end

it "should be created for each Mdm::Cred" do
migrator.convert_creds_in_workspace(workspace)
Metasploit::Credential::Password.where(data: cred1.pass).should_not be_blank
Metasploit::Credential::Password.where(data: cred2.pass).should_not be_blank
Metasploit::Credential::Password.where(data: cred3.pass).should_not be_blank
end
end
end

describe "creating the proper kinds of Private objects" do
describe "when an Mdm::Cred is an SMB hash" do
let(:cred) do
FactoryGirl.create(:mdm_cred,
service: service,
ptype: 'smb_hash',
pass: FactoryGirl.build(:metasploit_credential_ntlm_hash, password_data: 'f00b4rawesomesauc3!').data
)
end

before(:each) do
migrator.convert_creds_in_workspace(cred.service.host.workspace)
end

it 'should create a new NTLMHash in the database' do
Metasploit::Credential::NTLMHash.where(data: cred.pass).should_not be_blank
end
end

describe "when an Mdm::Cred is an SSH key" do
let(:ssh_key_content){ FactoryGirl.build(:metasploit_credential_ssh_key).data }
let(:cred) do
FactoryGirl.create(:mdm_cred,
service: service,
ptype: 'ssh_key',
pass: '/path/to/ssh_key'
)
end

before(:each) do
migrator.stub(:key_data_from_file).and_return ssh_key_content
migrator.convert_creds_in_workspace(cred.service.host.workspace)
end

it 'should create a new SSHKey in the database' do
Metasploit::Credential::SSHKey.where(data: ssh_key_content).should_not be_blank
end
end

describe "when an Mdm::Cred is a password" do
let(:cred) do
FactoryGirl.create(:mdm_cred,
service: service,
ptype: 'password',
pass: FactoryGirl.build(:metasploit_credential_password, data: 'f00b4rawesomesauc3!').data
)
end

before(:each) do
migrator.convert_creds_in_workspace(cred.service.host.workspace)
end

it 'should create a new Password in the database' do
Metasploit::Credential::Password.where(data: cred.pass).should_not be_blank
end
end

describe "when an Mdm::Cred is another kind of hash" do
let(:cred) do
FactoryGirl.create(:mdm_cred,
service: service,
ptype: 'salted_sha1_hash',
pass: FactoryGirl.build(:metasploit_credential_nonreplayable_hash, password_data: 'f00b4rawesomesauc3!').data
)
end

before(:each) do
migrator.convert_creds_in_workspace(cred.service.host.workspace)
end

it 'should create a new NonreplayableHash in the database' do
Metasploit::Credential::NonreplayableHash.where(data: cred.pass).should_not be_blank
end
end
end



end

end

0 comments on commit ffb8434

Please sign in to comment.