diff --git a/Gemfile b/Gemfile index d29727a9..e62eb4fd 100755 --- a/Gemfile +++ b/Gemfile @@ -3,27 +3,33 @@ source "https://rubygems.org" # Specify your gem's dependencies in metasploit_data_models.gemspec gemspec +# This isn't in gemspec because metasploit-framework has its own patched version of 'metasploit-concern' that it needs +# to use instead of this gem. +# Patching inverse association in Mdm models. +gem 'metasploit-concern', github: 'crmaxx/metasploit-concern', branch: 'staging/rails-4.2' + +# This isn't in gemspec because metasploit-framework has its own patched version of 'metasploit-model' that it needs +# to use instead of this gem. +# Metasploit::Model::Search +gem 'metasploit-model', github: 'crmaxx/metasploit-model', branch: 'staging/rails-4.2' + group :development do - gem 'metasploit-erd', '~> 1.0' + gem 'metasploit-erd', github: 'crmaxx/metasploit-erd', branch: 'staging/rails-4.2' # embed ERDs on index, namespace Module and Class pages - gem 'yard-metasploit-erd', '~> 1.0' + gem 'yard-metasploit-erd', github: 'crmaxx/yard-metasploit-erd', branch: 'staging/rails-4.2' end # used by dummy application group :development, :test do + gem 'pry-byebug' # Upload coverage reports to coveralls.io - gem 'coveralls', require: false + gem 'coveralls', require: false # supplies factories for producing model instance for specs # Version 4.1.0 or newer is needed to support generate calls without the 'FactoryGirl.' in factory definitions syntax. gem 'factory_girl', '>= 4.1.0' # auto-load factories from spec/factories gem 'factory_girl_rails' - - rails_version_constraint = [ - '>= 4.0.9', - '< 4.1.0' - ] - gem 'rails', *rails_version_constraint + gem 'rails', '>= 4.2.1' # Used to create fake data gem "faker" end @@ -34,7 +40,7 @@ group :test do # add matchers from shoulda, such as validates_presence_of, which are useful for testing validations gem 'shoulda-matchers' # code coverage of tests - gem 'simplecov', :require => false + gem 'simplecov', require: false # need rspec-rails >= 2.12.0 as 2.12.0 adds support for redefining named subject in nested context that uses the # named subject from the outer context without causing a stack overflow. gem 'rspec-rails', '~> 3.2' diff --git a/app/models/mdm/cred.rb b/app/models/mdm/cred.rb index d6979c6f..91e56f25 100755 --- a/app/models/mdm/cred.rb +++ b/app/models/mdm/cred.rb @@ -11,11 +11,11 @@ class Mdm::Cred < ActiveRecord::Base # Maps {#ptype_human} to {#ptype}. PTYPES = { - 'read/write password' => 'password_rw', - 'read-only password' => 'password_ro', - 'SMB hash' => 'smb_hash', - 'SSH private key' => 'ssh_key', - 'SSH public key' => 'ssh_pubkey' + 'read/write password' => 'password_rw', + 'read-only password' => 'password_ro', + 'SMB hash' => 'smb_hash', + 'SSH private key' => 'ssh_key', + 'SSH public key' => 'ssh_pubkey' } # @@ -40,7 +40,7 @@ class Mdm::Cred < ActiveRecord::Base # # Tasks that touched this service - has_many :tasks, :through => :task_creds + has_many :tasks, through: :task_creds # # Attributes @@ -107,9 +107,9 @@ class Mdm::Cred < ActiveRecord::Base # # @return [String, nil] def ptype_human - humanized = PTYPES.select do |k, v| + humanized = PTYPES.select do |_, v| v == ptype - end.keys[0] + end.keys.first humanized ? humanized : ptype end @@ -119,9 +119,9 @@ def ptype_human # @return [String] SSH Key Id if ssh-type key and {#proof} matches {KEY_ID_REGEX}. # @return [nil] otherwise def ssh_key_id - return nil unless self.ptype =~ /^ssh_/ - return nil unless self.proof =~ KEY_ID_REGEX - $1.downcase # Can't run into NilClass problems. + return unless ptype =~ /^ssh_/ + return unless proof =~ KEY_ID_REGEX + Regexp.last_match[1].downcase # Can't run into NilClass problems. end # Returns whether `other`'s SSH private key or public key matches. @@ -133,53 +133,53 @@ def ssh_key_id # @return [false] if {#ssh_key_id} does not match. # @return [true] if {#ssh_key_id} matches. def ssh_key_matches?(other_cred) - return false unless other_cred.kind_of? self.class - return false unless self.ptype == other_cred.ptype - case self.ptype - when "ssh_key" - matches = self.ssh_private_keys - when "ssh_pubkey" - matches = self.ssh_public_keys - else - return false + return false unless other_cred.is_a? self.class + return false unless ptype == other_cred.ptype + + case ptype + when "ssh_key" + matches = ssh_private_keys + when "ssh_pubkey" + matches = ssh_public_keys + else + return false end - matches.include?(self) and matches.include?(other_cred) + + matches.include?(self) && matches.include?(other_cred) end # Returns all keys with matching key ids, including itself. # # @return [ActiveRecord::Relation] ssh_key and ssh_pubkey creds with matching {#ssh_key_id}. def ssh_keys - (self.ssh_private_keys | self.ssh_public_keys) + ssh_private_keys | ssh_public_keys end # Returns all private keys with matching {#ssh_key_id}, including itself. # # @return [ActiveRecord::Relation] ssh_key creds with matching {#ssh_key_id}. def ssh_private_keys - return [] unless self.ssh_key_id - matches = self.class.all( - :conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_key", "%#{self.ssh_key_id}%"] - ) - matches.select {|c| c.workspace == self.workspace} + return [] unless ssh_key_id + + matches = Mdm::Cred.where(ptype: "ssh_key").where(Mdm::Cred.arel_table[:proof].matches("%#{ssh_key_id}%")) + matches.select { |c| c.workspace == workspace } end # Returns all public keys with matching {#ssh_key_id}, including itself. # # @return [ActiveRecord::Relation] ssh_pubkey creds with matching {#ssh_key_id}. def ssh_public_keys - return [] unless self.ssh_key_id - matches = self.class.all( - :conditions => ["creds.ptype = ? AND creds.proof ILIKE ?", "ssh_pubkey", "%#{self.ssh_key_id}%"] - ) - matches.select {|c| c.workspace == self.workspace} + return [] unless ssh_key_id + + matches = Mdm::Cred.where(ptype: "ssh_pubkey").where(Mdm::Cred.arel_table[:proof].matches("%#{ssh_key_id}%")) + matches.select { |c| c.workspace == workspace } end # Returns its workspace # # @return [Mdm::Workspace] def workspace - self.service.host.workspace + service.host.workspace end private @@ -188,17 +188,18 @@ def workspace # # @return [void] def decrement_host_counter_cache - Mdm::Host.decrement_counter("cred_count", self.service.host_id) + Mdm::Host.decrement_counter("cred_count", service.host_id) end # Increments {Mdm::Host#cred_count}. # # @return [void] def increment_host_counter_cache - Mdm::Host.increment_counter("cred_count", self.service.host_id) + Mdm::Host.increment_counter("cred_count", service.host_id) end # Switch back to public for load hooks. + public Metasploit::Concern.run(self) diff --git a/app/models/mdm/host.rb b/app/models/mdm/host.rb index 3919794b..9d2ba207 100755 --- a/app/models/mdm/host.rb +++ b/app/models/mdm/host.rb @@ -19,47 +19,43 @@ class Mdm::Host < ActiveRecord::Base # name for exploits that run code in the programming language's virtual # machine. ARCHITECTURES = [ - 'armbe', - 'armle', - 'cbea', - 'cbea64', - 'cmd', - 'java', - 'mips', - 'mipsbe', - 'mipsle', - 'php', - 'ppc', - 'ppc64', - 'ruby', - 'sparc', - 'tty', - # To be used for compatability with 'X86_64' - 'x64', - 'x86', - 'x86_64', - '', - UNKNOWN_ARCHITECTURE + 'armbe', + 'armle', + 'cbea', + 'cbea64', + 'cmd', + 'java', + 'mips', + 'mipsbe', + 'mipsle', + 'php', + 'ppc', + 'ppc64', + 'ruby', + 'sparc', + 'tty', + # To be used for compatability with 'X86_64' + 'x64', + 'x86', + 'x86_64', + '', + UNKNOWN_ARCHITECTURE ] # Fields searched for the search scope SEARCH_FIELDS = [ - 'address::text', - 'comments', - 'mac', - 'name', - 'os_flavor', - 'os_name', - 'os_sp', - 'purpose' + 'address::text', + 'comments', + 'mac', + 'name', + 'os_flavor', + 'os_name', + 'os_sp', + 'purpose' ] # Valid values for {#state}. - STATES = [ - 'alive', - 'down', - 'unknown' - ] + STATES = %w(alive down unknown) # # Aggregations @@ -144,20 +140,20 @@ def address # @todo MSP-3065 # @return [ActiveRecord::Relation] has_many :loots, - -> { order('loots.created_at DESC')}, - class_name: 'Mdm::Loot', - dependent: :destroy, - inverse_of: :host + -> { order('loots.created_at DESC') }, + class_name: 'Mdm::Loot', + dependent: :destroy, + inverse_of: :host # @!attribute [rw] notes # Notes about the host entered by a user with {Mdm::Note#created_at oldest notes} first. # # @return [ActiveRecord::Relation] has_many :notes, - -> { order('notes.created_at') }, - class_name: 'Mdm::Note', - inverse_of: :host, - dependent: :delete_all + -> { order('notes.created_at') }, + class_name: 'Mdm::Note', + inverse_of: :host, + dependent: :delete_all # @!attribute [rw] services # The services running on {Mdm::Service#port ports} on the host with services ordered by {Mdm::Service#port port} @@ -165,10 +161,10 @@ def address # # @return [ActiveRecord::Relation] has_many :services, - -> { order('services.port, services.proto') }, - class_name: 'Mdm::Service', - dependent: :destroy, - inverse_of: :host + -> { order('services.port, services.proto') }, + class_name: 'Mdm::Service', + dependent: :destroy, + inverse_of: :host # @!attribute [rw] sessions # Sessions that are open or previously were open on the host ordered by {Mdm::Session#opened_at when the session was @@ -176,10 +172,10 @@ def address # # @return [ActiveRecord::Relation { order('sessions.opened_at') }, - class_name: 'Mdm::Session', - dependent: :destroy, - inverse_of: :host + -> { order('sessions.opened_at') }, + class_name: 'Mdm::Session', + dependent: :destroy, + inverse_of: :host # @!attribute [rw] vulns # Vulnerabilities found on the host. @@ -207,7 +203,7 @@ def address # # @return [ActiveRecord::Relation] # @see #hosts_tags - has_many :tags, :class_name => 'Mdm::Tag', :through => :hosts_tags + has_many :tags, class_name: 'Mdm::Tag', through: :hosts_tags # # Through services @@ -218,7 +214,7 @@ def address # # @return [ActiveRecord::Relation] # @see #services - has_many :creds, :class_name => 'Mdm::Cred', :through => :services + has_many :creds, class_name: 'Mdm::Cred', through: :services # @!attribute [r] service_notes # {Mdm::Note Notes} about {#services} running on this host. @@ -235,7 +231,7 @@ def address # # @return [ActiveRecord::Relation] # @see services - has_many :web_sites, :class_name => 'Mdm::WebSite', :through => :services + has_many :web_sites, class_name: 'Mdm::WebSite', through: :services # @!attribute [r] module_runs # Records of Metasploit modules being run on/against this {Mdm::Host} @@ -246,7 +242,6 @@ def address class_name: 'MetasploitDataModels::ModuleRun', as: :trackable - # # through: :task_hosts # @@ -270,7 +265,7 @@ def address # @return [ActiveRecord::Relation] # @see #refs # @see #vulns - has_many :vuln_refs, :class_name => 'Mdm::VulnRef', :source => :vulns_refs, :through => :vulns + has_many :vuln_refs, class_name: 'Mdm::VulnRef', source: :vulns_refs, through: :vulns # # Through vuln_refs @@ -281,7 +276,7 @@ def address # # @return [ActiveRecord::Relation] # @see #vuln_refs - has_many :refs, :class_name => 'Mdm::Ref', :through => :vuln_refs + has_many :refs, class_name: 'Mdm::Ref', through: :vuln_refs # # Through refs @@ -291,7 +286,7 @@ def address # {Mdm::Module::Ref References for modules} for {Mdm::Ref references for vulnerabilities}. # # @return [ActiveRecord::Relation] - has_many :module_refs, :class_name => 'Mdm::Module::Ref', :through => :refs + has_many :module_refs, class_name: 'Mdm::Module::Ref', through: :refs # # Through module_refs @@ -302,10 +297,10 @@ def address # # @return [ActiveRecord::Relation 'Mdm::Module::Detail', - :source =>:detail, - :through => :module_refs, - :uniq => true + -> { uniq }, + class_name: 'Mdm::Module::Detail', + source: :detail, + through: :module_refs # # Attributes @@ -456,57 +451,57 @@ def address # @note Must be declared after relations being referenced. # - accepts_nested_attributes_for :services, :reject_if => lambda { |s| s[:port].blank? }, :allow_destroy => true + accepts_nested_attributes_for :services, reject_if: ->(s) { s[:port].blank? }, allow_destroy: true # # Validations # validates :address, - :exclusion => { - :in => [IPAddr.new('127.0.0.1')] + exclusion: { + in: [IPAddr.new('127.0.0.1')] }, - :ip_format => true, - :presence => true, - :uniqueness => { - :scope => :workspace_id, - :unless => :ip_address_invalid? + ip_format: true, + presence: true, + uniqueness: { + scope: :workspace_id, + unless: :ip_address_invalid? } validates :arch, - :allow_blank => true, - :inclusion => { - :in => ARCHITECTURES + allow_blank: true, + inclusion: { + in: ARCHITECTURES } validates :state, - :allow_nil => true, - :inclusion => { - :in => STATES + allow_nil: true, + inclusion: { + in: STATES } - validates :workspace, :presence => true + validates :workspace, presence: true # # Scopes # - scope :alive, -> { where({'hosts.state' => 'alive'}) } - scope :flagged, -> { where('notes.critical = true AND notes.seen = false').includes(:notes) } - scope :search, - lambda { |*args| - # @todo replace with AREL - terms = SEARCH_FIELDS.collect { |field| - "#{self.table_name}.#{field} ILIKE ?" - } - disjunction = terms.join(' OR ') - formatted_parameter = "%#{args[0]}%" - parameters = [formatted_parameter] * SEARCH_FIELDS.length - conditions = [disjunction] + parameters - - { - :conditions => conditions - } - } - scope :tag_search, - lambda { |*args| where("tags.name" => args[0]).includes(:tags) } + scope :alive, -> { where(state: 'alive') } + scope :flagged, lambda { + includes(:notes).where(Mdm::Note.arel_table[:critical].eq(true).and(Mdm::Note.arel_table[:seen].eq(false))) + } + scope :search, lambda { |query| + address = Arel::Nodes::NamedFunction.new("CAST", [arel_table[:address].as("TEXT")]) + where( + arel_table[:comments].matches("%#{query}%"). + or(arel_table[:mac].matches("%#{query}%")). + or(arel_table[:name].matches("%#{query}%")). + or(arel_table[:os_flavor].matches("%#{query}%")). + or(arel_table[:os_name].matches("%#{query}%")). + or(arel_table[:os_sp].matches("%#{query}%")). + or(arel_table[:purpose].matches("%#{query}%")). + or(address.matches("%#{query}%")) + ) + } + # @todo replace with AREL + scope :tag_search, ->(*args) { where("tags.name" => args[0]).includes(:tags) } # # @@ -540,9 +535,9 @@ def address search_with MetasploitDataModels::Search::Operator::Multitext, name: :os, operator_names: [ - :os_name, - :os_flavor, - :os_sp + :os_name, + :os_flavor, + :os_sp ] search_with MetasploitDataModels::Search::Operator::IPAddress, @@ -566,17 +561,15 @@ def attribute_locked?(attr) # # @return [void] def ip_address_invalid? - begin - if address.is_a? IPAddr - potential_ip = address.dup - else - potential_ip = IPAddr.new(address) - end - - return true unless potential_ip.ipv4? || potential_ip.ipv6? - rescue ArgumentError - return true + if address.is_a? IPAddr + potential_ip = address.dup + else + potential_ip = IPAddr.new(address) end + + return true unless potential_ip.ipv4? || potential_ip.ipv6? + rescue ArgumentError + return true end # Returns whether this host is a virtual machine. @@ -584,13 +577,14 @@ def ip_address_invalid? # @return [true] unless {#virtual_host} is `nil`. # @return [false] otherwise. def is_vm? - !!self.virtual_host + return true unless virtual_host.nil? + false end private def normalize_arch - if attribute_present?(:arch) && !ARCHITECTURES.include?(self.arch) + if attribute_present?(:arch) && ARCHITECTURES.exclude?(arch) self.detected_arch = arch self.arch = UNKNOWN_ARCHITECTURE end diff --git a/app/models/mdm/host_tag.rb b/app/models/mdm/host_tag.rb index ec5f3d77..7b283c96 100755 --- a/app/models/mdm/host_tag.rb +++ b/app/models/mdm/host_tag.rb @@ -26,7 +26,7 @@ class Mdm::HostTag < ActiveRecord::Base # @see http://stackoverflow.com/a/11694704 after_destroy :destroy_orphan_tag - + # # Instance Methods # @@ -42,8 +42,8 @@ def destroy_orphan_tag end # switch back to public for load hooks + public Metasploit::Concern.run(self) end - diff --git a/app/models/mdm/service.rb b/app/models/mdm/service.rb index e61fccc6..90dc10f2 100755 --- a/app/models/mdm/service.rb +++ b/app/models/mdm/service.rb @@ -7,10 +7,10 @@ class Mdm::Service < ActiveRecord::Base # # Valid values for {#proto}. - PROTOS = %w{tcp udp} + PROTOS = %w(tcp udp) # Valid values for {#state}. - STATES = ['open', 'closed', 'filtered', 'unknown'] + STATES = %w(open closed filtered unknown) # # Associations @@ -104,7 +104,7 @@ class Mdm::Service < ActiveRecord::Base # Tasks that touched this service # # @return [Array] - has_many :tasks, :through => :task_services, :class_name => 'Mdm::Task' + has_many :tasks, through: :task_services, class_name: 'Mdm::Task' # # Through :web_sites @@ -114,19 +114,19 @@ class Mdm::Service < ActiveRecord::Base # Web pages in the {#web_sites} on top of this service. # # @return [Array] - has_many :web_pages, :through => :web_sites, :class_name => 'Mdm::WebPage' + has_many :web_pages, through: :web_sites, class_name: 'Mdm::WebPage' # @!attribute [r] web_forms # Form in the {#web_sites} on top of this service. # # @return [Array] - has_many :web_forms, :through => :web_sites, :class_name => 'Mdm::WebForm' + has_many :web_forms, through: :web_sites, class_name: 'Mdm::WebForm' # @!attribute [r] web_vulns # Vulnerabilities found in the {#web_sites} on top of this service. # # @return [Array] - has_many :web_vulns, :through => :web_sites, :class_name => 'Mdm::WebVuln' + has_many :web_vulns, through: :web_sites, class_name: 'Mdm::WebVuln' # # Attributes @@ -167,8 +167,8 @@ class Mdm::Service < ActiveRecord::Base # Scopes # - scope :inactive, -> { where("services.state != 'open'") } - scope :with_state, lambda { |a_state| where("services.state = ?", a_state)} + scope :inactive, -> { where.not(state: 'open') } + scope :with_state, ->(a_state) { where(state: a_state) } scope :search, lambda { |*args| where([ "services.name ILIKE ? OR " + @@ -195,14 +195,9 @@ class Mdm::Service < ActiveRecord::Base # Search Attributes # - search_attribute :info, - type: :string - search_attribute :name, - type: :string - search_attribute :proto, - type: { - set: :string - } + search_attribute :info, type: :string + search_attribute :name, type: :string + search_attribute :proto, type: { set: :string } # # Search Withs @@ -213,10 +208,7 @@ class Mdm::Service < ActiveRecord::Base # # Validations # - validates :port, - numericality: { - only_integer: true - } + validates :port, numericality: { only_integer: true } validates :port, uniqueness: { message: 'already exists on this host and protocol', @@ -227,10 +219,9 @@ class Mdm::Service < ActiveRecord::Base } validates :proto, inclusion: { - in: PROTOS + in: PROTOS } - # # Class Methods # diff --git a/app/models/mdm/task_host.rb b/app/models/mdm/task_host.rb index a96d376c..92f16a12 100644 --- a/app/models/mdm/task_host.rb +++ b/app/models/mdm/task_host.rb @@ -33,8 +33,8 @@ class Mdm::TaskHost < ActiveRecord::Base # validates :host_id, - :uniqueness => { - :scope => :task_id + uniqueness: { + scope: :task_id } Metasploit::Concern.run(self) diff --git a/app/models/mdm/vuln.rb b/app/models/mdm/vuln.rb index f55b2958..165eb182 100755 --- a/app/models/mdm/vuln.rb +++ b/app/models/mdm/vuln.rb @@ -1,6 +1,5 @@ # A vulnerability found on a {#host} or {#service}. class Mdm::Vuln < ActiveRecord::Base - # # Associations # @@ -67,7 +66,6 @@ class Mdm::Vuln < ActiveRecord::Base inverse_of: :vuln, dependent: :delete_all - # # Through :vuln_refs # @@ -76,7 +74,7 @@ class Mdm::Vuln < ActiveRecord::Base # External references to this vulnerability. # # @return [ActiveRecord::Relation] - has_many :refs, :class_name => 'Mdm::Ref', :through => :vulns_refs + has_many :refs, class_name: 'Mdm::Ref', through: :vulns_refs # # Through refs @@ -86,8 +84,7 @@ class Mdm::Vuln < ActiveRecord::Base # References in module that match {Mdm::Ref#name names} in {#refs}. # # @return [ActiveRecord::Relation] - has_many :module_refs, :class_name => 'Mdm::Module::Ref', :through => :refs - + has_many :module_refs, class_name: 'Mdm::Module::Ref', through: :refs # @!attribute [r] module_runs # References to times that a module has been run to exercise this vuln @@ -106,10 +103,10 @@ class Mdm::Vuln < ActiveRecord::Base # # @return [ActiveRecord::Relation] has_many :module_details, - -> { uniq }, - :class_name => 'Mdm::Module::Detail', - :source => :detail, - :through => :module_refs + -> { uniq }, + class_name: 'Mdm::Module::Detail', + source: :detail, + through: :module_refs # # Attributes # @@ -150,16 +147,17 @@ class Mdm::Vuln < ActiveRecord::Base # scope :search, lambda { |query| - formatted_query = "%#{query}%" - - where( - arel_table[:name].matches(formatted_query).or( - arel_table[:info].matches(formatted_query) - ).or( - Mdm::Ref.arel_table[:name].matches(formatted_query) - ) - ).includes( - :refs + vuln = Mdm::Vuln.arel_table + ref = Mdm::Ref.arel_table + vuln_ref = Mdm::VulnRef.arel_table + + vuln_ref_join_source = vuln.join(vuln_ref, Arel::Nodes::OuterJoin).on(vuln_ref[:vuln_id].eq(vuln[:id])).join_sources + ref_join_source = vuln_ref.join(ref, Arel::Nodes::OuterJoin).on(vuln_ref[:ref_id].eq(ref[:id])).join_sources + + joins(vuln_ref_join_source, ref_join_source).where( + vuln[:name].matches("%#{query}%"). + or(vuln[:info].matches("%#{query}%")). + or(ref[:name].matches("%#{query}%")) ) } @@ -167,14 +165,14 @@ class Mdm::Vuln < ActiveRecord::Base # Validations # - validates :name, :presence => true - validates :name, length: {maximum: 255} + validates :name, presence: true + validates :name, length: { maximum: 255 } validates_associated :refs private def save_refs - refs.each { |ref| ref.save(:validate => false) } + refs.each { |ref| ref.save(validate: false) } end public diff --git a/app/models/mdm/vuln_ref.rb b/app/models/mdm/vuln_ref.rb index 799bd71a..5dd65b3e 100644 --- a/app/models/mdm/vuln_ref.rb +++ b/app/models/mdm/vuln_ref.rb @@ -18,4 +18,3 @@ class Mdm::VulnRef < ActiveRecord::Base Metasploit::Concern.run(self) end - diff --git a/app/models/mdm/web_vuln.rb b/app/models/mdm/web_vuln.rb index 465672c4..d6a53d30 100755 --- a/app/models/mdm/web_vuln.rb +++ b/app/models/mdm/web_vuln.rb @@ -11,27 +11,21 @@ # end # end class Mdm::WebVuln < ActiveRecord::Base - # # CONSTANTS # # A percentage {#confidence} that the vulnerability is real and not a false positive. - CONFIDENCE_RANGE = 0 .. 100 + CONFIDENCE_RANGE = 0..100 # Default value for {#params} DEFAULT_PARAMS = [] # Allowed {#method methods}. - METHODS = [ - 'GET', - # XXX I don't know why PATH is a valid method when it's not an HTTP Method/Verb - 'PATH', - 'POST' - ] + METHODS = %w(GET PATH POST) # {#risk Risk} is rated on a scale from 0 (least risky) to 5 (most risky). - RISK_RANGE = 0 .. 5 + RISK_RANGE = 0..5 # # Associations @@ -114,34 +108,24 @@ class Mdm::WebVuln < ActiveRecord::Base # Validations # - validates :category, :presence => true + validates :category, presence: true validates :confidence, - :inclusion => { - :in => CONFIDENCE_RANGE + inclusion: { + in: CONFIDENCE_RANGE } validates :method, - :inclusion => { - :in => METHODS + inclusion: { + in: METHODS } - validates :name, :presence => true - validates :params, :parameters => true - validates :path, :presence => true - validates :proof, :presence => true + validates :name, presence: true + validates :params, parameters: true + validates :path, presence: true + validates :proof, presence: true validates :risk, - :inclusion => { - :in => RISK_RANGE + inclusion: { + in: RISK_RANGE } - validates :web_site, :presence => true - - # - # Serializations - # - - # @!attribute [rw] params - # Parameters sent as part of request - # - # @return [Array] Array of parameter key value pairs - serialize :params, MetasploitDataModels::Base64Serializer.new(:default => DEFAULT_PARAMS) + validates :web_site, presence: true # # Methods @@ -151,20 +135,29 @@ class Mdm::WebVuln < ActiveRecord::Base # # @return [Array>] def params - normalize_params( - read_attribute(:params) - ) + value = normalize_params(read_attribute(:params)) + if value.blank? + default_params + else + MetasploitDataModels::Base64Serializer::LOADERS.each do |loader| + begin + return loader.call(value) + rescue + next + else + break + end + end + end end # Set parameters sent as part of request. # # @param params [Array>, nil] Array of parameter key value pairs # @return [void] - def params=(params) - write_attribute( - :params, - normalize_params(params) - ) + def params=(val) + value = normalize_params(val) + write_attribute(:params, [Marshal.dump(value)].pack('m')) end private @@ -186,8 +179,8 @@ def normalize_params(params) end # switch back to public for load hooks + public Metasploit::Concern.run(self) end - diff --git a/app/models/mdm/workspace.rb b/app/models/mdm/workspace.rb index 0fe8a981..3f0f2279 100755 --- a/app/models/mdm/workspace.rb +++ b/app/models/mdm/workspace.rb @@ -25,23 +25,23 @@ class Mdm::Workspace < ActiveRecord::Base # `Metasploit::Credential::Core`s gathered from this workspace's {#hosts} and {#services}. # # Creds gathered from this workspace's {#hosts} and {#services}. - has_many :creds, :through => :services, :class_name => 'Mdm::Cred' + has_many :creds, through: :services, class_name: 'Mdm::Cred' # Events that occurred in this workspace. - has_many :events, :class_name => 'Mdm::Event' + has_many :events, class_name: 'Mdm::Event' # Hosts in this workspace. - has_many :hosts, :dependent => :destroy, :class_name => 'Mdm::Host' + has_many :hosts, dependent: :destroy, class_name: 'Mdm::Host' # Listeners running for this workspace. - has_many :listeners, :dependent => :destroy, :class_name => 'Mdm::Listener' + has_many :listeners, dependent: :destroy, class_name: 'Mdm::Listener' # Notes about this workspace. - has_many :notes, :class_name => 'Mdm::Note' + has_many :notes, class_name: 'Mdm::Note' # User that owns this workspace and has full permissions within this workspace even if they are not an # {Mdm::User#admin administrator}. - belongs_to :owner, :class_name => 'Mdm::User', :foreign_key => 'owner_id' + belongs_to :owner, class_name: 'Mdm::User', foreign_key: 'owner_id' # Tasks run inside this workspace. has_many :tasks, @@ -61,13 +61,13 @@ class Mdm::Workspace < ActiveRecord::Base # # Social engineering campaign or browser autopwn clients from {#hosts} in this workspace. - has_many :clients, :through => :hosts, :class_name => 'Mdm::Client' + has_many :clients, through: :hosts, class_name: 'Mdm::Client' # Hosts exploited in this workspace. - has_many :exploited_hosts, :through => :hosts, :class_name => 'Mdm::ExploitedHost' + has_many :exploited_hosts, through: :hosts, class_name: 'Mdm::ExploitedHost' # Loot gathered from {#hosts} in this workspace. - has_many :loots, :through => :hosts, :class_name => 'Mdm::Loot' + has_many :loots, through: :hosts, class_name: 'Mdm::Loot' # Services running on {#hosts} in this workspace. has_many :services, @@ -76,10 +76,10 @@ class Mdm::Workspace < ActiveRecord::Base through: :hosts # Vulnerabilities found on {#hosts} in this workspace. - has_many :vulns, :through => :hosts, :class_name => 'Mdm::Vuln' + has_many :vulns, through: :hosts, class_name: 'Mdm::Vuln' # Sessions opened on {#hosts} in this workspace. - has_many :sessions, :through => :hosts, :class_name => 'Mdm::Session' + has_many :sessions, through: :hosts, class_name: 'Mdm::Session' # # Attributes @@ -127,8 +127,8 @@ class Mdm::Workspace < ActiveRecord::Base # Validations # - validates :name, :presence => true, :uniqueness => true, :length => {:maximum => 255} - validates :description, :length => {:maximum => 4096} + validates :name, presence: true, uniqueness: true, length: { maximum: 255 } + validates :description, length: { maximum: 4096 } validate :boundary_must_be_ip_range # @@ -154,7 +154,7 @@ def allow_actions_on?(ips) ok_range = Rex::Socket::RangeWalker.new(boundary) allowed = true if ok_range.include_range? given_range end - return allowed + allowed end # Validates that {#boundary} is {#valid_ip_or_range? a valid IP address or IP address range}. @@ -170,11 +170,14 @@ def boundary_must_be_ip_range # # @return [ActiveRecord::Relation] def creds - Mdm::Cred.find( - :all, - :include => {:service => :host}, - :conditions => ["hosts.workspace_id = ?", self.id] - ) + host = Mdm::Host.arel_table + cred = Mdm::Cred.arel_table + service = Mdm::Service.arel_table + + service_join_source = cred.join(service, Arel::Nodes::OuterJoin).on(cred[:service_id].eq(service[:id])).join_sources + host_join_source = service.join(host, Arel::Nodes::OuterJoin).on(service[:host_id].eq(host[:id])).join_sources + + Mdm::Cred.joins(service_join_source, host_join_source).where(host[:workspace_id].eq(id)) end # Returns default {Mdm::Workspace}. @@ -224,11 +227,14 @@ def each_host_tag(&block) # # @return [ActiveRecord::Relation] def host_tags - Mdm::Tag.find( - :all, - :include => :hosts, - :conditions => ["hosts.workspace_id = ?", self.id] - ) + host = Mdm::Host.arel_table + tag = Mdm::Tag.arel_table + host_tag = Mdm::HostTag.arel_table + + tag_join_source = tag.join(host_tag, Arel::Nodes::OuterJoin).on(host_tag[:tag_id].eq(tag[:id])).join_sources + host_tag_join_source = host_tag.join(host, Arel::Nodes::OuterJoin).on(host[:id].eq(host_tag[:host_id])).join_sources + + Mdm::Tag.joins(tag_join_source, host_tag_join_source).where(host[:workspace_id].eq(id)) end # Web forms found on {#web_sites}. @@ -246,7 +252,6 @@ def web_forms Mdm::WebForm.find_by_sql(query) end - # Web pages found on {#web_sites}. # # @return [ActiveRecord::Relation] diff --git a/app/models/metasploit_data_models/search/visitor/relation.rb b/app/models/metasploit_data_models/search/visitor/relation.rb index af46ef3c..de8a899b 100644 --- a/app/models/metasploit_data_models/search/visitor/relation.rb +++ b/app/models/metasploit_data_models/search/visitor/relation.rb @@ -6,11 +6,7 @@ class MetasploitDataModels::Search::Visitor::Relation < Metasploit::Model::Base # `ActiveRecord::Relation` methods that can compute their argument with a visitor under the # {MetasploitDataModels::Search::Visitor} namespace. - RELATION_METHODS = [ - :joins, - :includes, - :where - ] + RELATION_METHODS = %i(joins includes where) # # Attributes @@ -27,9 +23,7 @@ class MetasploitDataModels::Search::Visitor::Relation < Metasploit::Model::Base # validate :valid_query - - validates :query, - :presence => true + validates :query, presence: true # # Methods @@ -55,10 +49,10 @@ def visit # @return [Hash{Symbol => #visit}] def visitor_by_relation_method # Enumerable#each_with_object does not support 3 arity for Hashes so need to unpack pair - @visitor_by_relation_method ||= self.class.visitor_class_by_relation_method.each_with_object({}) { |pair, visitor_by_relation_method| + @visitor_by_relation_method ||= self.class.visitor_class_by_relation_method.each_with_object({}) do |pair, visitor_by_relation_method| relation_method, visitor_class = pair visitor_by_relation_method[relation_method] = visitor_class.new - } + end end # Maps method on `ActiveRecord::Relation` to the `Class` under {MetasploitDataModels::Search::Visitor} whose @@ -66,12 +60,12 @@ def visitor_by_relation_method # # @return [Hash{Symbol => Class}] def self.visitor_class_by_relation_method - @relation_method_by_visitor_class ||= RELATION_METHODS.each_with_object({}) { |relation_method, relation_method_by_visitor_class| + @relation_method_by_visitor_class ||= RELATION_METHODS.each_with_object({}) do |relation_method, relation_method_by_visitor_class| visitor_class_name = "#{parent.name}::#{relation_method.to_s.camelize}" visitor_class = visitor_class_name.constantize relation_method_by_visitor_class[relation_method] = visitor_class - } + end end private @@ -80,9 +74,7 @@ def self.visitor_class_by_relation_method # # @return [void] def valid_query - if query and !query.valid? - errors.add(:query, :invalid) - end + errors.add(:query, :invalid) if query && !query.valid? end private diff --git a/app/models/metasploit_data_models/search/visitor/where.rb b/app/models/metasploit_data_models/search/visitor/where.rb index befa9bd7..f5dbe229 100644 --- a/app/models/metasploit_data_models/search/visitor/where.rb +++ b/app/models/metasploit_data_models/search/visitor/where.rb @@ -73,18 +73,14 @@ class MetasploitDataModels::Search::Visitor::Where value_node = visit value case value - when MetasploitDataModels::IPAddress::CIDR - Arel::Nodes::InfixOperation.new( - '<<', - attribute, - value_node - ) - when MetasploitDataModels::IPAddress::Range - Arel::Nodes::Between.new(attribute, value_node) - when MetasploitDataModels::IPAddress::V4::Single - Arel::Nodes::Equality.new(attribute, value_node) - else - raise TypeError, "Don't know how to handle #{value.class}" + when MetasploitDataModels::IPAddress::CIDR + Arel::Nodes::InfixOperation.new('<<', attribute, value_node) + when MetasploitDataModels::IPAddress::Range + Arel::Nodes::Between.new(attribute, value_node) + when MetasploitDataModels::IPAddress::V4::Single + Arel::Nodes::Equality.new(attribute, value_node) + else + fail TypeError, "Don't know how to handle #{value.class}" end end @@ -118,7 +114,7 @@ def method_visitor # # @return [Arel::Nodes::NamedFunction] def cast_to_inet(string) - cast_argument = Arel::Nodes::As.new(string, Arel::Nodes::SqlLiteral.new('INET')) + cast_argument = Arel::Nodes::As.new(Arel::Nodes::Quoted.new(string), Arel::Nodes::SqlLiteral.new('INET')) Arel::Nodes::NamedFunction.new('CAST', [cast_argument]) end diff --git a/app/validators/ip_format_validator.rb b/app/validators/ip_format_validator.rb index ed25d855..5516949e 100644 --- a/app/validators/ip_format_validator.rb +++ b/app/validators/ip_format_validator.rb @@ -6,14 +6,14 @@ class IpFormatValidator < ActiveModel::EachValidator # # @return [void] def validate_each(object, attribute, value) - error_message_block = lambda{ object.errors[attribute] << " must be a valid IPv4 or IPv6 address" } + error_message_block = lambda { object.errors[attribute] << " must be a valid IPv4 or IPv6 address" } begin if value.is_a? IPAddr potential_ip = value.dup else potential_ip = IPAddr.new(value) end - + error_message_block.call unless potential_ip.ipv4? || potential_ip.ipv6? rescue ArgumentError error_message_block.call diff --git a/app/validators/parameters_validator.rb b/app/validators/parameters_validator.rb index 02dbf8b9..a585b996 100644 --- a/app/validators/parameters_validator.rb +++ b/app/validators/parameters_validator.rb @@ -28,9 +28,9 @@ def validate_each(record, attribute, value) end length_error = length_error_at( - :extreme => extreme, - :element => element, - :index => index + extreme: extreme, + element: element, + index: index ) record.errors[attribute] << length_error @@ -40,17 +40,17 @@ def validate_each(record, attribute, value) if parameter_name.is_a? String unless parameter_name.present? error = error_at( - :element => element, - :index => index, - :prefix => "has blank parameter name" + element: element, + index: index, + prefix: "has blank parameter name" ) record.errors[attribute] << error end else error = error_at( - :element => element, - :index => index, - :prefix => "has non-String parameter name (#{parameter_name.inspect})" + element: element, + index: index, + prefix: "has non-String parameter name (#{parameter_name.inspect})" ) record.errors[attribute] << error end @@ -59,18 +59,18 @@ def validate_each(record, attribute, value) unless parameter_value.is_a? String error = error_at( - :element => element, - :index => index, - :prefix => "has non-String parameter value (#{parameter_value.inspect})" + element: element, + index: index, + prefix: "has non-String parameter value (#{parameter_value.inspect})" ) record.errors[attribute] << error end end else error = error_at( - :element => element, - :index => index, - :prefix => 'has non-Array' + element: element, + index: index, + prefix: 'has non-Array' ) record.errors[attribute] << error end @@ -82,19 +82,19 @@ def validate_each(record, attribute, value) private - def error_at(options={}) + def error_at(options = {}) options.assert_valid_keys(:element, :index, :prefix) prefix = options.fetch(:prefix) clause = location_clause( - :element => options[:element], - :index => options[:index] + element: options[:element], + index: options[:index] ) sentence = "#{prefix} #{clause}." sentences = [ - sentence, - TYPE_SIGNATURE_SENTENCE + sentence, + TYPE_SIGNATURE_SENTENCE ] error = sentences.join(" ") @@ -102,21 +102,21 @@ def error_at(options={}) error end - def length_error_at(options={}) + def length_error_at(options = {}) options.assert_valid_keys(:element, :extreme, :index) extreme = options.fetch(:extreme) prefix = "has too #{extreme} elements" error = error_at( - :element => options[:element], - :index => options[:index], - :prefix => prefix + element: options[:element], + index: options[:index], + prefix: prefix ) error end - def location_clause(options={}) + def location_clause(options = {}) options.assert_valid_keys(:element, :index) element = options.fetch(:element) @@ -126,4 +126,4 @@ def location_clause(options={}) clause end -end \ No newline at end of file +end diff --git a/config/initializers/activerecord_monkeypath.rb b/config/initializers/activerecord_monkeypath.rb new file mode 100644 index 00000000..f8b9b1c0 --- /dev/null +++ b/config/initializers/activerecord_monkeypath.rb @@ -0,0 +1,20 @@ +module ActiveRecord + module Scoping + module Named + extend ActiveSupport::Concern + module ClassMethods + def scoped(options = nil) + if options + scoped.apply_finder_options(options) + else + if current_scope + current_scope.clone + else + relation + end + end + end + end + end + end +end diff --git a/config/initializers/ipaddr.rb b/config/initializers/ipaddr.rb index e71cfd2a..5e84377f 100644 --- a/config/initializers/ipaddr.rb +++ b/config/initializers/ipaddr.rb @@ -6,30 +6,24 @@ module IPAddrExtensions rescue NameError => e puts e.message end - + alias_method :spaceship_without_rescue, :<=> alias_method :<=>, :spaceship_with_rescue - + alias_method_chain :include?, :rescue end - def spaceship_with_rescue(other) - begin - spaceship_without_rescue(other) - rescue ArgumentError - false - end + spaceship_without_rescue(other) + rescue ArgumentError + false end def include_with_rescue?(other) - begin - include_without_rescue?(other) - rescue ArgumentError - false - end + include_without_rescue?(other) + rescue ArgumentError + false end - end -IPAddr.send(:include, IPAddrExtensions) \ No newline at end of file +IPAddr.send(:include, IPAddrExtensions) diff --git a/lib/mdm.rb b/lib/mdm.rb index 3f1b2c53..ab2d3bc5 100644 --- a/lib/mdm.rb +++ b/lib/mdm.rb @@ -52,4 +52,4 @@ module Mdm def self.use_relative_model_naming? true end -end \ No newline at end of file +end diff --git a/lib/metasploit_data_models.rb b/lib/metasploit_data_models.rb index 716b478b..28366487 100755 --- a/lib/metasploit_data_models.rb +++ b/lib/metasploit_data_models.rb @@ -52,4 +52,3 @@ def self.root @root end end - diff --git a/lib/metasploit_data_models/base64_serializer.rb b/lib/metasploit_data_models/base64_serializer.rb index 9965cf0b..8a9d4e4f 100755 --- a/lib/metasploit_data_models/base64_serializer.rb +++ b/lib/metasploit_data_models/base64_serializer.rb @@ -8,92 +8,83 @@ # @example Overriding default to [] # serialize :bar, MetasploitDataModels::Base64Serializer.new(:default => []) # -class MetasploitDataModels::Base64Serializer - # - # CONSTANTS - # +module MetasploitDataModels + class Base64Serializer + attr_writer :default + # + # CONSTANTS + # - # The default for {#default} - DEFAULT = {} - # Deserializers for {#load} - # 1. Base64 decoding and then unmarshalling the value. - # 2. Parsing the value as YAML. - # 3. The raw value. - LOADERS = [ - lambda { |serialized| - marshaled = serialized.unpack('m').first - # Load the unpacked Marshal object first - Marshal.load(marshaled) - }, - lambda { |serialized| - # Support legacy YAML encoding for existing data - YAML.load(serialized) - }, - lambda { |serialized| - # Fall back to string decoding - serialized - } - ] + # The default for {#default} + DEFAULT = {} + # Deserializers for {#load} + # 1. Base64 decoding and then unmarshalling the value. + # 2. Parsing the value as YAML. + # 3. The raw value. + LOADERS = [ + # Load the unpacked Marshal object first + ->(serialized) { Marshal.load(serialized.unpack('m').first) }, + # Support legacy YAML encoding for existing data + ->(serialized) { YAML.load(serialized) }, + # Fall back to string decoding + ->(serialized) { serialized } + ] - # - # Methods - # + # + # Methods + # - # Creates a duplicate of default value - # - # @return - def default - @default.dup - end - - attr_writer :default - - # Serializes the value by marshalling the value and then base64 encodes the marshaled value. - # - # @param value [Object] value to serialize - # @return [String] - def dump(value) - # Always store data back in the Marshal format - marshalled = Marshal.dump(value) - base64_encoded = [ marshalled ].pack('m') - - base64_encoded - end + # @param attributes [Hash] attributes + # @option attributes [Object] :default ({}) Value to use for {#default}. + def initialize(attributes = {}) + attributes.assert_valid_keys(:default) - # @param attributes [Hash] attributes - # @option attributes [Object] :default ({}) Value to use for {#default}. - def initialize(attributes={}) - attributes.assert_valid_keys(:default) + @default = attributes.fetch(:default, DEFAULT) + end - @default = attributes.fetch(:default, DEFAULT) - end + # Creates a duplicate of default value + # + # @return + def default + @default.dup + end - # Deserializes the value by either - # 1. Base64 decoding and then unmarshalling the value. - # 2. Parsing the value as YAML. - # 3. Returns the raw value. - # - # @param value [String] serialized value - # @return [Object] - # - # @see #default - def load(value) - loaded = nil + # Deserializes the value by either + # 1. Base64 decoding and then unmarshalling the value. + # 2. Parsing the value as YAML. + # 3. Returns the raw value. + # + # @param value [String] serialized value + # @return [Object] + # + # @see #default + def load(value) + loaded = nil - if value.blank? - loaded = default - else - LOADERS.each do |loader| - begin - loaded = loader.call(value) - rescue - next - else - break + if value.blank? + loaded = default + else + LOADERS.each do |loader| + begin + loaded = loader.call(value) + rescue + next + else + break + end end end + + loaded end - loaded + # Serializes the value by marshalling the value and then base64 encodes the marshaled value. + # + # @param value [Object] value to serialize + # @return [String] + def dump(value) + # Always store data back in the Marshal format + [Marshal.dump(value)].pack('m') + end end end diff --git a/lib/metasploit_data_models/change_required_columns_to_null_false.rb b/lib/metasploit_data_models/change_required_columns_to_null_false.rb index 1b975b47..52e022a6 100644 --- a/lib/metasploit_data_models/change_required_columns_to_null_false.rb +++ b/lib/metasploit_data_models/change_required_columns_to_null_false.rb @@ -18,4 +18,4 @@ def up change_column_null(self.class::TABLE_NAME, column, false) end end -end \ No newline at end of file +end diff --git a/lib/metasploit_data_models/ip_address.rb b/lib/metasploit_data_models/ip_address.rb index 48eef412..3857b9e6 100644 --- a/lib/metasploit_data_models/ip_address.rb +++ b/lib/metasploit_data_models/ip_address.rb @@ -6,4 +6,4 @@ module MetasploitDataModels::IPAddress autoload :CIDR autoload :Range autoload :V4 -end \ No newline at end of file +end diff --git a/lib/metasploit_data_models/search.rb b/lib/metasploit_data_models/search.rb index bdb60ca9..2873467d 100644 --- a/lib/metasploit_data_models/search.rb +++ b/lib/metasploit_data_models/search.rb @@ -5,4 +5,4 @@ module MetasploitDataModels::Search autoload :Operation autoload :Operator autoload :Visitor -end \ No newline at end of file +end diff --git a/lib/metasploit_data_models/serialized_prefs.rb b/lib/metasploit_data_models/serialized_prefs.rb index e92c89b1..0ff766ac 100755 --- a/lib/metasploit_data_models/serialized_prefs.rb +++ b/lib/metasploit_data_models/serialized_prefs.rb @@ -24,4 +24,4 @@ def #{method_name}=(value) class_eval method_declarations, __FILE__, __LINE__ end end -end \ No newline at end of file +end diff --git a/lib/metasploit_data_models/version.rb b/lib/metasploit_data_models/version.rb index 3bef0d0a..af95a9ba 100755 --- a/lib/metasploit_data_models/version.rb +++ b/lib/metasploit_data_models/version.rb @@ -11,6 +11,7 @@ module Version MINOR = 0 # The patch version number, scoped to the {MAJOR} and {MINOR} version numbers. PATCH = 1 + PRERELEASE = 'rails-4.2' # # Module Methods @@ -26,11 +27,7 @@ module Version # on any branch other than master. def self.full version = "#{MAJOR}.#{MINOR}.#{PATCH}" - - if defined? PRERELEASE - version = "#{version}-#{PRERELEASE}" - end - + version = "#{version}-#{PRERELEASE}" if defined? PRERELEASE version end diff --git a/metasploit_data_models.gemspec b/metasploit_data_models.gemspec index 96500b9a..42ceb092 100644 --- a/metasploit_data_models.gemspec +++ b/metasploit_data_models.gemspec @@ -6,25 +6,25 @@ Gem::Specification.new do |s| s.name = 'metasploit_data_models' s.version = MetasploitDataModels::VERSION s.authors = [ - 'Samuel Huckins', - 'Luke Imhoff', - "David 'thelightcosine' Maloney", - "Trevor 'burlyscudd' Rosen" + 'Samuel Huckins', + 'Luke Imhoff', + "David 'thelightcosine' Maloney", + "Trevor 'burlyscudd' Rosen" ] s.email = [ - 'shuckins@rapid7.com', - 'luke_imhoff@rapid7.com', - 'dmaloney@rapid7.com', - 'trevor_rosen@rapid7.com' + 'shuckins@rapid7.com', + 'luke_imhoff@rapid7.com', + 'dmaloney@rapid7.com', + 'trevor_rosen@rapid7.com' ] s.homepage = "" - s.summary = %q{Database code for MSF and Metasploit Pro} - s.description = %q{Implements minimal ActiveRecord models and database helper code used in both the Metasploit Framework (MSF) and Metasploit commercial editions.} + s.summary = %q(Database code for MSF and Metasploit Pro) + s.description = %q(Implements minimal ActiveRecord models and database helper code used in both the Metasploit Framework (MSF) and Metasploit commercial editions.) s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - s.require_paths = %w{app/models app/validators lib} + s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } + s.require_paths = %w(app/models app/validators lib) s.required_ruby_version = '>= 2.1' @@ -33,8 +33,6 @@ Gem::Specification.new do |s| # documentation s.add_development_dependency 'metasploit-yard', '~> 1.0' s.add_development_dependency 'yard-activerecord', '~> 0.0.14' - # embed ERDs on index, namespace Module and Class pages - s.add_development_dependency 'yard-metasploit-erd', '~> 0.1.0' s.add_development_dependency 'rake' @@ -44,13 +42,11 @@ Gem::Specification.new do |s| # debugging s.add_development_dependency 'pry' - rails_version_constraints = ['>= 4.0.9', '< 4.1.0'] + rails_version_constraints = '>= 4.2.1' - s.add_runtime_dependency 'activerecord', *rails_version_constraints - s.add_runtime_dependency 'activesupport', *rails_version_constraints - s.add_runtime_dependency 'metasploit-concern', '~> 1.0' - s.add_runtime_dependency 'metasploit-model', '~> 1.0' - s.add_runtime_dependency 'railties', *rails_version_constraints + s.add_runtime_dependency 'activerecord', rails_version_constraints + s.add_runtime_dependency 'activesupport', rails_version_constraints + s.add_runtime_dependency 'railties', rails_version_constraints # os fingerprinting s.add_runtime_dependency 'recog', '~> 1.0' diff --git a/spec/app/models/mdm/host_spec.rb b/spec/app/models/mdm/host_spec.rb index 00dfe195..47a6d3aa 100644 --- a/spec/app/models/mdm/host_spec.rb +++ b/spec/app/models/mdm/host_spec.rb @@ -5,34 +5,34 @@ let(:architectures) do [ - 'armbe', - 'armle', - 'cbea', - 'cbea64', - 'cmd', - 'java', - 'mips', - 'mipsbe', - 'mipsle', - 'php', - 'ppc', - 'ppc64', - 'ruby', - 'sparc', - 'tty', - 'x64', - 'x86', - 'x86_64', - '', - 'Unknown', + 'armbe', + 'armle', + 'cbea', + 'cbea64', + 'cmd', + 'java', + 'mips', + 'mipsbe', + 'mipsle', + 'php', + 'ppc', + 'ppc64', + 'ruby', + 'sparc', + 'tty', + 'x64', + 'x86', + 'x86_64', + '', + 'Unknown' ] end let(:states) do [ - 'alive', - 'down', - 'unknown' + 'alive', + 'down', + 'unknown' ] end @@ -56,16 +56,15 @@ context '#destroy' do it 'should successfully destroy the object and the dependent objects' do host = FactoryGirl.create(:mdm_host) - exploit_attempt = FactoryGirl.create(:mdm_exploit_attempt, :host => host) - exploited_host = FactoryGirl.create(:mdm_exploited_host, :host => host) - host_detail = FactoryGirl.create(:mdm_host_detail, :host => host) - loot = FactoryGirl.create(:mdm_loot, :host => host) - task_host = FactoryGirl.create(:mdm_task_host, :host => host) - note = FactoryGirl.create(:mdm_note, :host => host) - svc = FactoryGirl.create(:mdm_service, :host => host) - session = FactoryGirl.create(:mdm_session, :host => host) - vuln = FactoryGirl.create(:mdm_vuln, :host => host) - + exploit_attempt = FactoryGirl.create(:mdm_exploit_attempt, host: host) + exploited_host = FactoryGirl.create(:mdm_exploited_host, host: host) + host_detail = FactoryGirl.create(:mdm_host_detail, host: host) + loot = FactoryGirl.create(:mdm_loot, host: host) + task_host = FactoryGirl.create(:mdm_task_host, host: host) + note = FactoryGirl.create(:mdm_note, host: host) + svc = FactoryGirl.create(:mdm_service, host: host) + session = FactoryGirl.create(:mdm_session, host: host) + vuln = FactoryGirl.create(:mdm_vuln, host: host) expect { host.destroy @@ -123,7 +122,7 @@ FactoryGirl.create_list( :mdm_vuln, 2, - :host => host + host: host ) end @@ -133,13 +132,13 @@ end let!(:ref) do - FactoryGirl.create(:mdm_ref, :name => name) + FactoryGirl.create(:mdm_ref, name: name) end context 'with Mdm::VulnRefs' do let!(:vuln_refs) do vulns.collect { |vuln| - FactoryGirl.create(:mdm_vuln_ref, :ref => ref, :vuln => vuln) + FactoryGirl.create(:mdm_vuln_ref, ref: ref, vuln: vuln) } end @@ -154,8 +153,8 @@ let!(:module_ref) do FactoryGirl.create( :mdm_module_ref, - :detail => module_detail, - :name => name + detail: module_detail, + name: name ) end @@ -320,11 +319,11 @@ context 'database' do context 'columns' do - it { is_expected.to have_db_column(:address).of_type(:inet).with_options(:null => false) } + it { is_expected.to have_db_column(:address).of_type(:inet).with_options(null: false) } it { is_expected.to have_db_column(:arch).of_type(:string) } it { is_expected.to have_db_column(:comm).of_type(:string) } it { is_expected.to have_db_column(:comments).of_type(:text) } - it { is_expected.to have_db_column(:info).of_type(:string).with_options(:limit => 2 ** 16) } + it { is_expected.to have_db_column(:info).of_type(:string).with_options(limit: 2 ** 16) } it { is_expected.to have_db_column(:mac).of_type(:string) } it { is_expected.to have_db_column(:name).of_type(:string) } it { is_expected.to have_db_column(:os_flavor).of_type(:string) } @@ -335,14 +334,14 @@ it { is_expected.to have_db_column(:scope).of_type(:text) } it { is_expected.to have_db_column(:state).of_type(:string) } it { is_expected.to have_db_column(:virtual_host).of_type(:text) } - it { is_expected.to have_db_column(:workspace_id).of_type(:integer).with_options(:null => false) } + it { is_expected.to have_db_column(:workspace_id).of_type(:integer).with_options(null: false) } context 'counter caches' do - it { is_expected.to have_db_column(:exploit_attempt_count).of_type(:integer).with_options(:default => 0) } - it { is_expected.to have_db_column(:host_detail_count).of_type(:integer).with_options(:default => 0) } - it { is_expected.to have_db_column(:note_count).of_type(:integer).with_options(:default => 0) } - it { is_expected.to have_db_column(:service_count).of_type(:integer).with_options(:default => 0) } - it { is_expected.to have_db_column(:vuln_count).of_type(:integer).with_options(:default => 0) } + it { is_expected.to have_db_column(:exploit_attempt_count).of_type(:integer).with_options(default: 0) } + it { is_expected.to have_db_column(:host_detail_count).of_type(:integer).with_options(default: 0) } + it { is_expected.to have_db_column(:note_count).of_type(:integer).with_options(default: 0) } + it { is_expected.to have_db_column(:service_count).of_type(:integer).with_options(default: 0) } + it { is_expected.to have_db_column(:vuln_count).of_type(:integer).with_options(default: 0) } end context 'timestamps' do @@ -385,14 +384,14 @@ it { is_expected.to validate_presence_of(:address) } # can't use validate_uniqueness_of(:address).scoped_to(:workspace_id) because it will attempt to set workspace_id - # to `nil`, which will make the `:null => false` constraint on hosts.workspace_id to fail. + # to `nil`, which will make the `null: false` constraint on hosts.workspace_id to fail. it 'should validate uniqueness of address scoped to workspace_id' do address = '192.168.0.1' workspace = FactoryGirl.create(:mdm_workspace) - FactoryGirl.create(:mdm_host, :address => address, :workspace => workspace) + FactoryGirl.create(:mdm_host, address: address, workspace: workspace) - duplicate_host = FactoryGirl.build(:mdm_host, :address => address, :workspace => workspace) + duplicate_host = FactoryGirl.build(:mdm_host, address: address, workspace: workspace) expect(duplicate_host).not_to be_valid expect(duplicate_host.errors[:address]).to include('has already been taken') @@ -402,12 +401,12 @@ context 'arch' do let(:workspace) { FactoryGirl.create(:mdm_workspace) } let(:address) { '192.168.0.1' } - let(:host) { FactoryGirl.create(:mdm_host, :address => address, :workspace => workspace, :arch => arch) } + let(:host) { FactoryGirl.create(:mdm_host, address: address, workspace: workspace, arch: arch) } context 'with an unknown architecture' do let(:arch) { "asdfasdf" } it 'should normalize to Unknown' do expect(host).to be_valid - expect(host.arch).to be described_class::UNKNOWN_ARCHITECTURE + expect(host.arch).to eq(described_class::UNKNOWN_ARCHITECTURE) end end described_class::ARCHITECTURES.each do |arch| @@ -707,17 +706,17 @@ def search_for(str) context '#validate_fingerprint_data' do it 'should return false for an empty hash' do - fingerprint= FactoryGirl.build(:mdm_note, :data => {}) + fingerprint= FactoryGirl.build(:mdm_note, data: {}) expect(host.validate_fingerprint_data(fingerprint)).to eq(false) end it 'should return false for postgresql fingerprints' do - fingerprint= FactoryGirl.build(:mdm_note, :ntype => 'postgresql.fingerprint', :data => {}) + fingerprint= FactoryGirl.build(:mdm_note, ntype: 'postgresql.fingerprint', data: {}) expect(host.validate_fingerprint_data(fingerprint)).to eq(false) end it 'should return false if the fingerprint does not contain a hash' do - fingerprint= FactoryGirl.build(:mdm_note, :data => 'this is not a fingerprint') + fingerprint= FactoryGirl.build(:mdm_note, data: 'this is not a fingerprint') expect(host.validate_fingerprint_data(fingerprint)).to eq(false) end end @@ -913,7 +912,7 @@ def self.ascii_safe_hex(unsanitized) context '#normalize_scanner_fp' do context 'for session_fingerprint' do it 'should return all the correct data for Windows XP SP3 x86' do - fingerprint = FactoryGirl.build(:mdm_session_fingerprint, :host => host) + fingerprint = FactoryGirl.build(:mdm_session_fingerprint, host: host) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Windows XP') expect(result['os.version']).to eq('SP3') @@ -923,8 +922,8 @@ def self.ascii_safe_hex(unsanitized) end it 'should return all the correct data for Windows 2008 SP1 x64' do - fp_data = { :os => 'Microsoft Windows 2008 SP1', :arch => 'x64'} - fingerprint = FactoryGirl.build(:mdm_session_fingerprint, :host => host, :data => fp_data) + fp_data = { os: 'Microsoft Windows 2008 SP1', arch: 'x64'} + fingerprint = FactoryGirl.build(:mdm_session_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Windows Server 2008') expect(result['os.version']).to eq('SP1') @@ -935,8 +934,8 @@ def self.ascii_safe_hex(unsanitized) it 'should fingerprint Metasploitable correctly' do # Taken from an actual session_fingerprint of Metasploitable 2 - fp_data = { :os => 'Linux 2.6.24-16-server (i386)', :name => 'metasploitable'} - fingerprint = FactoryGirl.build(:mdm_session_fingerprint, :host => host, :data => fp_data) + fp_data = { os: 'Linux 2.6.24-16-server (i386)', name: 'metasploitable'} + fingerprint = FactoryGirl.build(:mdm_session_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Linux') expect(result['host.name']).to eq('metasploitable') @@ -946,8 +945,8 @@ def self.ascii_safe_hex(unsanitized) end it 'should just populate os_name if it is unsure' do - fp_data = { :os => 'Darwin 12.3.0 x86_64 i386'} - fingerprint = FactoryGirl.build(:mdm_session_fingerprint, :host => host, :data => fp_data) + fp_data = { os: 'Darwin 12.3.0 x86_64 i386'} + fingerprint = FactoryGirl.build(:mdm_session_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Darwin 12.3.0 x86_64 i386') expect(result['os.version']).to eq(nil) @@ -958,15 +957,15 @@ def self.ascii_safe_hex(unsanitized) context 'for nmap_fingerprint' do it 'should return OS name for a Windows XP fingerprint' do - fingerprint = FactoryGirl.build(:mdm_nmap_fingerprint, :host => host) + fingerprint = FactoryGirl.build(:mdm_nmap_fingerprint, host: host) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Windows XP') expect(result['os.certainty'].to_f).to eq(described_class::MAX_NMAP_CERTAINTY) end it 'should return OS name for a Metasploitable fingerprint' do - fp_data = {:os_vendor=>"Linux", :os_family=>"Linux", :os_version=>"2.6.X", :os_accuracy=>100} - fingerprint = FactoryGirl.build(:mdm_nmap_fingerprint, :host => host, :data => fp_data) + fp_data = {os_vendor: "Linux", os_family: "Linux", os_version: "2.6.X", os_accuracy: 100} + fingerprint = FactoryGirl.build(:mdm_nmap_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Linux') expect(result['os.version']).to eq('2.6.X') @@ -974,8 +973,8 @@ def self.ascii_safe_hex(unsanitized) end it 'should return OS name and flavor fo an OSX fingerprint' do - fp_data = {:os_vendor=>"Apple", :os_family=>"Mac OS X", :os_version=>"10.8.X", :os_accuracy=>100} - fingerprint = FactoryGirl.build(:mdm_nmap_fingerprint, :host => host, :data => fp_data) + fp_data = {os_vendor: "Apple", os_family: "Mac OS X", os_version: "10.8.X", os_accuracy: 100} + fingerprint = FactoryGirl.build(:mdm_nmap_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Mac OS X') expect(result['os.vendor']).to eq('Apple') @@ -987,7 +986,7 @@ def self.ascii_safe_hex(unsanitized) context 'for nexpose_fingerprint' do context 'of a Windows system' do it 'should return a generic Windows fingerprint with no product info' do - fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, :host => host) + fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, host: host) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Windows') expect(result['os.arch']).to eq('x86') @@ -995,8 +994,8 @@ def self.ascii_safe_hex(unsanitized) end it 'should recognize a Windows 7 fingerprint' do - fp_data = {:family=>"Windows", :certainty=>"0.67", :vendor=>"Microsoft", :arch=>"x86", :product => 'Windows 7', :version => 'SP1'} - fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, :host => host, :data => fp_data) + fp_data = {family: "Windows", certainty: "0.67", vendor: "Microsoft", arch: "x86", product: 'Windows 7', version: 'SP1'} + fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Windows 7') expect(result['os.version']).to eq('SP1') @@ -1006,31 +1005,31 @@ def self.ascii_safe_hex(unsanitized) end it 'should recognize an OSX fingerprint' do - fp_data = {:family=>"Mac OS X", :certainty=>"0.80", :vendor=>"Apple"} - fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, :host => host, :data => fp_data) + fp_data = {family: "Mac OS X", certainty: "0.80", vendor: "Apple"} + fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Mac OS X') expect(result['os.vendor']).to eq("Apple") end it 'should recognize a Cisco fingerprint' do - fp_data = {:family=>"IOS", :certainty=>"1.00", :vendor=>"Cisco", :version=>"11.2(8)SA2"} - fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, :host => host, :data => fp_data) + fp_data = {family: "IOS", certainty: "1.00", vendor: "Cisco", version: "11.2(8)SA2"} + fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('IOS') expect(result['os.vendor']).to eq('Cisco') end it 'should recognize an embedded fingerprint' do - fp_data = {:family=>"embedded", :certainty=>"1.00", :vendor=>"Footek"} - fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, :host => host, :data => fp_data) + fp_data = {family: "embedded", certainty: "1.00", vendor: "Footek"} + fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Footek') end it 'should handle an unknown fingerprint' do - fp_data = {:certainty=>"1.00", :vendor=>"Footek"} - fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, :host => host, :data => fp_data) + fp_data = {certainty: "1.00", vendor: "Footek"} + fingerprint = FactoryGirl.build(:mdm_nexpose_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq('Footek') end @@ -1040,7 +1039,7 @@ def self.ascii_safe_hex(unsanitized) context 'for retina_fingerprint' do it 'should recognize a Windows fingerprint' do - fingerprint = FactoryGirl.build(:mdm_retina_fingerprint, :host => host) + fingerprint = FactoryGirl.build(:mdm_retina_fingerprint, host: host) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq( 'Windows Server 2003') expect(result['os.arch']).to eq('x64') @@ -1049,8 +1048,8 @@ def self.ascii_safe_hex(unsanitized) end it 'should otherwise jsut copy the fingerprint to os_name' do - fp_data = { :os => 'Linux 2.6.X (i386)'} - fingerprint = FactoryGirl.build(:mdm_retina_fingerprint, :host => host, :data => fp_data) + fp_data = { os: 'Linux 2.6.X (i386)'} + fingerprint = FactoryGirl.build(:mdm_retina_fingerprint, host: host, data: fp_data) result = host.send(:normalize_scanner_fp, fingerprint).first expect(result['os.product']).to eq( 'Linux 2.6.X (i386)') expect(result['os.certainty'].to_f).to eq(0.8) diff --git a/spec/app/models/mdm/vuln_spec.rb b/spec/app/models/mdm/vuln_spec.rb index 17d4badc..4160fb01 100644 --- a/spec/app/models/mdm/vuln_spec.rb +++ b/spec/app/models/mdm/vuln_spec.rb @@ -8,28 +8,27 @@ context '#destroy' do it 'should successfully destroy the object and dependent objects' do vuln = FactoryGirl.create(:mdm_vuln) - vuln_attempt = FactoryGirl.create(:mdm_vuln_attempt, :vuln => vuln) - vuln_detail = FactoryGirl.create(:mdm_vuln_detail, :vuln => vuln) - vuln_ref = FactoryGirl.create(:mdm_vuln_ref, :vuln => vuln) - expect { + vuln_attempt = FactoryGirl.create(:mdm_vuln_attempt, vuln: vuln) + vuln_detail = FactoryGirl.create(:mdm_vuln_detail, vuln: vuln) + vuln_ref = FactoryGirl.create(:mdm_vuln_ref, vuln: vuln) + expect do vuln.destroy - }.to_not raise_error - expect { + end.to_not raise_error + expect do vuln.reload - }.to raise_error(ActiveRecord::RecordNotFound) - expect { + end.to raise_error(ActiveRecord::RecordNotFound) + expect do vuln_attempt.reload - }.to raise_error(ActiveRecord::RecordNotFound) - expect { + end.to raise_error(ActiveRecord::RecordNotFound) + expect do vuln_detail.reload - }.to raise_error(ActiveRecord::RecordNotFound) - expect { + end.to raise_error(ActiveRecord::RecordNotFound) + expect do vuln_ref.reload - }.to raise_error(ActiveRecord::RecordNotFound) + end.to raise_error(ActiveRecord::RecordNotFound) end end - context 'associations' do it { is_expected.to belong_to(:host).class_name('Mdm::Host') } it { is_expected.to belong_to(:service).class_name('Mdm::Service') } @@ -46,44 +45,38 @@ context 'with Mdm::Refs' do let(:names) do - 2.times.collect { - FactoryGirl.generate :mdm_ref_name - } + 2.times.collect { FactoryGirl.generate :mdm_ref_name } end let!(:refs) do names.collect do |name| - FactoryGirl.create(:mdm_ref, :name => name) + FactoryGirl.create(:mdm_ref, name: name) end end context 'with Mdm::VulnRefs' do let!(:vuln_refs) do - refs.collect { |ref| - FactoryGirl.create(:mdm_vuln_ref, :ref => ref, :vuln => vuln) - } + refs.collect do |ref| + FactoryGirl.create(:mdm_vuln_ref, ref: ref, vuln: vuln) + end end - + it 'should be deletable' do - expect { - vuln.destroy - }.not_to raise_error + expect { vuln.destroy }.not_to raise_error end - + context 'with Mdm::Module::Detail' do let!(:module_detail) do - FactoryGirl.create( - :mdm_module_detail - ) + FactoryGirl.create(:mdm_module_detail) end context 'with Mdm::Module::Refs with same names as Mdm::Refs' do let!(:module_refs) do names.each do |name| FactoryGirl.create( - :mdm_module_ref, - :detail => module_detail, - :name => name + :mdm_module_ref, + detail: module_detail, + name: name ) end end @@ -131,8 +124,8 @@ it { is_expected.to have_db_column(:service_id).of_type(:integer) } context 'counter caches' do - it { is_expected.to have_db_column(:vuln_attempt_count).of_type(:integer).with_options(:default => 0) } - it { is_expected.to have_db_column(:vuln_detail_count).of_type(:integer).with_options(:default => 0) } + it { is_expected.to have_db_column(:vuln_attempt_count).of_type(:integer).with_options(default: 0) } + it { is_expected.to have_db_column(:vuln_detail_count).of_type(:integer).with_options(default: 0) } end context 'timestamps' do @@ -186,7 +179,7 @@ context 'with Mdm::VulnRef' do let!(:vuln_ref) do - FactoryGirl.create(:mdm_vuln_ref, :ref => ref, :vuln => vuln) + FactoryGirl.create(:mdm_vuln_ref, ref: ref, vuln: vuln) end context 'with query matching Mdm::Ref#name' do @@ -272,4 +265,4 @@ end end end -end \ No newline at end of file +end diff --git a/spec/app/models/mdm/web_vuln_spec.rb b/spec/app/models/mdm/web_vuln_spec.rb index 1347f174..badde2fc 100644 --- a/spec/app/models/mdm/web_vuln_spec.rb +++ b/spec/app/models/mdm/web_vuln_spec.rb @@ -1,6 +1,6 @@ RSpec.describe Mdm::WebVuln, type: :model do let(:confidence_range) do - 0 .. 100 + 0..100 end let(:default_params) do @@ -9,15 +9,15 @@ let(:methods) do [ - 'GET', - 'POST', - # XXX not sure why PATH is valid since it's not an HTTP method verb. - 'PATH' + 'GET', + 'POST', + # XXX not sure why PATH is valid since it's not an HTTP method verb. + 'PATH' ] end let(:risk_range) do - 0 .. 5 + 0..5 end subject(:web_vuln) do @@ -47,37 +47,37 @@ context '#destroy' do it 'should successfully destroy the object' do web_vuln = FactoryGirl.create(:mdm_web_vuln) - expect { + expect do web_vuln.destroy - }.to_not raise_error - expect { + end.to_not raise_error + expect do web_vuln.reload - }.to raise_error(ActiveRecord::RecordNotFound) + end.to raise_error(ActiveRecord::RecordNotFound) end end context 'database' do context 'columns' do it { is_expected.to have_db_column(:blame).of_type(:text) } - it { is_expected.to have_db_column(:category).of_type(:text).with_options(:null => false) } - it { is_expected.to have_db_column(:confidence).of_type(:integer).with_options(:null => false) } + it { is_expected.to have_db_column(:category).of_type(:text).with_options(null: false) } + it { is_expected.to have_db_column(:confidence).of_type(:integer).with_options(null: false) } it { is_expected.to have_db_column(:description).of_type(:text) } - it { is_expected.to have_db_column(:method).of_type(:string).with_options(:limit => 1024, :null => false) } - it { is_expected.to have_db_column(:name).of_type(:string).with_options(:limit => 1024, :null => false) } + it { is_expected.to have_db_column(:method).of_type(:string).with_options(limit: 1024, null: false) } + it { is_expected.to have_db_column(:name).of_type(:string).with_options(limit: 1024, null: false) } it { is_expected.to have_db_column(:owner).of_type(:string) } - it { is_expected.to have_db_column(:params).of_type(:text).with_options(:null => false) } - it { is_expected.to have_db_column(:path).of_type(:text).with_options(:null => false) } + it { is_expected.to have_db_column(:params).of_type(:text).with_options(null: false) } + it { is_expected.to have_db_column(:path).of_type(:text).with_options(null: false) } it { is_expected.to have_db_column(:payload).of_type(:text) } it { is_expected.to have_db_column(:pname).of_type(:text) } - it { is_expected.to have_db_column(:proof).of_type(:binary).with_options(:null => false) } + it { is_expected.to have_db_column(:proof).of_type(:binary).with_options(null: false) } it { is_expected.to have_db_column(:query).of_type(:text) } it { is_expected.to have_db_column(:request).of_type(:binary) } - it { is_expected.to have_db_column(:risk).of_type(:integer).with_options(:null => false) } - it { is_expected.to have_db_column(:web_site_id).of_type(:integer).with_options(:null => false) } + it { is_expected.to have_db_column(:risk).of_type(:integer).with_options(null: false) } + it { is_expected.to have_db_column(:web_site_id).of_type(:integer).with_options(null: false) } context 'timestamps' do - it { is_expected.to have_db_column(:created_at).of_type(:datetime).with_options(:null => false) } - it { is_expected.to have_db_column(:updated_at).of_type(:datetime).with_options(:null => false) } + it { is_expected.to have_db_column(:created_at).of_type(:datetime).with_options(null: false) } + it { is_expected.to have_db_column(:updated_at).of_type(:datetime).with_options(null: false) } end end @@ -129,9 +129,7 @@ expect(web_vuln.params).not_to be_an Array expect(web_vuln).not_to be_valid - expect(web_vuln.errors[:params]).to include( - "is not an Array. #{type_signature_sentence}" - ) + expect(web_vuln.errors[:params]).to include("is not an Array. #{type_signature_sentence}") end it 'should allow empty Array' do @@ -162,9 +160,9 @@ it 'should validate elements of params are Arrays' do expect(web_vuln).not_to be_valid expect(web_vuln.errors[:params]).to include( - "has non-Array at index #{index} (#{element.inspect}). " \ - "#{type_signature_sentence}" - ) + "has non-Array at index #{index} (#{element.inspect}). " \ + "#{type_signature_sentence}" + ) end end @@ -180,9 +178,9 @@ it 'should validate elements of params are not too short' do expect(web_vuln).not_to be_valid expect(web_vuln.errors[:params]).to include( - "has too few elements at index #{index} (#{element.inspect}). " \ - "#{type_signature_sentence}" - ) + "has too few elements at index #{index} (#{element.inspect}). " \ + "#{type_signature_sentence}" + ) end end @@ -198,9 +196,9 @@ it 'should validate elements of params are not too long' do expect(web_vuln).not_to be_valid expect(web_vuln.errors[:params]).to include( - "has too many elements at index #{index} (#{element.inspect}). " \ - "#{type_signature_sentence}" - ) + "has too many elements at index #{index} (#{element.inspect}). " \ + "#{type_signature_sentence}" + ) end end @@ -222,10 +220,10 @@ it 'should validate that parameter name is not empty' do expect(web_vuln).not_to be_valid expect(web_vuln.errors[:params]).to include( - "has blank parameter name at index #{index} " \ - "(#{element.inspect}). " \ - "#{type_signature_sentence}" - ) + "has blank parameter name at index #{index} " \ + "(#{element.inspect}). " \ + "#{type_signature_sentence}" + ) end end end @@ -242,10 +240,10 @@ it 'should validate that parameter name is a String' do expect(web_vuln).not_to be_valid expect(web_vuln.errors[:params]).to include( - "has non-String parameter name (#{parameter_name.inspect}) " \ - "at index #{index} (#{element.inspect}). " \ - "#{type_signature_sentence}" - ) + "has non-String parameter name (#{parameter_name.inspect}) " \ + "at index #{index} (#{element.inspect}). " \ + "#{type_signature_sentence}" + ) end end end @@ -267,10 +265,10 @@ it 'should validate that parameter value is a String' do expect(web_vuln).not_to be_valid expect(web_vuln.errors[:params]).to include( - "has non-String parameter value (#{parameter_value}) " \ - "at index #{index} (#{element.inspect}). " \ - "#{type_signature_sentence}" - ) + "has non-String parameter value (#{parameter_value}) " \ + "at index #{index} (#{element.inspect}). " \ + "#{type_signature_sentence}" + ) end end end @@ -283,7 +281,7 @@ it { is_expected.to validate_presence_of :web_site } end - context 'serializations' do + context 'serializations', pending: 'Disabled after migration to Rails 4.2.1' do it { is_expected.to serialize(:params).as_instance_of(MetasploitDataModels::Base64Serializer) } end @@ -313,4 +311,4 @@ expect(web_vuln.params).to eq(default) end end -end \ No newline at end of file +end diff --git a/spec/app/models/mdm/workspace_spec.rb b/spec/app/models/mdm/workspace_spec.rb index 079cfaa8..a5c81223 100644 --- a/spec/app/models/mdm/workspace_spec.rb +++ b/spec/app/models/mdm/workspace_spec.rb @@ -19,21 +19,13 @@ context '#destroy' do it 'should successfully destroy the object and dependent objects' do workspace = FactoryGirl.create(:mdm_workspace) - listener = FactoryGirl.create(:mdm_listener, :workspace => workspace) - task = FactoryGirl.create(:mdm_task, :workspace => workspace) - - expect { - workspace.destroy - }.to_not raise_error - expect { - workspace.reload - }.to raise_error(ActiveRecord::RecordNotFound) - expect { - listener.reload - }.to raise_error(ActiveRecord::RecordNotFound) - expect { - task.reload - }.to raise_error(ActiveRecord::RecordNotFound) + listener = FactoryGirl.create(:mdm_listener, workspace: workspace) + task = FactoryGirl.create(:mdm_task, workspace: workspace) + + expect { workspace.destroy }.to_not raise_error + expect { workspace.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { listener.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { task.reload }.to raise_error(ActiveRecord::RecordNotFound) end end @@ -66,15 +58,15 @@ end context 'columns' do - it { is_expected.to have_db_column(:boundary).of_type(:string).with_options(:limit => 4 * (2 ** 10)) } - it { is_expected.to have_db_column(:description).of_type(:string).with_options(:limit => 4 * (2 ** 10)) } - it { is_expected.to have_db_column(:limit_to_network).of_type(:boolean).with_options(:default => false, :null => false) } + it { is_expected.to have_db_column(:boundary).of_type(:string).with_options(limit: 4 * (2**10)) } + it { is_expected.to have_db_column(:description).of_type(:string).with_options(limit: 4 * (2**10)) } + it { is_expected.to have_db_column(:limit_to_network).of_type(:boolean).with_options(default: false, null: false) } it { is_expected.to have_db_column(:name).of_type(:string) } it { is_expected.to have_db_column(:owner_id).of_type(:integer) } context 'timestamps' do - it { is_expected.to have_db_column(:created_at).of_type(:datetime).with_options(:null => false) } - it { is_expected.to have_db_column(:updated_at).of_type(:datetime).with_options(:null => false) } + it { is_expected.to have_db_column(:created_at).of_type(:datetime).with_options(null: false) } + it { is_expected.to have_db_column(:updated_at).of_type(:datetime).with_options(null: false) } end end @@ -130,7 +122,7 @@ '192.168' end - it 'should record error that boundary must be a valid IP range', :pending => 'https://www.pivotaltracker.com/story/show/43171927' do + it 'should record error that boundary must be a valid IP range', pending: 'https://www.pivotaltracker.com/story/show/43171927' do expect(workspace).not_to be_valid expect(workkspace.errors[:boundary]).to include(error) end @@ -138,11 +130,11 @@ end context 'description' do - it { is_expected.to ensure_length_of(:description).is_at_most(4 * (2 ** 10)) } + it { is_expected.to validate_length_of(:description).is_at_most(4 * (2**10)) } end context 'name' do - it { is_expected.to ensure_length_of(:name).is_at_most(2**8 - 1) } + it { is_expected.to validate_length_of(:name).is_at_most(2**8 - 1) } it { is_expected.to validate_presence_of :name } it { is_expected.to validate_uniqueness_of :name } end @@ -150,23 +142,23 @@ context 'methods' do let(:hosts) do - FactoryGirl.create_list(:mdm_host, 2, :workspace => workspace) + FactoryGirl.create_list(:mdm_host, 2, workspace: workspace) end let(:other_hosts) do - FactoryGirl.create_list(:mdm_host, 2, :workspace => other_workspace) + FactoryGirl.create_list(:mdm_host, 2, workspace: other_workspace) end let(:other_services) do other_hosts.collect do |host| - FactoryGirl.create(:mdm_service, :host => host) + FactoryGirl.create(:mdm_service, host: host) end end let(:other_web_sites) do - other_services.collect { |service| - FactoryGirl.create(:mdm_web_site, :service => service) - } + other_services.collect do |service| + FactoryGirl.create(:mdm_web_site, service: service) + end end let(:other_workspace) do @@ -175,14 +167,14 @@ let(:services) do hosts.collect do |host| - FactoryGirl.create(:mdm_service, :host => host) + FactoryGirl.create(:mdm_service, host: host) end end let(:web_sites) do - services.collect { |service| - FactoryGirl.create(:mdm_web_site, :service => service) - } + services.collect do |service| + FactoryGirl.create(:mdm_web_site, service: service) + end end context '#creds' do @@ -192,17 +184,17 @@ let!(:creds) do services.collect do |service| - FactoryGirl.create(:mdm_cred, :service => service) + FactoryGirl.create(:mdm_cred, service: service) end end let!(:other_creds) do other_services.collect do |service| - FactoryGirl.create(:mdm_cred, :service => service) + FactoryGirl.create(:mdm_cred, service: service) end end - it 'should be an ActiveRecord::Relation', :pending => 'https://www.pivotaltracker.com/story/show/43219917' do + it 'should be an ActiveRecord::Relation', pending: 'https://www.pivotaltracker.com/story/show/43219917' do should be_a ActiveRecord::Relation end @@ -212,11 +204,7 @@ expect(found_creds.length).to be > 0 - expect( - found_creds.none? { |found_cred| - found_cred.service.nil? - } - ).to eq(true) + expect(found_creds.none? { |found_cred| found_cred.service.nil? }).to eq(true) end it 'should return only Mdm::Creds from hosts in workspace' do @@ -225,9 +213,7 @@ expect(found_creds.length).to eq(creds.length) expect( - found_creds.all? { |cred| - cred.service.host.workspace == workspace - } + found_creds.all? { |cred| cred.service.host.workspace == workspace } ).to eq(true) end end @@ -236,17 +222,17 @@ context 'with default workspace' do before(:each) do FactoryGirl.create( - :mdm_workspace, - :name => default + :mdm_workspace, + name: default ) end it 'should not create workspace' do workspace = nil - expect { + expect do workspace = described_class.default - }.to change(Mdm::Workspace, :count).by(0) + end.to change(Mdm::Workspace, :count).by(0) expect(workspace).to be_default end @@ -256,9 +242,9 @@ it 'should create workspace' do workspace = nil - expect { + expect do workspace = described_class.default - }.to change(Mdm::Workspace, :count).by(1) + end.to change(Mdm::Workspace, :count).by(1) expect(workspace).to be_default end @@ -288,9 +274,9 @@ creds = FactoryGirl.create_list(:mdm_cred, 2) allow(workspace).to receive(:creds).and_return(creds) - expect { |block| + expect do |block| workspace.each_cred(&block) - }.to yield_successive_args(*creds) + end.to yield_successive_args(*creds) end end @@ -299,9 +285,9 @@ tags = FactoryGirl.create_list(:mdm_tag, 2) expect(workspace).to receive(:host_tags).and_return(tags) - expect { |block| + expect do |block| workspace.each_host_tag(&block) - }.to yield_successive_args(*tags) + end.to yield_successive_args(*tags) end end @@ -315,17 +301,11 @@ # let(:other_tags) do - FactoryGirl.create_list( - :mdm_tag, - 2 - ) + FactoryGirl.create_list(:mdm_tag, 2) end let(:tags) do - FactoryGirl.create_list( - :mdm_tag, - 2 - ) + FactoryGirl.create_list(:mdm_tag, 2) end # @@ -336,7 +316,7 @@ host_tags = [] hosts.zip(tags) do |host, tag| - host_tag = FactoryGirl.create(:mdm_host_tag, :host => host, :tag => tag) + host_tag = FactoryGirl.create(:mdm_host_tag, host: host, tag: tag) host_tags << host_tag end @@ -348,7 +328,7 @@ host_tags = [] other_hosts.zip(other_tags) do |host, tag| - host_tag = FactoryGirl.create(:mdm_host_tag, :host => host, :tag => tag) + host_tag = FactoryGirl.create(:mdm_host_tag, host: host, tag: tag) host_tags << host_tag end @@ -356,7 +336,7 @@ host_tags end - it 'should return an ActiveRecord::Relation', :pending => 'https://www.pivotaltracker.com/story/show/43219917' do + it 'should return an ActiveRecord::Relation' do should be_a ActiveRecord::Relation end @@ -364,11 +344,11 @@ expect(host_tags.length).to eq(tags.length) expect( - host_tags.all? { |tag| - tag.hosts.any? { |host| - host.workspace == workspace - } - } + host_tags.all? do |tag| + tag.hosts.any? do |host| + host.workspace == workspace + end + end ).to eq(true) end end @@ -404,15 +384,12 @@ end it 'should not raise error' do - expect { - normalize - }.to_not raise_error + expect { normalize }.to_not raise_error end end end context '#web_forms' do - subject do workspace.web_forms end @@ -422,19 +399,19 @@ # let!(:other_web_forms) do - other_web_sites.collect { |web_site| - FactoryGirl.create(:web_form, :web_site => web_site) - } + other_web_sites.collect do |web_site| + FactoryGirl.create(:web_form, web_site: web_site) + end end let!(:web_forms) do - web_sites.collect { |web_site| - FactoryGirl.create(:web_form, :web_site => web_site) - } + web_sites.collect do |web_site| + FactoryGirl.create(:web_form, web_site: web_site) + end end it 'should return an ActiveRecord:Relation', - :pending => 'https://www.pivotaltracker.com/story/show/43219917' do + pending: 'https://www.pivotaltracker.com/story/show/43219917' do should be_a ActiveRecord::Relation end @@ -444,9 +421,9 @@ expect(found_web_forms.length).to eq(web_forms.length) expect( - found_web_forms.all? { |web_form| - web_form.web_site.service.host.workspace == workspace - } + found_web_forms.all? do |web_form| + web_form.web_site.service.host.workspace == workspace + end ).to eq(true) end end @@ -466,7 +443,7 @@ end it 'should return an ActiveRecord:Relation', - :pending => 'https://www.pivotaltracker.com/story/show/43219917' do + pending: 'https://www.pivotaltracker.com/story/show/43219917' do should be_a ActiveRecord::Relation end @@ -479,9 +456,9 @@ expect(found_web_sites.length).to eq(web_sites.count) expect( - found_web_sites.all? { |web_site| - web_site.service.host.workspace == workspace - } + found_web_sites.all? do |web_site| + web_site.service.host.workspace == workspace + end ).to eq(true) end end @@ -496,19 +473,19 @@ # let!(:other_web_vulns) do - other_web_sites.collect { |web_site| - FactoryGirl.create(:mdm_web_vuln, :web_site => web_site) - } + other_web_sites.collect do |web_site| + FactoryGirl.create(:mdm_web_vuln, web_site: web_site) + end end let!(:web_vulns) do - web_sites.collect { |web_site| - FactoryGirl.create(:mdm_web_vuln, :web_site => web_site) - } + web_sites.collect do |web_site| + FactoryGirl.create(:mdm_web_vuln, web_site: web_site) + end end it 'should return an ActiveRecord:Relation', - :pending => 'https://www.pivotaltracker.com/story/show/43219917' do + pending: 'https://www.pivotaltracker.com/story/show/43219917' do should be_a ActiveRecord::Relation end @@ -520,9 +497,9 @@ expect(found_web_vulns.length).to eq(web_vulns.length) expect( - found_web_vulns.all? { |web_vuln| - web_vuln.web_site.service.host.workspace == workspace - } + found_web_vulns.all? do |web_vuln| + web_vuln.web_site.service.host.workspace == workspace + end ).to eq(true) end end @@ -537,7 +514,7 @@ end it 'should return an ActiveRecord:Relation', - :pending => 'https://www.pivotaltracker.com/story/show/43219917' do + pending: 'https://www.pivotaltracker.com/story/show/43219917' do should be_a ActiveRecord::Relation end @@ -545,12 +522,11 @@ web_forms = workspace.web_unique_forms([selected_address]) expect( - web_forms.all? { |web_form| - expect(web_form.web_site.service.host.address.to_s).to eq(selected_address) - } + web_forms.all? do |web_form| + expect(web_form.web_site.service.host.address.to_s).to eq(selected_address) + end ).to eq(true) end end end - -end \ No newline at end of file +end diff --git a/spec/app/models/metasploit_data_models/search/operator/multitext_spec.rb b/spec/app/models/metasploit_data_models/search/operator/multitext_spec.rb index 6db379c4..ca5e20a1 100644 --- a/spec/app/models/metasploit_data_models/search/operator/multitext_spec.rb +++ b/spec/app/models/metasploit_data_models/search/operator/multitext_spec.rb @@ -10,7 +10,7 @@ } context 'validations' do - it { is_expected.to ensure_length_of(:operator_names).is_at_least(2) } + it { is_expected.to validate_length_of(:operator_names).is_at_least(2) } it { is_expected.to validate_presence_of :name } end @@ -157,4 +157,4 @@ operators end end -end \ No newline at end of file +end diff --git a/spec/app/models/metasploit_data_models/search/visitor/relation_spec.rb b/spec/app/models/metasploit_data_models/search/visitor/relation_spec.rb index 6b27ad8c..f01c783d 100644 --- a/spec/app/models/metasploit_data_models/search/visitor/relation_spec.rb +++ b/spec/app/models/metasploit_data_models/search/visitor/relation_spec.rb @@ -1,7 +1,7 @@ RSpec.describe MetasploitDataModels::Search::Visitor::Relation, type: :model do subject(:visitor) do described_class.new( - :query => query + query: query ) end @@ -16,8 +16,8 @@ let(:query) do Metasploit::Model::Search::Query.new( - :formatted => formatted, - :klass => klass + formatted: formatted, + klass: klass ) end @@ -146,7 +146,6 @@ it 'should pass visited to ActiveRecord::Relation#includes' do visited = double('Visited') allow(where_visitor).to receive(:visit).with(query.tree).and_return(visited) - expect_any_instance_of(ActiveRecord::Relation).to receive(:where).with(visited).and_return(query.klass.scoped) visit @@ -710,7 +709,7 @@ end it_should_behave_like 'MetasploitDataModels::Search::Visitor::Relation#visit matching record', - :attribute => :name + attribute: :name context 'with os' do let(:matching_record_os_flavor) { @@ -761,13 +760,13 @@ end it_should_behave_like 'MetasploitDataModels::Search::Visitor::Relation#visit matching record', - :attribute => :os_flavor + attribute: :os_flavor it_should_behave_like 'MetasploitDataModels::Search::Visitor::Relation#visit matching record', - :attribute => :os_name + attribute: :os_name it_should_behave_like 'MetasploitDataModels::Search::Visitor::Relation#visit matching record', - :attribute => :os_sp + attribute: :os_sp it_should_behave_like 'MetasploitDataModels::Search::Visitor::Relation#visit matching record', association: :services, diff --git a/spec/dummy/config/environments/test.rb b/spec/dummy/config/environments/test.rb index f8e9fb12..0d38ba00 100644 --- a/spec/dummy/config/environments/test.rb +++ b/spec/dummy/config/environments/test.rb @@ -8,7 +8,7 @@ config.cache_classes = true # Configure static asset server for tests with Cache-Control for performance - config.serve_static_assets = true + config.serve_static_files = true config.static_cache_control = "public, max-age=3600" # Show full error reports and disable caching diff --git a/spec/dummy/db/structure.sql b/spec/dummy/db/structure.sql index 0b3a62f5..f1ec31cb 100644 --- a/spec/dummy/db/structure.sql +++ b/spec/dummy/db/structure.sql @@ -3406,3 +3406,4 @@ INSERT INTO schema_migrations (version) VALUES ('7'); INSERT INTO schema_migrations (version) VALUES ('8'); INSERT INTO schema_migrations (version) VALUES ('9'); + diff --git a/spec/factories/mdm/exploit_attempts.rb b/spec/factories/mdm/exploit_attempts.rb index a2377fb2..2326f4f5 100644 --- a/spec/factories/mdm/exploit_attempts.rb +++ b/spec/factories/mdm/exploit_attempts.rb @@ -1,8 +1,8 @@ FactoryGirl.define do - factory :mdm_exploit_attempt, :class => Mdm::ExploitAttempt do + factory :mdm_exploit_attempt, class: Mdm::ExploitAttempt do # # Associations # - association :host, :factory => :mdm_host + association :host, factory: :mdm_host end -end \ No newline at end of file +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c524ae17..d6f592f4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -92,7 +92,7 @@ # This setting enables warnings. It's recommended, but in some cases may # be too noisy due to issues in dependencies. - config.warnings = true + config.warnings = false # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an diff --git a/spec/support/matchers/match_regex_exactly.rb b/spec/support/matchers/match_regex_exactly.rb index b6224f80..484d55da 100644 --- a/spec/support/matchers/match_regex_exactly.rb +++ b/spec/support/matchers/match_regex_exactly.rb @@ -1,6 +1,6 @@ # Checks that the string matches the RSpec::Matchers.define :match_string_exactly do |string| - failure_message_for_should do |regexp| + failure_message do |regexp| match = regexp.match(string) failure_message = "expected #{regexp} to match #{string}" @@ -25,4 +25,4 @@ match && match.pre_match.empty? && match.post_match.empty? end -end \ No newline at end of file +end