Merge branch 'master' into feature/addp-modules
commit
0bf5f63d67
2
Gemfile
2
Gemfile
|
@ -5,7 +5,7 @@ gem 'activesupport', '>= 3.0.0'
|
|||
# Needed for Msf::DbManager
|
||||
gem 'activerecord'
|
||||
# Database models shared between framework and Pro.
|
||||
gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git'
|
||||
gem 'metasploit_data_models', :git => 'git://github.com/rapid7/metasploit_data_models.git', :tag => '0.3.0'
|
||||
# Needed for module caching in Mdm::ModuleDetails
|
||||
gem 'pg', '>= 0.11'
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
GIT
|
||||
remote: git://github.com/rapid7/metasploit_data_models.git
|
||||
revision: dd6c3a31c5ad8b55f4913b5ba20307178ba9c7bf
|
||||
revision: 73f26789500f278dd6fd555e839d09a3b81a05f4
|
||||
tag: 0.3.0
|
||||
specs:
|
||||
metasploit_data_models (0.0.2)
|
||||
metasploit_data_models (0.3.0)
|
||||
activerecord
|
||||
activesupport
|
||||
pg
|
||||
|
@ -27,7 +28,7 @@ GEM
|
|||
coderay (1.0.8)
|
||||
diff-lcs (1.1.3)
|
||||
i18n (0.6.1)
|
||||
method_source (0.8)
|
||||
method_source (0.8.1)
|
||||
multi_json (1.3.6)
|
||||
pg (0.14.1)
|
||||
pry (0.9.10)
|
||||
|
|
|
@ -10,13 +10,9 @@ require 'rubygems'
|
|||
|
||||
version = ">= 0"
|
||||
|
||||
if ARGV.first
|
||||
str = ARGV.first
|
||||
str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
|
||||
if str =~ /\A_(.*)_\z/
|
||||
version = $1
|
||||
ARGV.shift
|
||||
end
|
||||
if ARGV.first =~ /^_(.*)_$/ and Gem::Version.correct? $1 then
|
||||
version = $1
|
||||
ARGV.shift
|
||||
end
|
||||
|
||||
gem 'metasploit_data_models', version
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
.rvmrc
|
||||
.DS_Store
|
||||
*.gem
|
||||
.bundle
|
||||
Gemfile.lock
|
||||
pkg/*
|
|
@ -1,4 +0,0 @@
|
|||
source "http://rubygems.org"
|
||||
|
||||
# Specify your gem's dependencies in metasploit_data_models.gemspec
|
||||
gemspec
|
|
@ -1,75 +0,0 @@
|
|||
#MetasploitDataModels
|
||||
|
||||
The database layer for Metasploit
|
||||
|
||||
|
||||
## Purpose
|
||||
__MetasploitDataModels__ exists to do several key things:
|
||||
|
||||
1. Allow code sharing between Metasploit Framework (MSF) and the commercial versions of Metasploit (Community, Express, Pro -- usually referred to collectively as "Pro")
|
||||
|
||||
2. Give developers a lightweight entry point to MSF's backend for use in developing tools that gather data intended for later use with Metasploit (e.g. specialized scanners).
|
||||
|
||||
3. Make it easy to keep commercial stuff private while increasing the functionality of the open-source tools we provide to the community.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Rails
|
||||
|
||||
In a Rails application we simply include the ActiveRecord mixins directly, usually inside models with similar names.
|
||||
|
||||
### MSF
|
||||
When MetasploitDataModels is included by MSF, the gem dynamically creates
|
||||
ActiveRecord model classes.
|
||||
|
||||
Both of these behaviors are based on the assumption that the files in
|
||||
__lib/metasploit_data_models/active_record_models__, though implemented here as
|
||||
mixins, actually represent the basic ActiveRecord model structure that both Metasploit Framework and Metasploit Pro use.
|
||||
|
||||
### Elsewhere
|
||||
|
||||
__NOTE: This isn't in RubyGems yet. Using a Gemfile entry pointing to this repo (i.e., using [Bundler](http://gembundler.com)) is the suggested option for now.__
|
||||
|
||||
|
||||
Usage outside of Rapid7 is still alpha, and we're not making many promises. That being said, usage is easy:
|
||||
|
||||
```ruby
|
||||
connection_info = YAML.load_file("path/to/rails-style/db_config_file")
|
||||
ActiveRecord::Base.establish_connection(connection_info['development'])
|
||||
include MetasploitDataModels
|
||||
MetasploitDataModels.create_and_load_ar_classes
|
||||
```
|
||||
|
||||
Basically you need to do the following things:
|
||||
|
||||
1. Establish an ActiveRecord connection. A Rails __config/database.yml__ is ideal for this.
|
||||
2. Include the MetasploitDataModels module.
|
||||
3. Call the class method that builds the AR models into the Mdm namespace( __MetasploitDataModels.create_and_load_ar_classes__ ).
|
||||
|
||||
|
||||
## Developer Info
|
||||
|
||||
### Console
|
||||
The gem includes a console based on [Pry](https://github.com/pry/pry/)
|
||||
|
||||
Give it a path to a working MSF database.yml file for full
|
||||
ActiveRecord-based access to your data.
|
||||
|
||||
__Note:__ "development" mode is hardcoded into the console currently.
|
||||
|
||||
### ActiveRecord::ConnectionError issues
|
||||
Because the gem is defining mixins, there can be no knowledge of the
|
||||
specifics of any "current" ActiveRecord connection. But if ActiveRecord
|
||||
encounters something in a child class that would require knowledge of
|
||||
the connection adapter (e.g. the use of an RDBMS-specific function in
|
||||
a named scope's "WHERE" clause), it will check to see if the adapter
|
||||
supports it and then throw an exception when the connection object
|
||||
(which provides the adapter) is nil.
|
||||
|
||||
This means that, for all but the most trivial cases, you need to use Arel
|
||||
versions of queries instead of ones utilizing straight SQL.
|
||||
|
||||
You'll encounter this sometimes if you do dev work on this gem. A good
|
||||
rule of thumb: anything that goes into the class_eval block must be able
|
||||
to work without knowledge of the AR connection adapter type.
|
|
@ -1 +0,0 @@
|
|||
require "bundler/gem_tasks"
|
|
@ -1,63 +0,0 @@
|
|||
require "active_record"
|
||||
require "active_support"
|
||||
require "active_support/all"
|
||||
require "shellwords"
|
||||
|
||||
require "metasploit_data_models/version"
|
||||
require "metasploit_data_models/serialized_prefs"
|
||||
require "metasploit_data_models/base64_serializer"
|
||||
|
||||
require "metasploit_data_models/validators/ip_format_validator"
|
||||
require "metasploit_data_models/validators/password_is_strong_validator"
|
||||
|
||||
|
||||
# Declare the (blessedly short) common namespace for the ActiveRecord classes
|
||||
module Mdm; end
|
||||
|
||||
module MetasploitDataModels
|
||||
module ActiveRecordModels; end
|
||||
|
||||
# Dynamically create AR classes if being included from Msf::DBManager
|
||||
# otherwise, just make the modules available for arbitrary inclusion.
|
||||
def self.included(base)
|
||||
ar_mixins.each{|file| require file}
|
||||
create_and_load_ar_classes if base.to_s == 'Msf::DBManager'
|
||||
end
|
||||
|
||||
# The code in each of these represents the basic structure of a correspondingly named
|
||||
# ActiveRecord model class. Those classes are explicitly created in our Rails app
|
||||
# for the commercial versions, and the functionality from the mixins is included
|
||||
# into model classes directly.
|
||||
#
|
||||
# When not explicitly overloading the classes in your own files use MetasploitDataModels#create_and_load_ar_classes
|
||||
# to dynamically generate ActiveRecord classes in the Mdm namespace.
|
||||
def self.ar_mixins
|
||||
models_dir = File.expand_path(File.dirname(__FILE__)) + "/metasploit_data_models/active_record_models"
|
||||
Dir.glob("#{models_dir}/*.rb")
|
||||
end
|
||||
|
||||
# Dynamically create ActiveRecord descendant classes in the Mdm namespace
|
||||
def self.create_and_load_ar_classes
|
||||
ar_module_names.each do |cname|
|
||||
class_str =<<-RUBY
|
||||
class Mdm::#{cname} < ActiveRecord::Base
|
||||
include MetasploitDataModels::ActiveRecordModels::#{cname}
|
||||
end
|
||||
RUBY
|
||||
eval class_str, binding, __FILE__, __LINE__ # *slightly* more obvious stack trace
|
||||
end
|
||||
end
|
||||
|
||||
# Derive "constant" strings from the names of the files in
|
||||
# lib/metasploit_data_models/active_record_models
|
||||
def self.ar_module_names
|
||||
ar_mixins.inject([]) do |array, path|
|
||||
filename = File.basename(path).split(".").first
|
||||
c_name = filename.classify
|
||||
c_name << "s" if filename =~ /^[\w]+s$/ # classify can't do plurals
|
||||
array << c_name
|
||||
array
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,22 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ApiKey
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
|
||||
validate do |key|
|
||||
lic = License.get
|
||||
|
||||
if lic and not lic.supports_api?
|
||||
key.errors[:unsupported_product] = " - this product does not support API access"
|
||||
end
|
||||
|
||||
if key.token.to_s.empty?
|
||||
key.errors[:blank_token] = " - the specified authentication token is empty"
|
||||
end
|
||||
|
||||
if key.token.to_s.length < 8
|
||||
key.errors[:token_too_short] = " - the specified authentication token must be at least 8 characters long"
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Client
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :host, :class_name => "Mdm::Host"
|
||||
belongs_to :campaign, :class_name => "Campaign"
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,78 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Cred
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
belongs_to :service, :class_name => "Mdm::Service"
|
||||
|
||||
unless defined? PTYPES
|
||||
const_def =<<-CONST_DEF
|
||||
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"
|
||||
}
|
||||
CONST_DEF
|
||||
eval(const_def)
|
||||
end
|
||||
|
||||
eval("KEY_ID_REGEX = /([0-9a-fA-F:]{47})/") unless defined?(KEY_ID_REGEX) # Could be more strict
|
||||
|
||||
def ptype_human
|
||||
humanized = PTYPES.select do |k, v|
|
||||
v == ptype
|
||||
end.keys[0]
|
||||
|
||||
humanized ? humanized : ptype
|
||||
end
|
||||
|
||||
# Returns its workspace
|
||||
def workspace
|
||||
self.service.host.workspace
|
||||
end
|
||||
|
||||
# Returns its key id. If this is not an ssh-type key, returns nil.
|
||||
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.
|
||||
end
|
||||
|
||||
# Returns all private keys with matching key ids, including itself
|
||||
# If this is not an ssh-type key, always returns an empty array.
|
||||
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}
|
||||
end
|
||||
|
||||
# Returns all public keys with matching key ids, including itself
|
||||
# If this is not an ssh-type key, always returns an empty array.
|
||||
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}
|
||||
end
|
||||
|
||||
# Returns all keys with matching key ids, including itself
|
||||
# If this is not an ssh-type key, always returns an empty array.
|
||||
def ssh_keys
|
||||
(self.ssh_private_keys | self.ssh_public_keys)
|
||||
end
|
||||
|
||||
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
|
||||
false
|
||||
end
|
||||
matches.include?(self) and matches.include?(other_cred)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::CredFile
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Event
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
belongs_to :host
|
||||
|
||||
serialize :info, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
scope :flagged, where(:critical => true, :seen => false)
|
||||
scope :module_run, where(:name => 'module_run')
|
||||
|
||||
validates_presence_of :name
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ExploitAttempt
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :host, :class_name => "Mdm::Host", :counter_cache => :exploit_attempt_count
|
||||
validates :host_id, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ExploitedHost
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
belongs_to :host, :class_name => "Mdm::Host"
|
||||
belongs_to :service, :class_name => "Mdm::Service"
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
}
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::HostDetail
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :host, :class_name => "Mdm::Host", :counter_cache => :host_detail_count
|
||||
validates :host_id, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::HostTag
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
base.table_name = "hosts_tags"
|
||||
belongs_to :host, :class_name => "Mdm::Host"
|
||||
belongs_to :tag, :class_name => "Mdm::Tag"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ImportedCred
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Listener
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
belongs_to :task, :class_name => "Mdm::Task"
|
||||
|
||||
serialize :options, ::MetasploitDataModels::Base64Serializer.new
|
||||
validates :address, :presence => true, :ip_format => true
|
||||
validates :port, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Loot
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
belongs_to :host, :class_name => "Mdm::Host"
|
||||
belongs_to :service, :class_name => "Mdm::Service"
|
||||
|
||||
serialize :data, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
scope :search, lambda { |*args|
|
||||
where(["loots.ltype ILIKE ? OR " +
|
||||
"loots.name ILIKE ? OR " +
|
||||
"loots.info ILIKE ? OR " +
|
||||
"loots.data ILIKE ?",
|
||||
"%#{args[0]}%", "%#{args[0]}%", "%#{args[0]}%", "%#{args[0]}%"
|
||||
])
|
||||
}
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.loot_delete_file(self[:id])
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Macro
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
extend MetasploitDataModels::SerializedPrefs
|
||||
|
||||
serialize :actions, ::MetasploitDataModels::Base64Serializer.new
|
||||
serialize :prefs, ::MetasploitDataModels::Base64Serializer.new
|
||||
serialized_prefs_attr_accessor :max_time
|
||||
|
||||
validates :name, :presence => true, :format => /^[^'|"]+$/
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModRef
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModuleAction
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
base.table_name = "module_actions"
|
||||
belongs_to :module_detail
|
||||
validate :name, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModuleArch
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
base.table_name = "module_archs"
|
||||
belongs_to :module_detail
|
||||
validate :name, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModuleAuthor
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
base.table_name = "module_authors"
|
||||
belongs_to :module_detail
|
||||
validate :name, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,67 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModuleDetail
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
base.table_name = "module_details"
|
||||
|
||||
has_many :authors, :class_name => "Mdm::ModuleAuthor", :dependent => :destroy, :source => :module_author
|
||||
has_many :mixins, :class_name => "Mdm::ModuleMixin", :dependent => :destroy, :source => :module_mixin
|
||||
has_many :targets, :class_name => "Mdm::ModuleTarget", :dependent => :destroy, :source => :module_target
|
||||
has_many :actions, :class_name => "Mdm::ModuleAction", :dependent => :destroy, :source => :module_action
|
||||
has_many :refs, :class_name => "Mdm::ModuleRef", :dependent => :destroy, :source => :module_ref
|
||||
has_many :archs, :class_name => "Mdm::ModuleArch", :dependent => :destroy, :source => :module_arch
|
||||
has_many :platforms, :class_name => "Mdm::ModulePlatform", :dependent => :destroy, :source => :module_platform
|
||||
|
||||
validate :refname, :presence => true
|
||||
|
||||
validates_associated :authors
|
||||
validates_associated :mixins
|
||||
validates_associated :targets
|
||||
validates_associated :actions
|
||||
validates_associated :archs
|
||||
validates_associated :platforms
|
||||
validates_associated :refs
|
||||
|
||||
def add_author(name, email=nil)
|
||||
if email
|
||||
r = self.authors.build(:name => name, :email => email).save
|
||||
else
|
||||
self.authors.build(:name => name).save
|
||||
end
|
||||
end
|
||||
|
||||
def add_mixin(name)
|
||||
self.mixins.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_target(idx, name)
|
||||
self.targets.build(:index => idx, :name => name).save
|
||||
end
|
||||
|
||||
def add_action(name)
|
||||
self.actions.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_ref(name)
|
||||
self.refs.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_arch(name)
|
||||
self.archs.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_platform(name)
|
||||
self.platforms.build(:name => name).save
|
||||
end
|
||||
|
||||
def before_destroy
|
||||
Mdm::ModuleAuthor.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleMixin.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleTarget.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleAction.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleRef.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleArch.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModulePlatform.delete_all('module_detail_id = ?', self.id)
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModuleMixin
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
base.table_name = "module_mixins"
|
||||
belongs_to :module_detail
|
||||
validate :name, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModulePlatform
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
base.table_name = "module_platforms"
|
||||
belongs_to :module_detail
|
||||
validate :name, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModuleRef
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
base.table_name = "module_refs"
|
||||
belongs_to :module_detail
|
||||
validate :name, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ModuleTarget
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
base.table_name = "module_targets"
|
||||
belongs_to :module_detail
|
||||
validate :name, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::NexposeConsole
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
serialize :cached_sites, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
validates :name, :presence => true
|
||||
validates :address, :presence => true
|
||||
validates :username, :presence => true
|
||||
validates :password, :presence => true
|
||||
validates :port, :inclusion => {:in => 1..65535}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Note
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
notes = base.arel_table
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
belongs_to :host, :class_name => "Mdm::Host", :counter_cache => :note_count
|
||||
belongs_to :service, :class_name => "Mdm::Service"
|
||||
serialize :data, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
scope :flagged, where('critical = true AND seen = false')
|
||||
scope :visible, where(notes[:ntype].not_in(['web.form', 'web.url', 'web.vuln']))
|
||||
scope :search, lambda { |*args|
|
||||
where(["(data NOT ILIKE 'BAh7%' AND data LIKE ?)" +
|
||||
"OR (data ILIKE 'BAh7%' AND decode(data, 'base64') LIKE ?)" +
|
||||
"OR ntype ILIKE ?",
|
||||
"%#{args[0]}%", "%#{args[0]}%", "%#{args[0]}%"
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
after_save :normalize
|
||||
|
||||
private
|
||||
|
||||
def normalize
|
||||
if data_changed? and ntype =~ /fingerprint/
|
||||
host.normalize_os
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Profile
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
serialize :settings, ::MetasploitDataModels::Base64Serializer.new
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Ref
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
has_many :vulns, :through => :vulns_refs, :class_name => "Mdm::Vuln"
|
||||
has_many :vulns_refs, :class_name => "Mdm::VulnRef"
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Report
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
serialize :options, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
validates_format_of :name, :with => /^[A-Za-z0-9\x20\x2e\x2d\x5f\x5c]+$/, :message => "name must consist of A-Z, 0-9, space, dot, underscore, or dash", :allow_blank => true
|
||||
|
||||
serialize :options, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
scope :flagged, where('reports.downloaded_at is NULL')
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.report_delete_file(self[:id])
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::ReportTemplate
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.report_template_delete_file(self[:id])
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Route
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
belongs_to :session, :class_name => "Mdm::Session"
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,42 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Service
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
eval("STATES = ['open', 'closed', 'filtered', 'unknown']") unless defined? STATES
|
||||
has_many :vulns, :dependent => :destroy, :class_name => "Mdm::Vuln"
|
||||
has_many :notes, :dependent => :destroy, :class_name => "Mdm::Note"
|
||||
has_many :creds, :dependent => :destroy, :class_name => "Mdm::Cred"
|
||||
has_many :exploited_hosts, :dependent => :destroy, :class_name => "Mdm::ExploitedHost"
|
||||
has_many :web_sites, :dependent => :destroy, :class_name => "Mdm::WebSite"
|
||||
has_many :web_pages, :through => :web_sites, :class_name => "Mdm::WebPage"
|
||||
has_many :web_forms, :through => :web_sites, :class_name => "Mdm::WebForm"
|
||||
has_many :web_vulns, :through => :web_sites, :class_name => "Mdm::WebVuln"
|
||||
|
||||
belongs_to :host, :class_name => "Mdm::Host", :counter_cache => :service_count
|
||||
|
||||
has_many :web_pages, :through => :web_sites
|
||||
has_many :web_forms, :through => :web_sites
|
||||
has_many :web_vulns, :through => :web_sites
|
||||
|
||||
scope :inactive, where("services.state != 'open'")
|
||||
scope :with_state, lambda { |a_state| where("services.state = ?", a_state)}
|
||||
scope :search, lambda { |*args|
|
||||
where([
|
||||
"services.name ILIKE ? OR " +
|
||||
"services.info ILIKE ? OR " +
|
||||
"services.proto ILIKE ? OR " +
|
||||
"services.port = ? ",
|
||||
"%#{args[0]}%", "%#{args[0]}%", "%#{args[0]}%", (args[0].to_i > 0) ? args[0].to_i : 99999
|
||||
])
|
||||
}
|
||||
|
||||
after_save :normalize_host_os
|
||||
|
||||
def normalize_host_os
|
||||
if info_changed?
|
||||
host.normalize_os
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Session
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :host, :class_name => "Mdm::Host"
|
||||
|
||||
has_one :workspace, :through => :host, :class_name => "Mdm::Workspace"
|
||||
|
||||
has_many :events, :class_name => "Mdm::SessionEvent", :order => "created_at", :dependent => :delete_all
|
||||
has_many :routes, :class_name => "Mdm::Route", :dependent => :delete_all
|
||||
|
||||
scope :alive, where("closed_at IS NULL")
|
||||
scope :dead, where("closed_at IS NOT NULL")
|
||||
scope :upgradeable, where("closed_at IS NULL AND stype = 'shell' and platform ILIKE '%win%'")
|
||||
|
||||
serialize :datastore, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
before_destroy :stop
|
||||
|
||||
def upgradeable?
|
||||
(self.platform =~ /win/ and self.stype == 'shell')
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def stop
|
||||
c = Pro::Client.get rescue nil
|
||||
c.session_stop(self.local_id) rescue nil # ignore exceptions (XXX - ideally, stopped an already-stopped session wouldn't throw XMLRPCException)
|
||||
end
|
||||
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::SessionEvent
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
belongs_to :session, :class_name => "Mdm::Session"
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,27 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Tag
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
has_many :hosts_tags, :class_name => "Mdm::HostTag"
|
||||
has_many :hosts, :through => :hosts_tags, :class_name => "Mdm::Host"
|
||||
|
||||
belongs_to :user, :class_name => "Mdm::User"
|
||||
|
||||
validates :name, :presence => true, :format => {
|
||||
:with => /^[A-Za-z0-9\x2e\x2d_]+$/, :message => "must be alphanumeric, dots, dashes, or underscores"
|
||||
}
|
||||
validates :desc, :length => {:maximum => 8191, :message => "desc must be less than 8k."}
|
||||
|
||||
before_destroy :cleanup_hosts
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
def cleanup_hosts
|
||||
# Clean up association table records
|
||||
Mdm::HostTag.delete_all("tag_id = #{self.id}")
|
||||
end
|
||||
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Task
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
|
||||
serialize :options, ::MetasploitDataModels::Base64Serializer.new
|
||||
serialize :result, ::MetasploitDataModels::Base64Serializer.new
|
||||
serialize :settings, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
scope :running, order( "created_at DESC" ).where("completed_at IS NULL")
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.task_delete_log(self[:id]) if c
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::User
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
extend MetasploitDataModels::SerializedPrefs
|
||||
serialize :prefs, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
has_and_belongs_to_many :workspaces, :join_table => "workspace_members", :uniq => true, :class_name => "Mdm::Workspace"
|
||||
has_many :owned_workspaces, :foreign_key => "owner_id", :class_name => "Mdm::Workspace"
|
||||
has_many :tags, :class_name => "Mdm::Tag"
|
||||
|
||||
validates :password, :password_is_strong => true
|
||||
validates :password_confirmation, :password_is_strong => true
|
||||
|
||||
|
||||
serialized_prefs_attr_accessor :nexpose_host, :nexpose_port, :nexpose_user, :nexpose_pass, :nexpose_creds_type, :nexpose_creds_user, :nexpose_creds_pass
|
||||
serialized_prefs_attr_accessor :http_proxy_host, :http_proxy_port, :http_proxy_user, :http_proxy_pass
|
||||
serialized_prefs_attr_accessor :time_zone, :session_key
|
||||
serialized_prefs_attr_accessor :last_login_address # specifically NOT last_login_ip to prevent confusion with AuthLogic magic columns (which dont work for serialized fields)
|
||||
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::Vuln
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :host, :class_name => "Mdm::Host", :counter_cache => :vuln_count
|
||||
belongs_to :service, :class_name => "Mdm::Service", :foreign_key => :service_id
|
||||
has_many :vuln_details, :dependent => :destroy, :class_name => "Mdm::VulnDetail"
|
||||
has_many :vuln_attempts, :dependent => :destroy, :class_name => "Mdm::VulnAttempt"
|
||||
has_many :vulns_refs, :class_name => "Mdm::VulnRef"
|
||||
has_many :refs, :through => :vulns_refs, :class_name => "Mdm::Ref"
|
||||
|
||||
|
||||
validates :name, :presence => true
|
||||
validates_associated :refs
|
||||
|
||||
after_update :save_refs
|
||||
|
||||
scope :search, lambda { |*args|
|
||||
where(["(vulns.name ILIKE ? or vulns.info ILIKE ? or refs.name ILIKE ?)",
|
||||
"%#{args[0]}%", "%#{args[0]}%", "%#{args[0]}%"
|
||||
]).
|
||||
joins("LEFT OUTER JOIN vulns_refs ON vulns_refs.vuln_id=vulns.id LEFT OUTER JOIN refs ON refs.id=vulns_refs.ref_id")
|
||||
}
|
||||
|
||||
private
|
||||
|
||||
def save_refs
|
||||
refs.each { |ref| ref.save(:validate => false) }
|
||||
end
|
||||
|
||||
def before_destroy
|
||||
Mdm::VulnRef.delete_all('vuln_id = ?', self.id)
|
||||
Mdm::VulnDetail.delete_all('vuln_id = ?', self.id)
|
||||
Mdm::VulnAttempt.delete_all('vuln_id = ?', self.id)
|
||||
end
|
||||
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::VulnAttempt
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :vuln, :class_name => "Mdm::Vuln", :counter_cache => :vuln_attempt_count
|
||||
validates :vuln_id, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,8 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::VulnDetail
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :vuln, :class_name => "Mdm::Vuln", :counter_cache => :vuln_detail_count
|
||||
validates :vuln_id, :presence => true
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::VulnRef
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
base.table_name = "vulns_refs"
|
||||
belongs_to :ref
|
||||
belongs_to :vuln
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::WebForm
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
belongs_to :web_site, :class_name => "Mdm::WebSite"
|
||||
serialize :params, ::MetasploitDataModels::Base64Serializer.new
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::WebPage
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
belongs_to :web_site, :class_name => "Mdm::WebSite"
|
||||
serialize :headers, ::MetasploitDataModels::Base64Serializer.new
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::WebSite
|
||||
def self.included(base)
|
||||
base.class_eval {
|
||||
belongs_to :service, :class_name => "Mdm::Service", :foreign_key => "service_id"
|
||||
has_many :web_pages, :dependent => :destroy, :class_name => "Mdm::WebPage"
|
||||
has_many :web_forms, :dependent => :destroy, :class_name => "Mdm::WebForm"
|
||||
has_many :web_vulns, :dependent => :destroy, :class_name => "Mdm::WebVuln"
|
||||
|
||||
serialize :options, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
def to_url(ignore_vhost=false)
|
||||
proto = self.service.name == "https" ? "https" : "http"
|
||||
host = ignore_vhost ? self.service.host.address : self.vhost
|
||||
port = self.service.port
|
||||
|
||||
if Rex::Socket.is_ipv6?(host)
|
||||
host = "[#{host}]"
|
||||
end
|
||||
|
||||
url = "#{proto}://#{host}"
|
||||
if not ((proto == "http" and port == 80) or (proto == "https" and port == 443))
|
||||
url += ":#{port}"
|
||||
end
|
||||
url
|
||||
end
|
||||
|
||||
def page_count
|
||||
web_pages.size
|
||||
end
|
||||
|
||||
def form_count
|
||||
web_forms.size
|
||||
end
|
||||
|
||||
def vuln_count
|
||||
web_vulns.size
|
||||
end
|
||||
} # end class_eval block
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::WebVuln
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
belongs_to :web_site, :class_name => "Mdm::WebSite"
|
||||
serialize :params, ::MetasploitDataModels::Base64Serializer.new
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::WmapRequest
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
module MetasploitDataModels::ActiveRecordModels::WmapTarget
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,184 +0,0 @@
|
|||
# NOTE: this AR model is called "Project" on the Pro side
|
||||
|
||||
module MetasploitDataModels::ActiveRecordModels::Workspace
|
||||
def self.included(base)
|
||||
base.class_eval{
|
||||
|
||||
# Usage of the evil eval avoids dynamic constant assignment
|
||||
# exception when this module is included
|
||||
eval('DEFAULT = "default"') unless defined? DEFAULT
|
||||
|
||||
has_many :hosts, :dependent => :destroy, :class_name => "Mdm::Host"
|
||||
has_many :services, :through => :hosts, :class_name => "Mdm::Service", :foreign_key => "service_id"
|
||||
has_many :notes, :class_name => "Mdm::Note"
|
||||
has_many :loots, :through => :hosts, :class_name => "Mdm::Loot"
|
||||
has_many :events, :class_name => "Mdm::Event"
|
||||
has_many :reports, :dependent => :destroy, :class_name => "Mdm::Report"
|
||||
has_many :report_templates, :dependent => :destroy, :class_name => "Mdm::ReportTemplate"
|
||||
has_many :tasks, :dependent => :destroy, :class_name => "Mdm::Task", :order => "created_at DESC"
|
||||
has_many :clients, :through => :hosts, :class_name => "Mdm::Client"
|
||||
has_many :vulns, :through => :hosts, :class_name => "Mdm::Vuln"
|
||||
has_many :creds, :through => :services, :class_name => "Mdm::Cred"
|
||||
has_many :imported_creds, :dependent => :destroy, :class_name => "Mdm::ImportedCred"
|
||||
has_many :exploited_hosts, :through => :hosts, :class_name => "Mdm::ExploitedHost"
|
||||
has_many :sessions, :through => :hosts, :class_name => "Mdm::Session"
|
||||
has_many :cred_files, :dependent => :destroy, :class_name => "Mdm::CredFile"
|
||||
has_many :listeners, :dependent => :destroy, :class_name => "Mdm::Listener"
|
||||
belongs_to :owner, :class_name => "Mdm::User", :foreign_key => "owner_id"
|
||||
has_and_belongs_to_many :users, :join_table => "workspace_members", :uniq => true, :class_name => "Mdm::User"
|
||||
|
||||
before_save :normalize
|
||||
|
||||
validates :name, :presence => true, :uniqueness => true, :length => {:maximum => 255}
|
||||
validates :description, :length => {:maximum => 4096}
|
||||
validate :boundary_must_be_ip_range
|
||||
|
||||
def web_sites
|
||||
query = <<-EOQ
|
||||
SELECT DISTINCT web_sites.*
|
||||
FROM hosts, services, web_sites
|
||||
WHERE hosts.workspace_id = #{id} AND
|
||||
services.host_id = hosts.id AND
|
||||
web_sites.service_id = services.id
|
||||
EOQ
|
||||
Mdm::WebSite.find_by_sql(query)
|
||||
end
|
||||
|
||||
def web_pages
|
||||
query = <<-EOQ
|
||||
SELECT DISTINCT web_pages.*
|
||||
FROM hosts, services, web_sites, web_pages
|
||||
WHERE hosts.workspace_id = #{id} AND
|
||||
services.host_id = hosts.id AND
|
||||
web_sites.service_id = services.id AND
|
||||
web_pages.web_site_id = web_sites.id
|
||||
EOQ
|
||||
Mdm::WebPage.find_by_sql(query)
|
||||
end
|
||||
|
||||
def web_forms
|
||||
query = <<-EOQ
|
||||
SELECT DISTINCT web_forms.*
|
||||
FROM hosts, services, web_sites, web_forms
|
||||
WHERE hosts.workspace_id = #{id} AND
|
||||
services.host_id = hosts.id AND
|
||||
web_sites.service_id = services.id AND
|
||||
web_forms.web_site_id = web_sites.id
|
||||
EOQ
|
||||
Mdm::WebForm.find_by_sql(query)
|
||||
end
|
||||
|
||||
def unique_web_forms
|
||||
query = <<-EOQ
|
||||
SELECT DISTINCT web_forms.web_site_id, web_forms.path, web_forms.method, web_forms.query
|
||||
FROM hosts, services, web_sites, web_forms
|
||||
WHERE hosts.workspace_id = #{id} AND
|
||||
services.host_id = hosts.id AND
|
||||
web_sites.service_id = services.id AND
|
||||
web_forms.web_site_id = web_sites.id
|
||||
EOQ
|
||||
Mdm::WebForm.find_by_sql(query)
|
||||
end
|
||||
|
||||
def web_vulns
|
||||
query = <<-EOQ
|
||||
SELECT DISTINCT web_vulns.*
|
||||
FROM hosts, services, web_sites, web_vulns
|
||||
WHERE hosts.workspace_id = #{id} AND
|
||||
services.host_id = hosts.id AND
|
||||
web_sites.service_id = services.id AND
|
||||
web_vulns.web_site_id = web_sites.id
|
||||
EOQ
|
||||
Mdm::WebVuln.find_by_sql(query)
|
||||
end
|
||||
|
||||
def self.default
|
||||
find_or_create_by_name(DEFAULT)
|
||||
end
|
||||
|
||||
def default?
|
||||
name == DEFAULT
|
||||
end
|
||||
|
||||
def creds
|
||||
Mdm::Cred.find(
|
||||
:all,
|
||||
:include => {:service => :host},
|
||||
:conditions => ["hosts.workspace_id = ?", self.id]
|
||||
)
|
||||
end
|
||||
|
||||
def host_tags
|
||||
Mdm::Tag.find(
|
||||
:all,
|
||||
:include => :hosts,
|
||||
:conditions => ["hosts.workspace_id = ?", self.id]
|
||||
)
|
||||
end
|
||||
|
||||
#
|
||||
# This method iterates the creds table calling the supplied block with the
|
||||
# cred instance of each entry.
|
||||
#
|
||||
def each_cred(&block)
|
||||
creds.each do |cred|
|
||||
block.call(cred)
|
||||
end
|
||||
end
|
||||
|
||||
def each_host_tag(&block)
|
||||
host_tags.each do |host_tag|
|
||||
block.call(host_tag)
|
||||
end
|
||||
end
|
||||
|
||||
def web_unique_forms(addrs=nil)
|
||||
forms = unique_web_forms
|
||||
if addrs
|
||||
forms.reject!{|f| not addrs.include?( f.web_site.service.host.address ) }
|
||||
end
|
||||
forms
|
||||
end
|
||||
|
||||
def boundary_must_be_ip_range
|
||||
errors.add(:boundary, "must be a valid IP range") unless valid_ip_or_range?(boundary)
|
||||
end
|
||||
|
||||
#
|
||||
# If limit_to_network is disabled, this will always return true.
|
||||
# Otherwise, return true only if all of the given IPs are within the project
|
||||
# boundaries.
|
||||
#
|
||||
def allow_actions_on?(ips)
|
||||
return true unless limit_to_network
|
||||
return true unless boundary
|
||||
return true if boundary.empty?
|
||||
boundaries = Shellwords.split(boundary)
|
||||
return true if boundaries.empty? # It's okay if there is no boundary range after all
|
||||
given_range = Rex::Socket::RangeWalker.new(ips)
|
||||
return false unless given_range # Can't do things to nonexistant IPs
|
||||
allowed = false
|
||||
boundaries.each do |boundary_range|
|
||||
ok_range = Rex::Socket::RangeWalker.new(boundary)
|
||||
allowed = true if ok_range.include_range? given_range
|
||||
end
|
||||
return allowed
|
||||
end
|
||||
|
||||
private
|
||||
def valid_ip_or_range?(string)
|
||||
begin
|
||||
Rex::Socket::RangeWalker.new(string)
|
||||
rescue
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def normalize
|
||||
boundary.strip! if boundary
|
||||
end
|
||||
|
||||
} # end class_eval block
|
||||
end
|
||||
end
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
module MetasploitDataModels
|
||||
VERSION = "0.0.2"
|
||||
end
|
|
@ -1,25 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
require "metasploit_data_models/version"
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "metasploit_data_models"
|
||||
s.version = "0.0.2.43DEV" # This gemspec is linked to metasploit releases and follows trunk
|
||||
s.authors = ["Trevor Rosen"]
|
||||
s.email = ["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.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 = ["lib"]
|
||||
|
||||
# ---- Dependencies ----
|
||||
s.add_development_dependency "rspec"
|
||||
s.add_runtime_dependency "activerecord"
|
||||
s.add_runtime_dependency "activesupport"
|
||||
s.add_runtime_dependency "pg"
|
||||
s.add_runtime_dependency "pry"
|
||||
end
|
|
@ -1 +0,0 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + "/../lib/msf_models")
|
|
@ -0,0 +1,18 @@
|
|||
# bundler configuration
|
||||
.bundle
|
||||
# Mac OS X folder attributes
|
||||
.DS_Store
|
||||
# built gems
|
||||
*.gem
|
||||
# Rubymine project configuration
|
||||
.idea
|
||||
# Don't check in rvmrc since this is a gem
|
||||
.rvmrc
|
||||
# Installed gem versions. Not stored for the same reasons as .rvmrc
|
||||
Gemfile.lock
|
||||
# Packaging directory for builds
|
||||
pkg/*
|
||||
# Database configuration (with passwords) for specs
|
||||
spec/dummy/config/database.yml
|
||||
# logs
|
||||
*.log
|
|
@ -0,0 +1,10 @@
|
|||
source "http://rubygems.org"
|
||||
|
||||
# Specify your gem's dependencies in metasploit_data_models.gemspec
|
||||
gemspec
|
||||
|
||||
group :test do
|
||||
# rails is only used for testing with a dummy application in spec/dummy
|
||||
gem 'rails'
|
||||
gem 'rspec-rails'
|
||||
end
|
|
@ -0,0 +1,72 @@
|
|||
#MetasploitDataModels
|
||||
|
||||
The database layer for Metasploit
|
||||
|
||||
|
||||
## Purpose
|
||||
__MetasploitDataModels__ exists to do several key things:
|
||||
|
||||
1. Allow code sharing between Metasploit Framework (MSF) and the commercial versions of Metasploit (Community, Express, Pro -- usually referred to collectively as "Pro")
|
||||
|
||||
2. Give developers a lightweight entry point to MSF's backend for use in developing tools that gather data intended for later use with Metasploit (e.g. specialized scanners).
|
||||
|
||||
3. Make it easy to keep commercial stuff private while increasing the functionality of the open-source tools we provide to the community.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
### Rails
|
||||
|
||||
In a Rails application, MetasploitDataModels acts a
|
||||
[Rails Engine](http://edgeapi.rubyonrails.org/classes/Rails/Engine.html) and the models are available to application
|
||||
just as if they were defined under app/models. If your Rails appliation needs to modify the models, this can be done
|
||||
using ActiveSupport.on_load hooks in initializers. The block passed to on_load hook is evaluated in the context of the
|
||||
model class, so defining method and including modules will work just like reopeninng the class, but
|
||||
ActiveSupport.on_load ensures that the monkey patches will work after reloading in development mode. Each class has a
|
||||
different on_load name, which is just the class name converted to an underscored symbol, so Mdm::ApiKey runs the
|
||||
:mdm_api_key load hooks, etc.
|
||||
|
||||
# Gemfile
|
||||
gem :metasploiit_data_models, :git => git://github.com/rapid7/metasploit_data_models.git, :tag => 'v0.3.0'
|
||||
|
||||
# config/initializers/metasploit_data_models.rb
|
||||
ActiveSupport.on_load(:mdm_api_key) do
|
||||
# Returns the String obfuscated token for display. Meant to avoid CSRF
|
||||
# api-key stealing attackes.
|
||||
def obfuscated_token
|
||||
token[0..3] + "****************************"
|
||||
end
|
||||
end
|
||||
|
||||
### Metasploit Framework
|
||||
|
||||
In Metasploit Framework, `MetasploitDataModels.require_models` is called by the `Msf::DbManager` to use the data models
|
||||
only if the user wants to use the database.
|
||||
|
||||
### Elsewhere
|
||||
|
||||
__NOTE: This isn't in RubyGems yet. Using a Gemfile entry pointing to this repo (i.e., using
|
||||
[Bundler](http://gembundler.com)) is the suggested option for now.__
|
||||
|
||||
Usage outside of Rapid7 is still alpha, as reflected in the pre-1.0.0 version, and we're not making many promises. That
|
||||
being said, usage is easy:
|
||||
|
||||
connection_info = YAML.load_file("path/to/rails-style/db_config_file")
|
||||
ActiveRecord::Base.establish_connection(connection_info['development'])
|
||||
MetasploitDataModels.require_models
|
||||
|
||||
Basically you need to do the following things:
|
||||
|
||||
1. Establish an ActiveRecord connection. A Rails __config/database.yml__ is ideal for this.
|
||||
2. `MetasploitDataModels.require_models`
|
||||
|
||||
|
||||
## Developer Info
|
||||
|
||||
### Console
|
||||
The gem includes a console based on [Pry](https://github.com/pry/pry/)
|
||||
|
||||
Give it a path to a working MSF database.yml file for full
|
||||
ActiveRecord-based access to your data.
|
||||
|
||||
__Note:__ "development" mode is hardcoded into the console currently.
|
|
@ -0,0 +1,7 @@
|
|||
require 'bundler/gem_tasks'
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
RSpec::Core::RakeTask.new(:spec)
|
||||
|
||||
task :default => :spec
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
class Mdm::ApiKey < ActiveRecord::Base
|
||||
#
|
||||
# Validators
|
||||
#
|
||||
|
||||
validate :supports_api
|
||||
validates :token, :presence => true, :length => { :minimum => 8 }
|
||||
|
||||
protected
|
||||
|
||||
def supports_api
|
||||
license = License.get
|
||||
|
||||
if license and not license.supports_api?
|
||||
errors[:license] = " - this product does not support API access"
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_api_key, self)
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class Mdm::Client < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
belongs_to :campaign, :class_name => 'Mdm::Campaign'
|
||||
belongs_to :host, :class_name => 'Mdm::Host'
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_client, self)
|
||||
end
|
|
@ -0,0 +1,80 @@
|
|||
class Mdm::Cred < ActiveRecord::Base
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
KEY_ID_REGEX = /([0-9a-fA-F:]{47})/
|
||||
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'
|
||||
}
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
belongs_to :service, :class_name => "Mdm::Service"
|
||||
|
||||
def ptype_human
|
||||
humanized = PTYPES.select do |k, v|
|
||||
v == ptype
|
||||
end.keys[0]
|
||||
|
||||
humanized ? humanized : ptype
|
||||
end
|
||||
|
||||
# Returns its key id. If this is not an ssh-type key, returns nil.
|
||||
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.
|
||||
end
|
||||
|
||||
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
|
||||
false
|
||||
end
|
||||
matches.include?(self) and matches.include?(other_cred)
|
||||
end
|
||||
|
||||
# Returns all keys with matching key ids, including itself
|
||||
# If this is not an ssh-type key, always returns an empty array.
|
||||
def ssh_keys
|
||||
(self.ssh_private_keys | self.ssh_public_keys)
|
||||
end
|
||||
|
||||
# Returns all private keys with matching key ids, including itself
|
||||
# If this is not an ssh-type key, always returns an empty array.
|
||||
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}
|
||||
end
|
||||
|
||||
# Returns all public keys with matching key ids, including itself
|
||||
# If this is not an ssh-type key, always returns an empty array.
|
||||
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}
|
||||
end
|
||||
|
||||
# Returns its workspace
|
||||
def workspace
|
||||
self.service.host.workspace
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_cred, self)
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
class Mdm::CredFile < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_cred_file, self)
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
class Mdm::Event < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :host, :class_name => 'Mdm::Host'
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :flagged, where(:critical => true, :seen => false)
|
||||
scope :module_run, where(:name => 'module_run')
|
||||
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :info, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_event, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
class Mdm::ExploitAttempt < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :exploit_attempt_count
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :host_id, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_exploit_attempt, self)
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
class Mdm::ExploitedHost < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :host, :class_name => 'Mdm::Host'
|
||||
belongs_to :service, :class_name => 'Mdm::Service'
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_exploited_host, self)
|
||||
end
|
134
lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host.rb
Executable file
134
lib/gemcache/ruby/1.9.1/gems/metasploit_data_models-0.3.0/app/models/mdm/host.rb
Executable file
|
@ -0,0 +1,134 @@
|
|||
|
||||
class Mdm::Host < ActiveRecord::Base
|
||||
include Mdm::Host::OperatingSystemNormalization
|
||||
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
before_destroy :cleanup_tags
|
||||
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
|
||||
# Fields searched for the search scope
|
||||
SEARCH_FIELDS = [
|
||||
'address::text',
|
||||
'hosts.name',
|
||||
'os_name',
|
||||
'os_flavor',
|
||||
'os_sp',
|
||||
'mac',
|
||||
'purpose',
|
||||
'comments'
|
||||
]
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :exploit_attempts, :dependent => :destroy, :class_name => 'Mdm::ExploitAttempt'
|
||||
has_many :exploited_hosts, :dependent => :destroy, :class_name => 'Mdm::ExploitedHost'
|
||||
has_many :clients, :dependent => :delete_all, :class_name => 'Mdm::Client'
|
||||
has_many :host_details, :dependent => :destroy, :class_name => 'Mdm::HostDetail'
|
||||
# hosts_tags are cleaned up in before_destroy:
|
||||
has_many :hosts_tags, :class_name => 'Mdm::HostTag'
|
||||
has_many :loots, :dependent => :destroy, :class_name => 'Mdm::Loot', :order => 'loots.created_at desc'
|
||||
has_many :notes, :dependent => :delete_all, :class_name => 'Mdm::Note', :order => 'notes.created_at'
|
||||
has_many :services, :dependent => :destroy, :class_name => 'Mdm::Service', :order => 'services.port, services.proto'
|
||||
has_many :sessions, :dependent => :destroy, :class_name => 'Mdm::Session', :order => 'sessions.opened_at'
|
||||
has_many :vulns, :dependent => :delete_all, :class_name => 'Mdm::Vuln'
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
#
|
||||
# Through host_tags
|
||||
#
|
||||
has_many :tags, :through => :hosts_tags, :class_name => 'Mdm::Tag'
|
||||
|
||||
#
|
||||
# Through services
|
||||
#
|
||||
has_many :creds, :through => :services, :class_name => 'Mdm::Cred'
|
||||
has_many :service_notes, :through => :services
|
||||
has_many :web_sites, :through => :services, :class_name => 'Mdm::WebSite'
|
||||
|
||||
#
|
||||
# Nested Attributes
|
||||
# @note Must be declared after relations being referenced.
|
||||
#
|
||||
|
||||
accepts_nested_attributes_for :services, :reject_if => lambda { |s| s[:port].blank? }, :allow_destroy => true
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :address,
|
||||
:exclusion => {
|
||||
:in => ['127.0.0.1']
|
||||
},
|
||||
:ip_format => true,
|
||||
:presence => true,
|
||||
:uniqueness => {
|
||||
:scope => :workspace_id,
|
||||
:unless => :ip_address_invalid?
|
||||
}
|
||||
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|
|
||||
"#{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) }
|
||||
|
||||
def attribute_locked?(attr)
|
||||
n = notes.find_by_ntype("host.updated.#{attr}")
|
||||
n && n.data[:locked]
|
||||
end
|
||||
|
||||
def cleanup_tags
|
||||
# No need to keep tags with no hosts
|
||||
tags.each do |tag|
|
||||
tag.destroy if tag.hosts == [self]
|
||||
end
|
||||
# Clean up association table records
|
||||
Mdm::HostTag.delete_all("host_id = #{self.id}")
|
||||
end
|
||||
|
||||
# This is replicated by the IpAddressValidator class. Had to put it here as well to avoid
|
||||
# SQL errors when checking address uniqueness.
|
||||
def ip_address_invalid?
|
||||
begin
|
||||
potential_ip = IPAddr.new(address)
|
||||
return true unless potential_ip.ipv4? || potential_ip.ipv6?
|
||||
rescue ArgumentError
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
def is_vm?
|
||||
!!self.virtual_host
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_host, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
class Mdm::HostDetail < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :host_detail_count
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :host_id, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_host_detail, self)
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
class Mdm::HostTag < ActiveRecord::Base
|
||||
self.table_name = "hosts_tags"
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :host, :class_name => 'Mdm::Host'
|
||||
belongs_to :tag, :class_name => 'Mdm::Tag'
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_host_tag, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
class Mdm::ImportedCred < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_imported_cred, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
class Mdm::Listener < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :task, :class_name => 'Mdm::Task'
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :options, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :address, :ip_format => true, :presence => true
|
||||
validates :port, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_listener, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
class Mdm::Loot < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
|
||||
RELATIVE_SEARCH_FIELDS = [
|
||||
'ltype',
|
||||
'name',
|
||||
'info',
|
||||
'data'
|
||||
]
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :host, :class_name => 'Mdm::Host'
|
||||
belongs_to :service, :class_name => 'Mdm::Service'
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :search, lambda { |*args|
|
||||
# @todo replace with AREL
|
||||
terms = RELATIVE_SEARCH_FIELDS.collect { |relative_field|
|
||||
"loots.#{relative_field} ILIKE ?"
|
||||
}
|
||||
disjunction = terms.join(' OR ')
|
||||
formatted_parameter = "%#{args[0]}%"
|
||||
parameters = [formatted_parameter] * RELATIVE_SEARCH_FIELDS.length
|
||||
conditions = [disjunction] + parameters
|
||||
|
||||
where(conditions)
|
||||
}
|
||||
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :data, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.loot_delete_file(self[:id])
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_loot, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
class Mdm::Macro < ActiveRecord::Base
|
||||
extend MetasploitDataModels::SerializedPrefs
|
||||
|
||||
#
|
||||
# Serialization
|
||||
#
|
||||
|
||||
serialize :actions, MetasploitDataModels::Base64Serializer.new
|
||||
serialize :prefs, MetasploitDataModels::Base64Serializer.new
|
||||
serialized_prefs_attr_accessor :max_time
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :name, :presence => true, :format => /^[^'|"]+$/
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_macro, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
class Mdm::ModRef < ActiveRecord::Base
|
||||
ActiveSupport.run_load_hooks(:mdm_mod_ref, self)
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
class Mdm::ModuleAction < ActiveRecord::Base
|
||||
self.table_name = 'module_actions'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :module_detail, :class_name => 'Mdm::ModuleDetail'
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
validate :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_action, self)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class Mdm::ModuleArch < ActiveRecord::Base
|
||||
self.table_name = 'module_archs'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :module_detail, :class_name => 'Mdm::ModuleDetail'
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validate :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_arch, self)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class Mdm::ModuleAuthor < ActiveRecord::Base
|
||||
self.table_name = 'module_authors'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :module_detail
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validate :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_author, self)
|
||||
end
|
|
@ -0,0 +1,73 @@
|
|||
class Mdm::ModuleDetail < ActiveRecord::Base
|
||||
self.table_name = 'module_details'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :actions, :class_name => 'Mdm::ModuleAction', :dependent => :destroy, :source => :module_action
|
||||
has_many :archs, :class_name => 'Mdm::ModuleArch', :dependent => :destroy, :source => :module_arch
|
||||
has_many :authors, :class_name => 'Mdm::ModuleAuthor', :dependent => :destroy, :source => :module_author
|
||||
has_many :mixins, :class_name => 'Mdm::ModuleMixin', :dependent => :destroy, :source => :module_mixin
|
||||
has_many :platforms, :class_name => 'Mdm::ModulePlatform', :dependent => :destroy, :source => :module_platform
|
||||
has_many :refs, :class_name => 'Mdm::ModuleRef', :dependent => :destroy, :source => :module_ref
|
||||
has_many :targets, :class_name => 'Mdm::ModuleTarget', :dependent => :destroy, :source => :module_target
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validate :refname, :presence => true
|
||||
|
||||
validates_associated :actions
|
||||
validates_associated :archs
|
||||
validates_associated :authors
|
||||
validates_associated :mixins
|
||||
validates_associated :platforms
|
||||
validates_associated :refs
|
||||
validates_associated :targets
|
||||
|
||||
def add_author(name, email=nil)
|
||||
if email
|
||||
r = self.authors.build(:name => name, :email => email).save
|
||||
else
|
||||
self.authors.build(:name => name).save
|
||||
end
|
||||
end
|
||||
|
||||
def add_mixin(name)
|
||||
self.mixins.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_target(idx, name)
|
||||
self.targets.build(:index => idx, :name => name).save
|
||||
end
|
||||
|
||||
def add_action(name)
|
||||
self.actions.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_ref(name)
|
||||
self.refs.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_arch(name)
|
||||
self.archs.build(:name => name).save
|
||||
end
|
||||
|
||||
def add_platform(name)
|
||||
self.platforms.build(:name => name).save
|
||||
end
|
||||
|
||||
def before_destroy
|
||||
Mdm::ModuleAuthor.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleMixin.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleTarget.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleAction.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleRef.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModuleArch.delete_all('module_detail_id = ?', self.id)
|
||||
Mdm::ModulePlatform.delete_all('module_detail_id = ?', self.id)
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_detail, self)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class Mdm::ModuleMixin < ActiveRecord::Base
|
||||
self.table_name = 'module_mixins'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :module_detail, :class_name => 'Mdm::ModuleDetail'
|
||||
|
||||
#
|
||||
# Validation
|
||||
#
|
||||
|
||||
validate :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_mixin, self)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class Mdm::ModulePlatform < ActiveRecord::Base
|
||||
self.table_name = 'module_platforms'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :module_detail
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validate :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_platform, self)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class Mdm::ModuleRef < ActiveRecord::Base
|
||||
self.table_name = 'module_refs'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :module_detail, :class_name => 'Mdm::ModuleDetail'
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validate :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_ref, self)
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class Mdm::ModuleTarget < ActiveRecord::Base
|
||||
self.table_name = 'module_targets'
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :module_detail
|
||||
|
||||
#
|
||||
# Validators
|
||||
#
|
||||
|
||||
validate :name, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_module_target, self)
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
class Mdm::NexposeConsole < ActiveRecord::Base
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :cached_sites, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :address, :presence => true
|
||||
validates :name, :presence => true
|
||||
validates :password, :presence => true
|
||||
validates :port, :inclusion => {:in => 1..65535}
|
||||
validates :username, :presence => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_nexpose_console, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
class Mdm::Note < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
after_save :normalize
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
belongs_to :host, :class_name => "Mdm::Host", :counter_cache => :note_count
|
||||
belongs_to :service, :class_name => "Mdm::Service"
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :flagged, where('critical = true AND seen = false')
|
||||
|
||||
notes = self.arel_table
|
||||
scope :visible, where(notes[:ntype].not_in(['web.form', 'web.url', 'web.vuln']))
|
||||
|
||||
scope :search, lambda { |*args|
|
||||
where(["(data NOT ILIKE 'BAh7%' AND data LIKE ?)" +
|
||||
"OR (data ILIKE 'BAh7%' AND decode(data, 'base64') LIKE ?)" +
|
||||
"OR ntype ILIKE ?",
|
||||
"%#{args[0]}%", "%#{args[0]}%", "%#{args[0]}%"
|
||||
])
|
||||
}
|
||||
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :data, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
private
|
||||
|
||||
def normalize
|
||||
if data_changed? and ntype =~ /fingerprint/
|
||||
host.normalize_os
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_note, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class Mdm::Profile < ActiveRecord::Base
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
serialize :settings, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_profile, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
class Mdm::Ref < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :vulns_refs, :class_name => 'Mdm::VulnRef'
|
||||
|
||||
#
|
||||
# Through :vuln_refs
|
||||
#
|
||||
has_many :vulns, :class_name => 'Mdm::Vuln', :through => :vulns_refs
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_ref, self)
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
class Mdm::Report < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :flagged, where('reports.downloaded_at is NULL')
|
||||
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :options, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :name,
|
||||
:format => {
|
||||
:allow_blank => true,
|
||||
:message => "name must consist of A-Z, 0-9, space, dot, underscore, or dash",
|
||||
:with => /^[A-Za-z0-9\x20\x2e\x2d\x5f\x5c]+$/
|
||||
}
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.report_delete_file(self[:id])
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_report, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
class Mdm::ReportTemplate < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :workspace, :class_name => 'Mdm::Workspace'
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.report_template_delete_file(self[:id])
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_report_template, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class Mdm::Route < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :session, :class_name => 'Mdm::Session'
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_route, self)
|
||||
end
|
|
@ -0,0 +1,56 @@
|
|||
class Mdm::Service < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
after_save :normalize_host_os
|
||||
|
||||
#
|
||||
# CONSTANTS
|
||||
#
|
||||
|
||||
STATES = ['open', 'closed', 'filtered', 'unknown']
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :creds, :dependent => :destroy, :class_name => 'Mdm::Cred'
|
||||
has_many :exploited_hosts, :dependent => :destroy, :class_name => 'Mdm::ExploitedHost'
|
||||
belongs_to :host, :class_name => 'Mdm::Host', :counter_cache => :service_count
|
||||
has_many :notes, :dependent => :destroy, :class_name => 'Mdm::Note'
|
||||
has_many :vulns, :dependent => :destroy, :class_name => 'Mdm::Vuln'
|
||||
has_many :web_sites, :dependent => :destroy, :class_name => 'Mdm::WebSite'
|
||||
|
||||
#
|
||||
# Through :web_sites
|
||||
#
|
||||
has_many :web_pages, :through => :web_sites, :class_name => 'Mdm::WebPage'
|
||||
has_many :web_forms, :through => :web_sites, :class_name => 'Mdm::WebForm'
|
||||
has_many :web_vulns, :through => :web_sites, :class_name => 'Mdm::WebVuln'
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :inactive, where("services.state != 'open'")
|
||||
scope :with_state, lambda { |a_state| where("services.state = ?", a_state)}
|
||||
scope :search, lambda { |*args|
|
||||
where([
|
||||
"services.name ILIKE ? OR " +
|
||||
"services.info ILIKE ? OR " +
|
||||
"services.proto ILIKE ? OR " +
|
||||
"services.port = ? ",
|
||||
"%#{args[0]}%", "%#{args[0]}%", "%#{args[0]}%", (args[0].to_i > 0) ? args[0].to_i : 99999
|
||||
])
|
||||
}
|
||||
|
||||
def normalize_host_os
|
||||
if info_changed?
|
||||
host.normalize_os
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_service, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
class Mdm::Session < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
before_destroy :stop
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :events, :class_name => 'Mdm::SessionEvent', :order => 'created_at', :dependent => :delete_all
|
||||
belongs_to :host, :class_name => 'Mdm::Host'
|
||||
has_many :routes, :class_name => 'Mdm::Route', :dependent => :delete_all
|
||||
|
||||
#
|
||||
# Through :host
|
||||
#
|
||||
has_one :workspace, :through => :host, :class_name => 'Mdm::Workspace'
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :alive, where('closed_at IS NULL')
|
||||
scope :dead, where('closed_at IS NOT NULL')
|
||||
scope :upgradeable, where("closed_at IS NULL AND stype = 'shell' and platform ILIKE '%win%'")
|
||||
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :datastore, ::MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
def upgradeable?
|
||||
(self.platform =~ /win/ and self.stype == 'shell')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stop
|
||||
c = Pro::Client.get rescue nil
|
||||
# ignore exceptions (XXX - ideally, stopped an already-stopped session wouldn't throw XMLRPCException)
|
||||
c.session_stop(self.local_id) rescue nil
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_session, self)
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class Mdm::SessionEvent < ActiveRecord::Base
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :session, :class_name => 'Mdm::Session'
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_session_event, self)
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
class Mdm::Tag < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
before_destroy :cleanup_hosts
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :hosts_tags, :class_name => 'Mdm::HostTag'
|
||||
belongs_to :user, :class_name => 'Mdm::User'
|
||||
|
||||
#
|
||||
# Through :hosts_tags
|
||||
#
|
||||
has_many :hosts, :through => :hosts_tags, :class_name => 'Mdm::Host'
|
||||
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :desc,
|
||||
:length => {
|
||||
:maximum => ((8 * (2 ** 10)) - 1),
|
||||
:message => "desc must be less than 8k."
|
||||
}
|
||||
validates :name,
|
||||
:format => {
|
||||
:with => /^[A-Za-z0-9\x2e\x2d_]+$/, :message => "must be alphanumeric, dots, dashes, or underscores"
|
||||
},
|
||||
:presence => true
|
||||
|
||||
def cleanup_hosts
|
||||
# Clean up association table records
|
||||
Mdm::HostTag.delete_all("tag_id = #{self.id}")
|
||||
end
|
||||
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_tag, self)
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
class Mdm::Task < ActiveRecord::Base
|
||||
#
|
||||
# Callbacks
|
||||
#
|
||||
|
||||
before_destroy :delete_file
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
belongs_to :workspace, :class_name => "Mdm::Workspace"
|
||||
|
||||
#
|
||||
# Scopes
|
||||
#
|
||||
|
||||
scope :running, order( "created_at DESC" ).where("completed_at IS NULL")
|
||||
|
||||
#
|
||||
# Serializations
|
||||
#
|
||||
|
||||
serialize :options, MetasploitDataModels::Base64Serializer.new
|
||||
serialize :result, MetasploitDataModels::Base64Serializer.new
|
||||
serialize :settings, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
private
|
||||
|
||||
def delete_file
|
||||
c = Pro::Client.get rescue nil
|
||||
if c
|
||||
c.task_delete_log(self[:id]) if c
|
||||
else
|
||||
::File.unlink(self.path) rescue nil
|
||||
end
|
||||
end
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_task, self)
|
||||
end
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
class Mdm::User < ActiveRecord::Base
|
||||
extend MetasploitDataModels::SerializedPrefs
|
||||
|
||||
#
|
||||
# Relations
|
||||
#
|
||||
|
||||
has_many :owned_workspaces, :foreign_key => 'owner_id', :class_name => 'Mdm::Workspace'
|
||||
has_many :tags, :class_name => 'Mdm::Tag'
|
||||
has_and_belongs_to_many :workspaces, :join_table => 'workspace_members', :uniq => true, :class_name => 'Mdm::Workspace'
|
||||
|
||||
#
|
||||
# Serialziations
|
||||
#
|
||||
|
||||
serialize :prefs, MetasploitDataModels::Base64Serializer.new
|
||||
|
||||
serialized_prefs_attr_accessor :nexpose_host, :nexpose_port, :nexpose_user, :nexpose_pass, :nexpose_creds_type, :nexpose_creds_user, :nexpose_creds_pass
|
||||
serialized_prefs_attr_accessor :http_proxy_host, :http_proxy_port, :http_proxy_user, :http_proxy_pass
|
||||
serialized_prefs_attr_accessor :time_zone, :session_key
|
||||
serialized_prefs_attr_accessor :last_login_address # specifically NOT last_login_ip to prevent confusion with AuthLogic magic columns (which dont work for serialized fields)
|
||||
|
||||
#
|
||||
# Validations
|
||||
#
|
||||
|
||||
validates :password, :password_is_strong => true
|
||||
validates :password_confirmation, :password_is_strong => true
|
||||
|
||||
ActiveSupport.run_load_hooks(:mdm_user, self)
|
||||
end
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue