Merge feature/recog into post-electro master for this PR

bug/bundler_fix
HD Moore 2014-08-16 01:19:08 -05:00
commit 6d92d701d7
644 changed files with 30275 additions and 21976 deletions

24
.gitignore vendored
View File

@ -15,8 +15,6 @@ Gemfile.local.lock
config/database.yml
# simplecov coverage data
coverage
data/meterpreter/ext_server_pivot.x86.dll
data/meterpreter/ext_server_pivot.x64.dll
doc/
external/source/meterpreter/java/bin
external/source/meterpreter/java/build
@ -50,6 +48,28 @@ tags
*.opensdf
*.user
# Rails log directory
/log
# ignore release/debug folders for exploits
external/source/exploits/**/Debug
external/source/exploits/**/Release
# Avoid checking in Meterpreter binaries. These are supplied upstream by
# the meterpreter_bins gem.
data/meterpreter/elevator.*.dll
data/meterpreter/ext_server_espia.*.dll
data/meterpreter/ext_server_extapi.*.dll
data/meterpreter/ext_server_incognito.*.dll
data/meterpreter/ext_server_kiwi.*.dll
data/meterpreter/ext_server_lanattacks.*.dll
data/meterpreter/ext_server_mimikatz.*.dll
data/meterpreter/ext_server_priv.*.dll
data/meterpreter/ext_server_stdapi.*.dll
data/meterpreter/metsrv.*.dll
data/meterpreter/screenshot.*.dll
# Avoid checking in Meterpreter libs that are built from
# private source. If you're interested in this functionality,
# check out Metasploit Pro: http://metasploit.com/download
data/meterpreter/ext_server_pivot.*.dll

56
.rubocop.yml Normal file
View File

@ -0,0 +1,56 @@
# This list was intially created by analyzing the last three months (51
# modules) committed to Metasploit Framework. Many, many older modules
# will have offenses, but this should at least provide a baseline for
# new modules.
#
# Updates to this file should include a 'Description' parameter for any
# explaination needed.
# inherit_from: .rubocop_todo.yml
Style/ClassLength:
Description: 'Most Metasploit modules are quite large. This is ok.'
Enabled: true
Exclude:
- 'modules/**/*'
Style/Documentation:
Enabled: true
Description: 'Most Metasploit modules do not have class documentation.'
Exclude:
- 'modules/**/*'
Style/Encoding:
Enabled: true
Description: 'We prefer binary to UTF-8.'
EnforcedStyle: 'when_needed'
Style/LineLength:
Description: >-
Metasploit modules often pattern match against very
long strings when identifying targets.
Enabled: true
Max: 180
Style/MethodLength:
Enabled: true
Description: >-
While the style guide suggests 10 lines, exploit definitions
often exceed 200 lines.
Max: 300
Style/NumericLiterals:
Enabled: false
Description: 'This often hurts readability for exploit-ish code.'
Style/SpaceInsideBrackets:
Enabled: false
Description: 'Until module template are final, most modules will fail this.'
Style/StringLiterals:
Enabled: false
Description: 'Single vs double quote fights are largely unproductive.'
Style/WordArray:
Enabled: false
Description: 'Metasploit prefers consistent use of []'

View File

@ -3,5 +3,8 @@
--exclude \.ut\.rb/
--exclude \.ts\.rb/
--files CONTRIBUTING.md,COPYING,HACKING,LICENSE
app/**/*.rb
lib/msf/**/*.rb
lib/metasploit/**/*.rb
lib/rex/**/*.rb
plugins/**/*.rb

View File

@ -33,6 +33,7 @@ and Metasploit's [Common Coding Mistakes](https://github.com/rapid7/metasploit-f
## Code Contributions
* **Do** stick to the [Ruby style guide](https://github.com/bbatsov/ruby-style-guide).
* *Do* get [Rubocop](https://rubygems.org/search?query=rubocop) relatively quiet against the code you are adding or modifying.
* **Do** follow the [50/72 rule](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) for Git commit messages.
* **Do** create a [topic branch](http://git-scm.com/book/en/Git-Branching-Branching-Workflows#Topic-Branches) to work on instead of working directly on `master`.

89
Gemfile
View File

@ -1,73 +1,54 @@
source 'https://rubygems.org'
# Need 3+ for ActiveSupport::Concern
gem 'activesupport', '>= 3.0.0', '< 4.0.0'
# Needed for some admin modules (cfme_manageiq_evm_pass_reset.rb)
gem 'bcrypt'
# Needed for some admin modules (scrutinizer_add_user.rb)
gem 'json'
# Needed by msfgui and other rpc components
gem 'msgpack'
# Needed by anemone crawler
gem 'nokogiri'
# Needed by db.rb and Msf::Exploit::Capture
gem 'packetfu', '1.1.9'
# Needed by JSObfu
gem 'rkelly-remix', '0.0.6'
# Needed by anemone crawler
gem 'robots'
# Needed for service fingerprinting (Recog)
gem 'recog', :git => 'git@github.com:rapid7/recog.git'
# Needed for some post modules
gem 'sqlite3'
# Add default group gems to `metasploit-framework.gemspec`:
# spec.add_runtime_dependency '<name>', [<version requirements>]
gemspec
group :db do
# Needed for Msf::DbManager
gem 'activerecord', '>= 3.0.0', '< 4.0.0'
# Database models shared between framework and Pro (depends on hmoore-r7 branch until merged)
# Unfortunately, MDM is broken after 0.17.0 in master right now, so waiting on that merge...
gem 'metasploit_data_models', '= 0.17.2'
# gem 'metasploit_data_models', :git => 'git@github.com:hmoore-r7/metasploit_data_models.git'
# Metasploit::Credential database models
gem 'metasploit-credential', '>= 0.8.6', '< 0.9'
# Database models shared between framework and Pro.
# gem 'metasploit_data_models', '~> 0.19'
gem 'metasploit_data_models', :git => 'git@github.com:hmoore-r7/metasploit_data_models.git'
# Needed for module caching in Mdm::ModuleDetails
gem 'pg', '>= 0.11'
end
group :development do
# Markdown formatting for yard
gem 'redcarpet'
# generating documentation
gem 'yard'
# for development and testing purposes
gem 'pry'
end
group :development, :test do
# 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'
# automatically include factories from spec/factories
gem 'factory_girl_rails'
# Make rspec output shorter and more useful
gem 'fivemat', '1.2.1'
# running documentation generation tasks and rspec tasks
gem 'rake', '>= 10.0.0'
# testing framework
gem 'rspec', '>= 2.12', '< 3.0.0'
# Define `rake spec`. Must be in development AND test so that its available by default as a rake test when the
# environment is development
gem 'rspec-rails' , '>= 2.12', '< 3.0.0'
end
group :pcap do
gem 'network_interface', '~> 0.0.1'
# For sniffer and raw socket modules
gem 'pcaprub'
end
group :development do
# Markdown formatting for yard
gem 'redcarpet'
# generating documentation
gem 'yard'
end
group :development, :test do
# 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'
# Make rspec output shorter and more useful
gem 'fivemat', '1.2.1'
# running documentation generation tasks and rspec tasks
gem 'rake', '>= 10.0.0'
end
group :test do
# Removes records from database created during tests. Can't use rspec-rails'
# transactional fixtures because multiple connections are in use so
# transactions won't work.
gem 'database_cleaner'
# testing framework
gem 'rspec', '>= 2.12'
gem 'shoulda-matchers'
# code coverage for tests
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.

View File

@ -27,8 +27,6 @@ end
# Create a custom group
group :local do
# Use pry to help view and interact with objects in the framework
gem 'pry', '~> 0.9'
# Use pry-debugger to step through code during development
gem 'pry-debugger', '~> 0.2'
# Add the lab gem so that the 'lab' plugin will work again

View File

@ -1,13 +1,49 @@
GIT
remote: git@github.com:rapid7/recog.git
revision: 9c2983de2e2ebbeb98c2211b5be95ab4099479bc
remote: git@github.com:hmoore-r7/metasploit_data_models.git
revision: 73846d7fb08817e44ec09684d7af1cd2372f6799
specs:
recog (0.01)
metasploit_data_models (0.19.7)
activerecord (>= 3.2.13, < 4.0.0)
activesupport
arel-helpers
metasploit-concern (~> 0.1.0)
metasploit-model (~> 0.26.1)
pg
recog
PATH
remote: .
specs:
metasploit-framework (4.10.1.pre.dev)
actionpack (< 4.0.0)
activesupport (>= 3.0.0, < 4.0.0)
bcrypt
json
metasploit-model (~> 0.26.1)
meterpreter_bins (= 0.0.6)
msgpack
nokogiri
packetfu (= 1.1.9)
railties
rkelly-remix (= 0.0.6)
robots
rubyzip (~> 1.1)
sqlite3
tzinfo
GEM
remote: https://rubygems.org/
specs:
actionpack (3.2.19)
activemodel (= 3.2.19)
activesupport (= 3.2.19)
builder (~> 3.0.0)
erubis (~> 2.7.0)
journey (~> 1.0.4)
rack (~> 1.4.5)
rack-cache (~> 1.2)
rack-test (~> 0.6.1)
sprockets (~> 2.2.1)
activemodel (3.2.19)
activesupport (= 3.2.19)
builder (~> 3.0.0)
@ -20,29 +56,68 @@ GEM
i18n (~> 0.6, >= 0.6.4)
multi_json (~> 1.0)
arel (3.0.3)
arel-helpers (2.0.1)
activerecord (>= 3.1.0, < 5)
bcrypt (3.1.7)
builder (3.0.4)
database_cleaner (1.1.1)
coderay (1.1.0)
diff-lcs (1.2.4)
erubis (2.7.0)
factory_girl (4.2.0)
activesupport (>= 3.0.0)
factory_girl_rails (4.2.1)
factory_girl (~> 4.2.0)
railties (>= 3.0.0)
fivemat (1.2.1)
hike (1.2.3)
i18n (0.6.9)
json (1.8.0)
metasploit_data_models (0.17.0)
activerecord (>= 3.2.13)
activesupport
journey (1.0.4)
json (1.8.1)
metasploit-concern (0.1.1)
activesupport (~> 3.0, >= 3.0.0)
metasploit-credential (0.8.9)
metasploit-concern (~> 0.1.0)
metasploit-model (~> 0.26.1)
metasploit_data_models (~> 0.19.4)
pg
rubyntlm
rubyzip (~> 1.1)
metasploit-model (0.26.1)
activesupport
meterpreter_bins (0.0.6)
method_source (0.8.2)
mini_portile (0.6.0)
msgpack (0.5.5)
msgpack (0.5.8)
multi_json (1.0.4)
network_interface (0.0.1)
nokogiri (1.6.2.1)
nokogiri (1.6.3.1)
mini_portile (= 0.6.0)
packetfu (1.1.9)
pcaprub (0.11.3)
pg (0.17.1)
pry (0.10.0)
coderay (~> 1.1.0)
method_source (~> 0.8.1)
slop (~> 3.4)
rack (1.4.5)
rack-cache (1.2)
rack (>= 0.4)
rack-ssl (1.3.4)
rack
rack-test (0.6.2)
rack (>= 1.0)
railties (3.2.19)
actionpack (= 3.2.19)
activesupport (= 3.2.19)
rack-ssl (~> 1.3.2)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (>= 0.14.6, < 2.0)
rake (10.1.0)
rdoc (3.12.2)
json (~> 1.4)
recog (0.01)
nokogiri
redcarpet (3.0.0)
rkelly-remix (0.0.6)
robots (0.10.1)
@ -54,13 +129,31 @@ GEM
rspec-expectations (2.14.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.3)
rspec-rails (2.14.2)
actionpack (>= 3.0)
activemodel (>= 3.0)
activesupport (>= 3.0)
railties (>= 3.0)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rubyntlm (0.4.0)
rubyzip (1.1.6)
shoulda-matchers (2.3.0)
activesupport (>= 3.0.0)
simplecov (0.5.4)
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
slop (3.6.0)
sprockets (2.2.2)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.9)
thor (0.19.1)
tilt (1.4.1)
timecop (0.6.3)
tzinfo (0.3.39)
yard (0.8.7)
@ -70,27 +163,21 @@ PLATFORMS
DEPENDENCIES
activerecord (>= 3.0.0, < 4.0.0)
activesupport (>= 3.0.0, < 4.0.0)
bcrypt
database_cleaner
factory_girl (>= 4.1.0)
factory_girl_rails
fivemat (= 1.2.1)
json
metasploit_data_models (= 0.17.0)
msgpack
metasploit-credential (>= 0.8.6, < 0.9)
metasploit-framework!
metasploit_data_models!
network_interface (~> 0.0.1)
nokogiri
packetfu (= 1.1.9)
pcaprub
pg (>= 0.11)
pry
rake (>= 10.0.0)
recog!
redcarpet
rkelly-remix (= 0.0.6)
robots
rspec (>= 2.12)
rspec (>= 2.12, < 3.0.0)
rspec-rails (>= 2.12, < 3.0.0)
shoulda-matchers
simplecov (= 0.5.4)
sqlite3
timecop
yard

View File

@ -10,7 +10,7 @@ CONTRIBUTING.md
in the same directory as this file, and to a lesser extent:
The Metasploit Development Environment
https://github.com/rapid7/metasploit-framework/wiki/Metasploit-Development-Environment
https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment
Common Coding Mistakes
https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes

View File

@ -1,81 +1,11 @@
require 'bundler/setup'
pathname = Pathname.new(__FILE__)
root = pathname.parent
# add metasploit-framework/lib to load paths so rake files can just require
# files normally without having to use __FILE__ and recalculating root and the
# path to lib
lib_pathname = root.join('lib')
$LOAD_PATH.unshift(lib_pathname.to_s)
#!/usr/bin/env rake
require File.expand_path('../config/application', __FILE__)
require 'metasploit/framework/require'
# @note must be before `Metasploit::Framework::Application.load_tasks`
#
# load rake files like a rails engine
#
# define db rake tasks from activerecord if activerecord is in the bundle. activerecord could be not in the bundle if
# the user installs with `bundle install --without db`
Metasploit::Framework::Require.optionally_active_record_railtie
rakefile_glob = root.join('lib', 'tasks', '**', '*.rake').to_path
Dir.glob(rakefile_glob) do |rakefile|
# Skip database tasks, will load them later if MDM is present
next if rakefile =~ /database\.rake$/
load rakefile
end
print_without = false
begin
require 'rspec/core/rake_task'
rescue LoadError
puts "rspec not in bundle, so can't set up spec tasks. " \
"To run specs ensure to install the development and test groups."
print_without = true
else
RSpec::Core::RakeTask.new(:spec => 'db:test:prepare')
task :default => :spec
end
# Require yard before loading metasploit_data_models rake tasks as the yard tasks won't be defined if
# YARD is not defined when yard.rake is loaded.
begin
require 'yard'
rescue LoadError
puts "yard not in bundle, so can't set up yard tasks. " \
"To generate documentation ensure to install the development group."
print_without = true
end
begin
require 'metasploit_data_models'
rescue LoadError
puts "metasploit_data_models not in bundle, so can't set up db tasks. " \
"To run database tasks, ensure to install the db bundler group."
print_without = true
else
load 'lib/tasks/database.rake'
metasploit_data_models_task_glob = MetasploitDataModels.root.join(
'lib',
'tasks',
'**',
'*.rake'
).to_s
# include tasks from metasplioit_data_models, such as `rake yard`.
# metasploit-framework specific yard options are in .yardopts
Dir.glob(metasploit_data_models_task_glob) do |path|
load path
end
end
if print_without
puts "Bundle currently installed " \
"'--without #{Bundler.settings.without.join(' ')}'."
puts "To clear the without option do `bundle install --without ''` " \
"(the --without flag with an empty string) or " \
"`rm -rf .bundle` to remove the .bundle/config manually and " \
"then `bundle install`"
end
Metasploit::Framework::Application.load_tasks

View File

@ -0,0 +1,23 @@
# Adds associations to `Metasploit::Credential::Core` which are inverses of association on models under
# {BruteForce::Reuse}.
require 'metasploit/framework/credential'
module Metasploit::Credential::Core::ToCredential
extend ActiveSupport::Concern
included do
def to_credential
Metasploit::Framework::Credential.new(
public: public.try(:username),
private: private.try(:data),
private_type: private.try(:type).try(:demodulize).try(:underscore).try(:to_sym),
realm: realm.try(:value),
realm_key: realm.try(:key),
parent: self
)
end
end
end

View File

@ -0,0 +1,2 @@
require 'metasploit/framework/file_path_validator'
require 'metasploit/framework/executable_path_validator'

View File

@ -0,0 +1,16 @@
module Metasploit
module Framework
# This is a ActiveModel custom validator that assumes the attribute
# is supposed to be the path to a regular file. It checks whether the
# file exists and whether or not it is an executable file.
class ExecutablePathValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless ::File.executable? value
record.errors[attribute] << (options[:message] || "is not a valid path to an executable file")
end
end
end
end
end

View File

@ -0,0 +1,16 @@
module Metasploit
module Framework
# This is a ActiveModel custom validator that assumes the attribute
# is supposed to be the path to a regular file. It checks whether the
# file exists and whether or not it is a regular file.
class FilePathValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless ::File.file? value
record.errors[attribute] << (options[:message] || "is not a valid path to a regular file")
end
end
end
end
end

58
config/application.rb Normal file
View File

@ -0,0 +1,58 @@
require 'rails'
require File.expand_path('../boot', __FILE__)
all_environments = [
:development,
:production,
:test
]
Bundler.require(
*Rails.groups(
db: all_environments,
pcap: all_environments
)
)
#
# Railties
#
# For compatibility with jquery-rails (and other engines that need action_view) in pro
require 'action_view/railtie'
#
# Project
#
require 'metasploit/framework/common_engine'
require 'msf/base/config'
module Metasploit
module Framework
class Application < Rails::Application
include Metasploit::Framework::CommonEngine
environment_database_yaml = ENV['MSF_DATABASE_CONFIG']
if environment_database_yaml
# DO NOT check if the path exists: if the environment variable is set, then the user meant to use this path
# and if it doesn't exist then an error should occur so the user knows the environment variable points to a
# non-existent file.
config.paths['config/database'] = environment_database_yaml
else
user_config_root = Pathname.new(Msf::Config.get_config_root)
user_database_yaml = user_config_root.join('database.yml')
# DO check if the path exists as in test environments there may be no config root, in which case the normal
# rails location, `config/database.yml`, should contain the database config.
if user_database_yaml.exist?
config.paths['config/database'] = [user_database_yaml.to_path]
end
end
end
end
end
# Silence warnings about this defaulting to true
I18n.enforce_available_locales = true

39
config/boot.rb Normal file
View File

@ -0,0 +1,39 @@
require 'pathname'
require 'rubygems'
GEMFILE_EXTENSIONS = [
'.local',
''
]
msfenv_real_pathname = Pathname.new(__FILE__).realpath
root = msfenv_real_pathname.parent.parent
unless ENV['BUNDLE_GEMFILE']
require 'pathname'
GEMFILE_EXTENSIONS.each do |extension|
extension_pathname = root.join("Gemfile#{extension}")
if extension_pathname.readable?
ENV['BUNDLE_GEMFILE'] = extension_pathname.to_path
break
end
end
end
begin
require 'bundler'
rescue LoadError
$stderr.puts "[*] Metasploit requires the Bundler gem to be installed"
$stderr.puts " $ gem install bundler"
exit(0)
end
Bundler.setup
lib_path = root.join('lib').to_path
unless $LOAD_PATH.include? lib_path
$LOAD_PATH.unshift lib_path
end

5
config/environment.rb Normal file
View File

@ -0,0 +1,5 @@
# Load the rails application
require File.expand_path('../application', __FILE__)
# Initialize the rails application
Metasploit::Framework::Application.initialize!

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -6,10 +6,10 @@
##
# General
##
define("TLV_TYPE_HANDLE", TLV_META_TYPE_UINT | 600);
define("TLV_TYPE_HANDLE", TLV_META_TYPE_QWORD | 600);
define("TLV_TYPE_INHERIT", TLV_META_TYPE_BOOL | 601);
define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_UINT | 630);
define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_UINT | 631);
define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_QWORD | 630);
define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_QWORD | 631);
##
# Fs
@ -65,7 +65,7 @@ define("PROCESS_EXECUTE_FLAG_SUSPENDED", (1 << 2));
define("PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN", (1 << 3));
# Registry
define("TLV_TYPE_HKEY", TLV_META_TYPE_UINT | 1000);
define("TLV_TYPE_HKEY", TLV_META_TYPE_QWORD | 1000);
define("TLV_TYPE_ROOT_KEY", TLV_TYPE_HKEY);
define("TLV_TYPE_BASE_KEY", TLV_META_TYPE_STRING | 1001);
define("TLV_TYPE_PERMISSION", TLV_META_TYPE_UINT | 1002);
@ -90,12 +90,12 @@ define("TLV_TYPE_ENV_GROUP", TLV_META_TYPE_GROUP | 1102);
define("DELETE_KEY_FLAG_RECURSIVE", (1 << 0));
# Process
define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_UINT | 2000);
define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2000);
define("TLV_TYPE_ALLOCATION_TYPE", TLV_META_TYPE_UINT | 2001);
define("TLV_TYPE_PROTECTION", TLV_META_TYPE_UINT | 2002);
define("TLV_TYPE_PROCESS_PERMS", TLV_META_TYPE_UINT | 2003);
define("TLV_TYPE_PROCESS_MEMORY", TLV_META_TYPE_RAW | 2004);
define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_UINT | 2005);
define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2005);
define("TLV_TYPE_MEMORY_STATE", TLV_META_TYPE_UINT | 2006);
define("TLV_TYPE_MEMORY_TYPE", TLV_META_TYPE_UINT | 2007);
define("TLV_TYPE_ALLOC_PROTECTION", TLV_META_TYPE_UINT | 2008);
@ -109,16 +109,16 @@ define("TLV_TYPE_PROCESS_ARGUMENTS", TLV_META_TYPE_STRING | 2305);
define("TLV_TYPE_IMAGE_FILE", TLV_META_TYPE_STRING | 2400);
define("TLV_TYPE_IMAGE_FILE_PATH", TLV_META_TYPE_STRING | 2401);
define("TLV_TYPE_PROCEDURE_NAME", TLV_META_TYPE_STRING | 2402);
define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_UINT | 2403);
define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_UINT | 2404);
define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_QWORD | 2403);
define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_QWORD | 2404);
define("TLV_TYPE_IMAGE_GROUP", TLV_META_TYPE_GROUP | 2405);
define("TLV_TYPE_IMAGE_NAME", TLV_META_TYPE_STRING | 2406);
define("TLV_TYPE_THREAD_ID", TLV_META_TYPE_UINT | 2500);
define("TLV_TYPE_THREAD_PERMS", TLV_META_TYPE_UINT | 2502);
define("TLV_TYPE_EXIT_CODE", TLV_META_TYPE_UINT | 2510);
define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_UINT | 2511);
define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_UINT | 2512);
define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_QWORD | 2511);
define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_QWORD | 2512);
define("TLV_TYPE_CREATION_FLAGS", TLV_META_TYPE_UINT | 2513);
define("TLV_TYPE_REGISTER_NAME", TLV_META_TYPE_STRING | 2540);
@ -137,7 +137,7 @@ define("TLV_TYPE_DESKTOP", TLV_META_TYPE_STRING | 3002);
# Event Log
##
define("TLV_TYPE_EVENT_SOURCENAME", TLV_META_TYPE_STRING | 4000);
define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_UINT | 4001);
define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_QWORD | 4001);
define("TLV_TYPE_EVENT_NUMRECORDS", TLV_META_TYPE_UINT | 4002);
define("TLV_TYPE_EVENT_READFLAGS", TLV_META_TYPE_UINT | 4003);

View File

@ -252,6 +252,7 @@ TLV_META_TYPE_STRING = (1 << 16)
TLV_META_TYPE_UINT = (1 << 17)
TLV_META_TYPE_RAW = (1 << 18)
TLV_META_TYPE_BOOL = (1 << 19)
TLV_META_TYPE_QWORD = (1 << 20)
TLV_META_TYPE_COMPRESSED = (1 << 29)
TLV_META_TYPE_GROUP = (1 << 30)
TLV_META_TYPE_COMPLEX = (1 << 31)
@ -284,10 +285,10 @@ TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54
##
# General
##
TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600
TLV_TYPE_HANDLE = TLV_META_TYPE_QWORD | 600
TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601
TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630
TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631
TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_QWORD | 630
TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_QWORD | 631
##
# Fs
@ -346,7 +347,7 @@ TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530
##
# Registry
##
TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000
TLV_TYPE_HKEY = TLV_META_TYPE_QWORD | 1000
TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY
TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001
TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002
@ -376,12 +377,12 @@ DELETE_KEY_FLAG_RECURSIVE = (1 << 0)
##
# Process
##
TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000
TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000
TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001
TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002
TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003
TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004
TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005
TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005
TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006
TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007
TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008
@ -397,16 +398,16 @@ TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307
TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400
TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401
TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402
TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403
TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404
TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_QWORD | 2403
TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_QWORD | 2404
TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405
TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406
TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500
TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502
TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510
TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511
TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512
TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_QWORD | 2511
TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_QWORD | 2512
TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513
TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540
@ -425,7 +426,7 @@ TLV_TYPE_DESKTOP = TLV_META_TYPE_STRING | 3002
# Event Log
##
TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000
TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001
TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_QWORD | 4001
TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002
TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003

View File

@ -125,6 +125,7 @@ define("TLV_META_TYPE_STRING", (1 << 16));
define("TLV_META_TYPE_UINT", (1 << 17));
define("TLV_META_TYPE_RAW", (1 << 18));
define("TLV_META_TYPE_BOOL", (1 << 19));
define("TLV_META_TYPE_QWORD", (1 << 20));
define("TLV_META_TYPE_COMPRESSED", (1 << 29));
define("TLV_META_TYPE_GROUP", (1 << 30));
define("TLV_META_TYPE_COMPLEX", (1 << 31));
@ -655,6 +656,11 @@ function tlv_pack($tlv) {
if (($tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING) {
$ret = pack("NNa*", 8 + strlen($tlv['value'])+1, $tlv['type'], $tlv['value'] . "\0");
}
elseif (($tlv['type'] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) {
$hi = ($tlv['value'] >> 32) & 0xFFFFFFFF;
$lo = $tlv['value'] & 0xFFFFFFFF;
$ret = pack("NNNN", 8 + 8, $tlv['type'], $hi, $lo);
}
elseif (($tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) {
$ret = pack("NNN", 8 + 4, $tlv['type'], $tlv['value']);
}
@ -693,6 +699,10 @@ function tlv_unpack($raw_tlv) {
elseif (($type & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) {
$tlv = unpack("Nlen/Ntype/Nvalue", substr($raw_tlv, 0, $tlv['len']));
}
elseif (($type & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) {
$tlv = unpack("Nlen/Ntype/Nhi/Nlo", substr($raw_tlv, 0, $tlv['len']));
$tlv['value'] = $tlv['hi'] << 32 | $tlv['lo'];
}
elseif (($type & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL) {
$tlv = unpack("Nlen/Ntype/cvalue", substr($raw_tlv, 0, $tlv['len']));
}

View File

@ -54,6 +54,7 @@ TLV_META_TYPE_STRING = (1 << 16)
TLV_META_TYPE_UINT = (1 << 17)
TLV_META_TYPE_RAW = (1 << 18)
TLV_META_TYPE_BOOL = (1 << 19)
TLV_META_TYPE_QWORD = (1 << 20)
TLV_META_TYPE_COMPRESSED = (1 << 29)
TLV_META_TYPE_GROUP = (1 << 30)
TLV_META_TYPE_COMPLEX = (1 << 31)
@ -150,6 +151,8 @@ def packet_enum_tlvs(pkt, tlv_type = None):
val = str(val.split(NULL_BYTE, 1)[0])
elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT:
val = struct.unpack('>I', val)[0]
elif (tlv[1] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD:
val = struct.unpack('>Q', val)[0]
elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL:
val = bool(struct.unpack('b', val)[0])
elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW:
@ -175,6 +178,8 @@ def tlv_pack(*args):
data = ""
if (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT:
data = struct.pack('>III', 12, tlv['type'], tlv['value'])
elif (tlv['type'] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD:
data = struct.pack('>IIQ', 16, tlv['type'], tlv['value'])
elif (tlv['type'] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL:
data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8')
else:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -13,7 +13,7 @@
require 'rubygems'
require 'pathname'
require 'hpricot'
require 'nokogiri'
require 'uri'
class CrawlerSimple < BaseParser
@ -24,17 +24,14 @@ class CrawlerSimple < BaseParser
return
end
doc = Hpricot(result.body.to_s)
doc.search('a').each do |link|
hr = link.attributes['href']
if hr and !hr.match(/^(\#|javascript\:)/)
# doc = Hpricot(result.body.to_s)
doc = Nokogiri::HTML(result.body.to_s)
doc.css('a').each do |anchor_tag|
hr = anchor_tag['href']
if hr && !hr.match(/^(\#|javascript\:)/)
begin
hreq = urltohash('GET', hr, request['uri'], nil)
insertnewpath(hreq)
rescue URI::InvalidURIError
#puts "Parse error"
#puts "Error: #{link[0]}"

View File

@ -13,7 +13,7 @@
require 'rubygems'
require 'pathname'
require 'hpricot'
require 'nokogiri'
require 'uri'
class CrawlerForms < BaseParser
@ -27,49 +27,30 @@ class CrawlerForms < BaseParser
hr = ''
m = ''
doc = Hpricot(result.body.to_s)
doc.search('form').each do |f|
hr = f.attributes['action']
doc = Nokogiri::HTML(result.body.to_s)
doc.css('form').each do |f|
hr = f['action']
fname = f.attributes['name']
if fname.empty?
fname = "NONE"
end
fname = f['name']
fname = "NONE" if fname.empty?
m = "GET"
if !f.attributes['method'].empty?
m = f.attributes['method'].upcase
end
m = f['method'].empty? ? 'GET' : f['method'].upcase
#puts "Parsing form name: #{fname} (#{m})"
htmlform = Hpricot(f.inner_html)
htmlform = Nokogiri::HTML(f.inner_html)
arrdata = []
htmlform.search('input').each do |p|
#puts p.attributes['name']
#puts p.attributes['type']
#puts p.attributes['value']
#raw_request has uri_encoding disabled as it encodes '='.
arrdata << (p.attributes['name'] + "=" + Rex::Text.uri_encode(p.attributes['value']))
htmlform.css('input').each do |p|
arrdata << "#{p['name']}=#{Rex::Text.uri_encode(p['value'])}"
end
data = arrdata.join("&").to_s
begin
hreq = urltohash(m, hr, request['uri'], data)
hreq['ctype'] = 'application/x-www-form-urlencoded'
insertnewpath(hreq)
rescue URI::InvalidURIError
#puts "Parse error"
#puts "Error: #{link[0]}"
end
end
end

View File

@ -9,33 +9,29 @@
require 'rubygems'
require 'pathname'
require 'hpricot'
require 'nokogiri'
require 'uri'
class CrawlerFrames < BaseParser
def parse(request,result)
if !result['Content-Type'].include? "text/html"
return
end
return unless result['Content-Type'].include?('text/html')
doc = Hpricot(result.body.to_s)
doc.search('iframe').each do |ifra|
doc = Nokogiri::HTML(result.body.to_s)
doc.css('iframe').each do |ifra|
ir = ifra['src']
ir = ifra.attributes['src']
if ir and !ir.match(/^(\#|javascript\:)/)
if ir && !ir.match(/^(\#|javascript\:)/)
begin
hreq = urltohash('GET', ir, request['uri'], nil)
insertnewpath(hreq)
rescue URI::InvalidURIError
#puts "Error"
end
end
end
end
end
end
end
end

View File

@ -10,33 +10,26 @@
require 'rubygems'
require 'pathname'
require 'hpricot'
require 'nokogiri'
require 'uri'
class CrawlerImage < BaseParser
def parse(request,result)
if !result['Content-Type'].include? "text/html"
return
end
return unless result['Content-Type'].include?('text/html')
doc = Hpricot(result.body.to_s)
doc.search('img').each do |i|
im = i.attributes['src']
if im and !im.match(/^(\#|javascript\:)/)
doc = Nokogiri::HTML(result.body.to_s)
doc.css('img').each do |i|
im = i['src']
if im && !im.match(/^(\#|javascript\:)/)
begin
hreq = urltohash('GET', im, request['uri'], nil)
insertnewpath(hreq)
rescue URI::InvalidURIError
#puts "Parse error"
#puts "Error: #{i[0]}"
end
end
end
end
end

View File

@ -10,33 +10,25 @@
require 'rubygems'
require 'pathname'
require 'hpricot'
require 'nokogiri'
require 'uri'
class CrawlerLink < BaseParser
def parse(request,result)
return unless result['Content-Type'].include?('text/html')
if !result['Content-Type'].include? "text/html"
return
end
doc = Hpricot(result.body.to_s)
doc.search('link').each do |link|
hr = link.attributes['href']
if hr and !hr.match(/^(\#|javascript\:)/)
doc = Nokogiri::HTML(result.body.to_s)
doc.css('link').each do |link|
hr = link['href']
if hr && !hr.match(/^(\#|javascript\:)/)
begin
hreq = urltohash('GET', hr, request['uri'], nil)
insertnewpath(hreq)
rescue URI::InvalidURIError
#puts "Parse error"
#puts "Error: #{link[0]}"
end
end
end
end
end

View File

@ -13,36 +13,25 @@
require 'rubygems'
require 'pathname'
require 'hpricot'
require 'nokogiri'
require 'uri'
class CrawlerObjects < BaseParser
def parse(request,result)
if !result['Content-Type'].include? "text/html"
return
end
return unless result['Content-Type'].include?('text/html') # TOOD: use MIXIN
hr = ''
m = ''
doc = Hpricot(result.body.to_s)
doc.search("//object/embed").each do |obj|
doc = Nokogiri::HTML(result.body.to_s)
doc.xpath("//object/embed").each do |obj|
s = obj['src']
begin
hreq = urltohash('GET', s, request['uri'], nil)
insertnewpath(hreq)
rescue URI::InvalidURIError
#puts "Parse error"
#puts "Error: #{link[0]}"
end
end
end
end
end

View File

@ -13,36 +13,27 @@
require 'rubygems'
require 'pathname'
require 'hpricot'
require 'nokogiri'
require 'uri'
class CrawlerScripts < BaseParser
def parse(request,result)
if !result['Content-Type'].include? "text/html"
return
end
return unless result['Content-Type'].include? "text/html"
hr = ''
m = ''
doc = Hpricot(result.body.to_s)
doc.search("//script").each do |obj|
doc = Nokogiri::HTML(result.body.to_s)
doc.xpath("//script").each do |obj|
s = obj['src']
begin
hreq = urltohash('GET', s, request['uri'], nil)
insertnewpath(hreq)
rescue URI::InvalidURIError
#puts "Parse error"
#puts "Error: #{link[0]}"
end
end
end
end
end
end

68
data/php/hop.php Normal file
View File

@ -0,0 +1,68 @@
<?php
$magic = 'TzGq';
$tempdir = sys_get_temp_dir() . "/hop" . $magic;
if(!is_dir($tempdir)){
mkdir($tempdir); //make sure it's there
}
//get url
$url = $_SERVER["QUERY_STRING"];
//like /path/hop.php?/uRIcksm_lOnGidENTifIEr
//Looks for a file with a name or contents prefix, if found, send it and deletes it
function findSendDelete($tempdir, $prefix, $one=true){
if($dh = opendir($tempdir)){
while(($file = readdir($dh)) !== false){
if(strpos($file, $prefix) !== 0){
continue;
}
readfile($tempdir."/".$file);
unlink($tempdir."/".$file);
if($one){
break;
}
}
}
}
//handle control
if($url === "/control"){
if($_SERVER['REQUEST_METHOD'] === 'POST'){
//handle data for payload - save in a "down" file or the "init" file
$postdata = file_get_contents("php://input");
if(array_key_exists('HTTP_X_INIT', $_SERVER)){
$f = fopen($tempdir."/init", "w"); //only one init file
}else{
$prefix = "down_" . bin2hex($_SERVER['HTTP_X_URLFRAG']);
$f = fopen(tempnam($tempdir,$prefix), "w");
}
fwrite($f, $postdata);
fclose($f);
}else{
findSendDelete($tempdir, "up_", false);
}
}else if($_SERVER['REQUEST_METHOD'] === 'POST'){
//get data
$postdata = file_get_contents("php://input");
//See if we should send anything down
if($postdata === 'RECV'){
findSendDelete($tempdir, "down_" . bin2hex($url));
$fname = $tempdir . "/up_recv_" . bin2hex($url); //Only keep one RECV poll
}else{
$fname = tempnam($tempdir, "up_"); //actual data gets its own filename
}
//find free and write new file
$f = fopen($fname, "w");
fwrite($f, $magic);
//Little-endian pack length and data
$urlen = strlen($url);
fwrite($f, pack('V', $urlen));
fwrite($f, $url);
$postdatalen = strlen($postdata);
fwrite($f, pack('V', $postdatalen));
fwrite($f, $postdata);
fclose($f);
//Initial query will be a GET and have a 12345 in it
}else if(strpos($url, "12345") !== FALSE){
readfile($tempdir."/init");
}

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130717150737) do
ActiveRecord::Schema.define(:version => 20140801150537) do
create_table "api_keys", :force => true do |t|
t.text "token"
@ -28,6 +28,16 @@ ActiveRecord::Schema.define(:version => 20130717150737) do
t.datetime "updated_at"
end
create_table "credential_cores_tasks", :id => false, :force => true do |t|
t.integer "core_id"
t.integer "task_id"
end
create_table "credential_logins_tasks", :id => false, :force => true do |t|
t.integer "login_id"
t.integer "task_id"
end
create_table "creds", :force => true do |t|
t.integer "service_id", :null => false
t.datetime "created_at", :null => false
@ -167,6 +177,113 @@ ActiveRecord::Schema.define(:version => 20130717150737) do
t.binary "prefs"
end
create_table "metasploit_credential_cores", :force => true do |t|
t.integer "origin_id", :null => false
t.string "origin_type", :null => false
t.integer "private_id"
t.integer "public_id"
t.integer "realm_id"
t.integer "workspace_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "logins_count", :default => 0
end
add_index "metasploit_credential_cores", ["origin_type", "origin_id"], :name => "index_metasploit_credential_cores_on_origin_type_and_origin_id"
add_index "metasploit_credential_cores", ["private_id"], :name => "index_metasploit_credential_cores_on_private_id"
add_index "metasploit_credential_cores", ["public_id"], :name => "index_metasploit_credential_cores_on_public_id"
add_index "metasploit_credential_cores", ["realm_id"], :name => "index_metasploit_credential_cores_on_realm_id"
add_index "metasploit_credential_cores", ["workspace_id", "private_id"], :name => "unique_private_metasploit_credential_cores", :unique => true
add_index "metasploit_credential_cores", ["workspace_id", "public_id", "private_id"], :name => "unique_realmless_metasploit_credential_cores", :unique => true
add_index "metasploit_credential_cores", ["workspace_id", "public_id"], :name => "unique_public_metasploit_credential_cores", :unique => true
add_index "metasploit_credential_cores", ["workspace_id", "realm_id", "private_id"], :name => "unique_publicless_metasploit_credential_cores", :unique => true
add_index "metasploit_credential_cores", ["workspace_id", "realm_id", "public_id", "private_id"], :name => "unique_complete_metasploit_credential_cores", :unique => true
add_index "metasploit_credential_cores", ["workspace_id", "realm_id", "public_id"], :name => "unique_privateless_metasploit_credential_cores", :unique => true
add_index "metasploit_credential_cores", ["workspace_id"], :name => "index_metasploit_credential_cores_on_workspace_id"
create_table "metasploit_credential_logins", :force => true do |t|
t.integer "core_id", :null => false
t.integer "service_id", :null => false
t.string "access_level"
t.string "status", :null => false
t.datetime "last_attempted_at"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_logins", ["core_id", "service_id"], :name => "index_metasploit_credential_logins_on_core_id_and_service_id", :unique => true
add_index "metasploit_credential_logins", ["service_id", "core_id"], :name => "index_metasploit_credential_logins_on_service_id_and_core_id", :unique => true
create_table "metasploit_credential_origin_cracked_passwords", :force => true do |t|
t.integer "metasploit_credential_core_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_origin_cracked_passwords", ["metasploit_credential_core_id"], :name => "originating_credential_cores"
create_table "metasploit_credential_origin_imports", :force => true do |t|
t.text "filename", :null => false
t.integer "task_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_origin_imports", ["task_id"], :name => "index_metasploit_credential_origin_imports_on_task_id"
create_table "metasploit_credential_origin_manuals", :force => true do |t|
t.integer "user_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_origin_manuals", ["user_id"], :name => "index_metasploit_credential_origin_manuals_on_user_id"
create_table "metasploit_credential_origin_services", :force => true do |t|
t.integer "service_id", :null => false
t.text "module_full_name", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_origin_services", ["service_id", "module_full_name"], :name => "unique_metasploit_credential_origin_services", :unique => true
create_table "metasploit_credential_origin_sessions", :force => true do |t|
t.text "post_reference_name", :null => false
t.integer "session_id", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_origin_sessions", ["session_id", "post_reference_name"], :name => "unique_metasploit_credential_origin_sessions", :unique => true
create_table "metasploit_credential_privates", :force => true do |t|
t.string "type", :null => false
t.text "data", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "jtr_format"
end
add_index "metasploit_credential_privates", ["type", "data"], :name => "index_metasploit_credential_privates_on_type_and_data", :unique => true
create_table "metasploit_credential_publics", :force => true do |t|
t.string "username", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_publics", ["username"], :name => "index_metasploit_credential_publics_on_username", :unique => true
create_table "metasploit_credential_realms", :force => true do |t|
t.string "key", :null => false
t.string "value", :null => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "metasploit_credential_realms", ["key", "value"], :name => "index_metasploit_credential_realms_on_key_and_value", :unique => true
create_table "mod_refs", :force => true do |t|
t.string "module", :limit => 1024
t.string "mtype", :limit => 128

View File

@ -7,7 +7,7 @@ CLASSES = Exploit.java
all: $(CLASSES:.java=.class)
install:
mv *.class ../../../../data/exploits/CVE-2013-3465/
mv *.class ../../../../data/exploits/CVE-2013-2465/
clean:
rm -rf *.class

View File

@ -37,7 +37,7 @@ class BitStruct
old_writer = "#{attr_chars}="
define_method "#{attr}=" do |val|
data = val.split(sep).map{|s|s.to_i(base)}.pack("c*")
data = val.split(sep).map{|s|s.to_i(base)}.pack("C*")
send(old_writer, data)
end
end

View File

@ -378,6 +378,10 @@ module Kernel #:nodoc:all
# This method handles the loading of FASTLIB archives
#
def fastlib_require(name)
if name.respond_to? :to_path
name = name.to_path
end
name = name + ".rb" if not name =~ /\.rb$/
return false if fastlib_already_loaded?(name)
return false if fastlib_already_tried?(name)

View File

@ -1,3 +1,29 @@
#
# Gems
#
# gems must load explicitly any gem declared in gemspec
# @see https://github.com/bundler/bundler/issues/2018#issuecomment-6819359
#
require 'active_support'
require 'bcrypt'
require 'json'
require 'msgpack'
require 'metasploit/model'
require 'nokogiri'
require 'packetfu'
# railties has not autorequire defined
# rkelly-remix is a fork of rkelly, so it's autorequire is 'rkelly' and not 'rkelly-remix'
require 'rkelly'
require 'robots'
require 'zip'
#
# Project
#
require 'msf/core'
# Top-level namespace that is shared between {Metasploit::Framework
# metasploit-framework} and pro, which uses Metasploit::Pro.
module Metasploit
@ -5,30 +31,6 @@ module Metasploit
# works in compatible manner with activerecord's rake tasks and other
# railties.
module Framework
# Returns the environment for {Metasploit::Framework}. Checks
# `METASPLOIT_FRAMEWORK_ENV` environment variable for value. Defaults to
# `'development'` if `METASPLOIT_FRAMEWORK_ENV` is not set in the
# environment variables.
#
# {env} is a ActiveSupport::StringInquirer like `Rails.env` so it can be
# queried for its value.
#
# @example check if environment is development
# if Metasploit::Framework.env.development?
# # runs only when in development
# end
#
# @return [ActiveSupport::StringInquirer] the environment name
def self.env
unless instance_variable_defined? :@env
name = ENV['METASPLOIT_FRAMEWORK_ENV']
name ||= 'development'
@env = ActiveSupport::StringInquirer.new(name)
end
@env
end
# Returns the root of the metasploit-framework project. Use in place of
# `Rails.root`.
#

View File

@ -0,0 +1,323 @@
# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/exploit/tcp'
module Metasploit
module Framework
module AFP
module Client
def next_id
@request_id ||= -1
@request_id += 1
@request_id
end
def get_info
packet = "\00" # Flag: Request
packet << "\x03" # Command: FPGetSrvrInfo
packet << [next_id].pack('n') # requestID
packet << "\x00\x00\x00\x00" # Data offset
packet << "\x00\x00\x00\x00" # Length
packet << "\x00\x00\x00\x00" # Reserved
sock.put(packet)
response = sock.timed_read(1024)
return parse_info_response(response)
end
def open_session
packet = "\00" # Flag: Request
packet << "\x04" # Command: DSIOpenSession
packet << [next_id].pack('n') # requestID
packet << "\x00\x00\x00\x00" # Data offset
packet << "\x00\x00\x00\x06" # Length
packet << "\x00\x00\x00\x00" # Reserved
packet << "\x01" # Attention Quantum
packet << "\x04" # Length
packet << "\x00\x00\x04\x00" # 1024
sock.put(packet)
response = sock.timed_read(1024)
return parse_open_session_response(response)
end
def login(user, pass)
if user == ''
return no_user_authent_login
end
p = OpenSSL::BN.new("BA2873DFB06057D43F2024744CEEE75B", 16)
g = OpenSSL::BN.new("7", 10)
ra = OpenSSL::BN.new('86F6D3C0B0D63E4B11F113A2F9F19E3BBBF803F28D30087A1450536BE979FD42', 16)
ma = g.mod_exp(ra, p)
padded_user = (user.length + 1) % 2 != 0 ? user + "\x00" : user
bin_user = [padded_user.length, padded_user].pack("Ca*")
length = 18 + bin_user.length + ma.to_s(2).length
packet = "\00" # Flag: Request
packet << "\x02" # Command: DSICommand
packet << [next_id].pack('n') # requestID
packet << "\x00\x00\x00\x00" # Data offset
packet << [length].pack('N') # Length (42)
packet << "\x00\x00\x00\x00" # Reserved
packet << "\x12" # AFPCommand: FPLogin (18)
packet << "\x06\x41\x46\x50\x33\x2e\x31" # AFPVersion: AFP3.1
packet << "\x09\x44\x48\x43\x41\x53\x54\x31\x32\x38" #UAM: DHCAST128
packet << bin_user # username
packet << ma.to_s(2) # random number
sock.put(packet)
begin
response = sock.timed_read(1024, self.login_timeout)
rescue Timeout::Error
#vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)")
return :connection_error
end
flags, command, request_id, error_code, length, reserved = parse_header(response)
case error_code
when -5001 #kFPAuthContinue
return parse_login_response_add_send_login_count(response, {:p => p, :g => g, :ra => ra, :ma => ma,
:password => pass, :user => user})
when -5023 #kFPUserNotAuth (User dosen't exists)
#print_status("AFP #{rhost}:#{rport} User #{user} dosen't exists")
return :skip_user
else
return :connection_error
end
end
def close_session
packet = "\00" # Flag: Request
packet << "\x01" # Command: DSICloseSession
packet << [next_id].pack('n') # requestID
packet << "\x00\x00\x00\x00" #Data offset
packet << "\x00\x00\x00\x00" #Length
packet << "\x00\x00\x00\x00" #Reserved
sock.put(packet)
end
def no_user_authent_login
packet = "\00" # Flag: Request
packet << "\x02" # Command: DSICommand
packet << [next_id].pack('n') # requestID
packet << "\x00\x00\x00\x00" # Data offset
packet << "\x00\x00\x00\x18" # Length (24)
packet << "\x00\x00\x00\x00" # Reserved
packet << "\x12" # AFPCommand: FPLogin (18)
packet << "\x06\x41\x46\x50\x33\x2e\x31" #AFP3.1
packet << "\x0f\x4e\x6f\x20\x55\x73\x65\x72\x20\x41\x75\x74\x68\x65\x6e\x74" #UAM: No User Authent
sock.put(packet)
begin
response = sock.timed_read(1024, self.login_timeout)
rescue Timeout::Error
vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)")
return :connection_error
end
flags, command, request_id, error_code, length, reserved = parse_header(response)
if error_code == 0
return :true
else
return false
end
end
def parse_login_response_add_send_login_count(response, data)
dhx_s2civ = 'CJalbert'
dhx_c2civ = 'LWallace'
flags, command, request_id, error_code, length, reserved = parse_header(response)
body = get_body(response, length)
id, mb, enc_data = body.unpack("nH32a*")
mb = OpenSSL::BN.new(mb, 16)
k = mb.mod_exp(data[:ra], data[:p] )
cipher = OpenSSL::Cipher.new('cast5-cbc').decrypt
cipher.key = k.to_s(2)
cipher.iv = dhx_s2civ
cipher.padding = 0
nonce = cipher.update(enc_data)
nonce << cipher.final
nonce = nonce[0..15]
nonce = OpenSSL::BN.new(nonce, 2) + 1
plain_text = nonce.to_s(2) + data[:password].ljust(64, "\x00")
cipher = OpenSSL::Cipher.new('cast5-cbc').encrypt
cipher.key = k.to_s(2)
cipher.iv = dhx_c2civ
auth_response = cipher.update(plain_text)
auth_response << cipher.final
packet = "\00" # Flag: Request
packet << "\x02" # Command: DSICommand
packet << [next_id].pack('n') # requestID
packet << "\x00\x00\x00\x00" # Data offset
packet << [auth_response.length + 2].pack("N") # Length
packet << "\x00\x00\x00\x00" # Reserved
packet << "\x13" # AFPCommand: FPLoginCont (19)
packet << "\x00"
packet << [id].pack('n')
packet << auth_response
sock.put(packet)
begin
response = sock.timed_read(1024, self.login_timeout)
rescue Timeout::Error
vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)")
return :connection_error
end
flags, command, request_id, error_code, length, reserved = parse_header(response)
if error_code == 0
return true
else
return false
end
end
def parse_open_session_response(response)
_, _, _, error_code, _, _ = parse_header(response)
return error_code == 0 ? true : false
end
def parse_info_response(response)
parsed_data = {}
flags, command, request_id, error_code, length, reserved = parse_header(response)
raise "AFP #{rhost}:#{rport} Server response with error" if error_code != 0
body = get_body(response, length)
machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags =
body.unpack('nnnnn')
server_name_length = body.unpack('@10C').first
parsed_data[:server_name] = body.unpack("@11A#{server_name_length}").first
pos = 11 + server_name_length
pos += 1 if pos % 2 != 0 #padding
server_signature_offset, network_addresses_offset, directory_names_offset,
utf8_servername_offset = body.unpack("@#{pos}nnnn")
parsed_data[:machine_type] = read_pascal_string(body, machine_type_offset)
parsed_data[:versions] = read_array(body, afp_versions_offset)
parsed_data[:uams] = read_array(body, uam_count_offset)
# skiped icon
parsed_data[:server_flags] = parse_flags(server_flags)
parsed_data[:signature] = body.unpack("@#{server_signature_offset}H32").first
network_addresses = read_array(body, network_addresses_offset, true)
parsed_data[:network_addresses] = parse_network_addresses(network_addresses)
# skiped directory names
#Error catching for offset issues on this field. Need better error ahndling all through here
begin
parsed_data[:utf8_server_name] = read_utf8_pascal_string(body, utf8_servername_offset)
rescue
parsed_data[:utf8_server_name] = "N/A"
end
return parsed_data
end
def parse_header(packet)
header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order
header[3] = packet[4..7].reverse.unpack("l").first
return header
end
def get_body(packet, body_length)
body = packet[16..body_length + 15]
raise "AFP #{rhost}:#{rport} Invalid body length" if body.length != body_length
return body
end
def read_pascal_string(str, offset)
length = str.unpack("@#{offset}C").first
return str.unpack("@#{offset + 1}A#{length}").first
end
def read_utf8_pascal_string(str, offset)
length = str.unpack("@#{offset}n").first
return str[offset + 2..offset + length + 1]
end
def read_array(str, offset, afp_network_address=false)
size = str.unpack("@#{offset}C").first
pos = offset + 1
result = []
size.times do
result << read_pascal_string(str, pos)
pos += str.unpack("@#{pos}C").first
pos += 1 unless afp_network_address
end
return result
end
def parse_network_addresses(network_addresses)
parsed_addreses = []
network_addresses.each do |address|
case address.unpack('C').first
when 0 #Reserved
next
when 1 # Four-byte IP address
parsed_addreses << IPAddr.ntop(address[1..4]).to_s
when 2 # Four-byte IP address followed by a two-byte port number
parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}"
when 3 # DDP address (depricated)
next
when 4 # DNS name (maximum of 254 bytes)
parsed_addreses << address[1..address.length - 1]
when 5 # This functionality is deprecated.
next
when 6 # IPv6 address (16 bytes)
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]"
when 7 # IPv6 address (16 bytes) followed by a two-byte port number
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}"
else # Something wrong?
raise "Error parsing network addresses"
end
end
return parsed_addreses
end
def parse_flags(flags)
flags = flags.to_s(2)
result = {}
result['Super Client'] = flags[0,1] == '1' ? true : false
result['UUIDs'] = flags[5,1] == '1' ? true : false
result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false
result['Open Directory'] = flags[7,1] == '1' ? true : false
result['Reconnect'] = flags[8,1] == '1' ? true : false
result['Server Notifications'] = flags[9,1] == '1' ? true : false
result['TCP/IP'] = flags[10,1] == '1' ? true : false
result['Server Signature'] = flags[11,1] == '1' ? true : false
result['Server Messages'] = flags[12,1] == '1' ? true : false
result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false
result['Password Changing'] = flags[14,1] == '1' ? true : false
result['Copy File'] = flags[5,1] == '1' ? true : false
return result
end
end
end
end
end

View File

@ -0,0 +1,7 @@
module Metasploit
module Framework
module API
end
end
end

View File

@ -0,0 +1,16 @@
module Metasploit
module Framework
module API
# @note This is a like. The API version is not semantically version and it's version has actually never changed
# even though API changes have occured. DO NOT base compatibility on this version.
module Version
MAJOR = 1
MINOR = 0
PATCH = 0
end
VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::PATCH}"
GEM_VERSION = Gem::Version.new(VERSION)
end
end
end

View File

@ -0,0 +1,26 @@
#
# Gems
#
# have to be exact so minimum is loaded prior to parsing arguments which could
# influence loading.
require 'active_support/dependencies/autoload'
# @note Must use the nested declaration of the
# {Metasploit::Framework::Command} namespace because commands need to be able
# to be required directly without any other part of metasploit-framework
# besides config/boot so that the commands can parse arguments, setup
# RAILS_ENV, and load config/application.rb correctly.
module Metasploit
module Framework
module Command
# Namespace for commands for metasploit-framework. There are
# corresponding classes in the {Metasploit::Framework::ParsedOptions}
# namespace, which handle for parsing the options for each command.
extend ActiveSupport::Autoload
autoload :Base
autoload :Console
end
end
end

View File

@ -0,0 +1,113 @@
#
# Gems
#
require 'active_support/core_ext/module/introspection'
#
# Project
#
require 'metasploit/framework/command'
require 'metasploit/framework/parsed_options'
require 'metasploit/framework/require'
# Based on pattern used for lib/rails/commands in the railties gem.
class Metasploit::Framework::Command::Base
#
# Attributes
#
# @!attribute [r] application
# The Rails application for metasploit-framework.
#
# @return [Metasploit::Framework::Application]
attr_reader :application
# @!attribute [r] parsed_options
# The parsed options from the command line.
#
# @return (see parsed_options)
attr_reader :parsed_options
#
# Class Methods
#
# @note {require_environment!} should be called to load
# `config/application.rb` to so that the RAILS_ENV can be set from the
# command line options in `ARGV` prior to `Rails.env` being set.
# @note After returning, `Rails.application` will be defined and configured.
#
# Parses `ARGV` for command line arguments to configure the
# `Rails.application`.
#
# @return (see parsed_options)
def self.require_environment!
parsed_options = self.parsed_options
# RAILS_ENV must be set before requiring 'config/application.rb'
parsed_options.environment!
ARGV.replace(parsed_options.positional)
# allow other Rails::Applications to use this command
if !defined?(Rails) || Rails.application.nil?
# @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40
require Pathname.new(__FILE__).parent.parent.parent.parent.parent.join('config', 'application')
end
# have to configure before requiring environment because
# config/environment.rb calls initialize! and the initializers will use
# the configuration from the parsed options.
parsed_options.configure(Rails.application)
# support disabling the database
unless parsed_options.options.database.disable
Metasploit::Framework::Require.optionally_active_record_railtie
end
Rails.application.require_environment!
parsed_options
end
def self.parsed_options
parsed_options_class.new
end
def self.parsed_options_class
@parsed_options_class ||= parsed_options_class_name.constantize
end
def self.parsed_options_class_name
@parsed_options_class_name ||= "#{parent.parent}::ParsedOptions::#{name.demodulize}"
end
def self.start
parsed_options = require_environment!
new(application: Rails.application, parsed_options: parsed_options).start
end
#
# Instance Methods
#
# @param attributes [Hash{Symbol => ActiveSupport::OrderedOptions,Rails::Application}]
# @option attributes [Rails::Application] :application
# @option attributes [ActiveSupport::OrderedOptions] :parsed_options
# @raise [KeyError] if :application is not given
# @raise [KeyError] if :parsed_options is not given
def initialize(attributes={})
@application = attributes.fetch(:application)
@parsed_options = attributes.fetch(:parsed_options)
end
# @abstract Use {#application} to start this command.
#
# Starts this command.
#
# @return [void]
# @raise [NotImplementedError]
def start
raise NotImplementedError
end
end

View File

@ -0,0 +1,64 @@
#
# Project
#
require 'metasploit/framework/command'
require 'metasploit/framework/command/base'
# Based on pattern used for lib/rails/commands in the railties gem.
class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base
def start
case parsed_options.options.subcommand
when :version
$stderr.puts "Framework Version: #{Metasploit::Framework::VERSION}"
else
driver.run
end
end
private
# The console UI driver.
#
# @return [Msf::Ui::Console::Driver]
def driver
unless @driver
# require here so minimum loading is done before {start} is called.
require 'msf/ui'
@driver = Msf::Ui::Console::Driver.new(
Msf::Ui::Console::Driver::DefaultPrompt,
Msf::Ui::Console::Driver::DefaultPromptChar,
driver_options
)
end
@driver
end
def driver_options
unless @driver_options
options = parsed_options.options
driver_options = {}
driver_options['Config'] = options.framework.config
driver_options['ConfirmExit'] = options.console.confirm_exit
driver_options['DatabaseEnv'] = options.environment
driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths
driver_options['DatabaseYAML'] = options.database.config
driver_options['Defanged'] = options.console.defanged
driver_options['DisableBanner'] = options.console.quiet
driver_options['DisableDatabase'] = options.database.disable
driver_options['LocalOutput'] = options.console.local_output
driver_options['ModulePath'] = options.modules.path
driver_options['Plugins'] = options.console.plugins
driver_options['RealReadline'] = options.console.real_readline
driver_options['Resource'] = options.console.resources
driver_options['XCommands'] = options.console.commands
@driver_options = driver_options
end
@driver_options
end
end

View File

@ -0,0 +1,76 @@
#
# Standard Library
#
require 'fileutils'
# `Rails::Engine` behavior common to both {Metasploit::Framework::Application} and {Metasploit::Framework::Engine}.
module Metasploit::Framework::CommonEngine
extend ActiveSupport::Concern
included do
#
# config
#
# Force binary encoding to remove necessity to set external and internal encoding when construct Strings from
# from files. Socket#read always returns a String in ASCII-8bit encoding
#
# @see http://rubydoc.info/stdlib/core/IO:read
config.before_initialize do
encoding = 'binary'
Encoding.default_external = encoding
Encoding.default_internal = encoding
end
config.root = Msf::Config::install_root
config.paths.add 'data/meterpreter', glob: '**/ext_*'
config.paths.add 'modules'
config.active_support.deprecation = :notify
#
# `initializer`s
#
initializer 'metasploit_framework.merge_meterpreter_extensions' do
Rails.application.railties.engines.each do |engine|
merge_meterpreter_extensions(engine)
end
# The Rails.application itself could have paths['data/meterpreter'], but will not be part of
# Rails.application.railties.engines because only direct subclasses of `Rails::Engine` are returned.
merge_meterpreter_extensions(Rails.application)
end
end
#
# Instance Methods
#
private
# Merges the meterpreter extensions from `engine`'s `paths['data/meterpreter]`.
#
# @param engine [Rails::Engine] a Rails engine or application that has meterpreter extensions
# @return [void]
# @todo Make metasploit-framework look for meterpreter extension in paths['data/meterpreter'] from the engine instead of copying them.
def merge_meterpreter_extensions(engine)
data_meterpreter_paths = engine.paths['data/meterpreter']
# may be `nil` since 'data/meterpreter' is not part of the core Rails::Engine paths set.
if data_meterpreter_paths
source_paths = data_meterpreter_paths.existent
destination_directory = root.join('data', 'meterpreter').to_path
source_paths.each do |source_path|
basename = File.basename(source_path)
destination_path = File.join(destination_directory, basename)
unless destination_path == source_path
FileUtils.copy(source_path, destination_directory)
end
end
end
end
end

View File

@ -0,0 +1,74 @@
require 'metasploit/framework/credential'
module Metasploit
module Framework
# This class is responsible for taking datastore options from the snmp_login module
# and yielding appropriate {Metasploit::Framework::Credential}s to the {Metasploit::Framework::LoginScanner::SNMP}.
# This one has to be different from {credentialCollection} as it will only have a {Metasploit::Framework::Credential#public}
# It may be slightly confusing that the attribues are called password and pass_file, because this is what the legacy
# module used. However, community Strings are now considered more to be public credentials than private ones.
class CommunityStringCollection
# @!attribute pass_file
# Path to a file containing passwords, one per line
# @return [String]
attr_accessor :pass_file
# @!attribute password
# @return [String]
attr_accessor :password
# @!attribute prepended_creds
# List of credentials to be tried before any others
#
# @see #prepend_cred
# @return [Array<Credential>]
attr_accessor :prepended_creds
# @option opts [String] :pass_file See {#pass_file}
# @option opts [String] :password See {#password}
# @option opts [Array<Credential>] :prepended_creds ([]) See {#prepended_creds}
def initialize(opts = {})
opts.each do |attribute, value|
public_send("#{attribute}=", value)
end
self.prepended_creds ||= []
end
# Combines all the provided credential sources into a stream of {Credential}
# objects, yielding them one at a time
#
# @yieldparam credential [Metasploit::Framework::Credential]
# @return [void]
def each
begin
if pass_file.present?
pass_fd = File.open(pass_file, 'r:binary')
pass_fd.each_line do |line|
line.chomp!
yield Metasploit::Framework::Credential.new(public: line, paired: false)
end
end
if password.present?
yield Metasploit::Framework::Credential.new(public: password, paired: false)
end
ensure
pass_fd.close if pass_fd && !pass_fd.closed?
end
end
# Add {Credential credentials} that will be yielded by {#each}
#
# @see prepended_creds
# @param cred [Credential]
# @return [self]
def prepend_cred(cred)
prepended_creds.unshift cred
self
end
end
end
end

View File

@ -0,0 +1,7 @@
module Metasploit
module Framework
module Core
end
end
end

View File

@ -0,0 +1,19 @@
require 'metasploit/framework/version'
module Metasploit
module Framework
# @note This is a lie. The core libraries are not semantically versioned. This is currently just linked to the
# Metasploit::Framework::Version, which is also not semantically versioned.
module Core
module Version
MAJOR = Metasploit::Framework::Version::MAJOR
MINOR = Metasploit::Framework::Version::MINOR
PATCH = Metasploit::Framework::Version::PATCH
PRERELEASE = Metasploit::Framework::Version::PRERELEASE
end
VERSION = Metasploit::Framework::VERSION
GEM_VERSION = Gem::Version.new(Metasploit::Framework::GEM_VERSION)
end
end
end

View File

@ -0,0 +1,105 @@
require 'active_model'
module Metasploit
module Framework
# This class provides an in-memory representation of a conceptual Credential
#
# It contains the public, private, and realm if any.
class Credential
include ActiveModel::Validations
# @!attribute paired
# @return [Boolean] Whether BOTH a public and private are required
# (defaults to `true`)
attr_accessor :paired
# @!attribute parent
# @return [Object] the parent object that had .to_credential called on it to create this object
attr_accessor :parent
# @!attribute private
# The private credential component (e.g. username)
#
# @return [String] if {#paired} is `true` or {#private} is `nil`
# @return [String, nil] if {#paired} is `false` or {#private} is not `nil`.
attr_accessor :private
# @!attribute private_type
# The type of private credential this object represents, e.g. a
# password or an NTLM hash.
#
# @return [String]
attr_accessor :private_type
# @!attribute public
# The public credential component (e.g. password)
#
# @return [String] if {#paired} is `true` or {#public} is `nil`
# @return [String, nil] if {#paired} is `false` or {#public} is not `nil`.
attr_accessor :public
# @!attribute realm
# @return [String,nil] The realm credential component (e.g domain name)
attr_accessor :realm
# @!attribute realm
# @return [String,nil] The type of {#realm}
attr_accessor :realm_key
validates :paired,
inclusion: { in: [true, false] }
# If we have no public we MUST have a private (e.g. SNMP Community String)
validates :private,
exclusion: { in: [nil] },
if: "public.nil? or paired"
# These values should be #demodularized from subclasses of
# `Metasploit::Credential::Private`
validates :private_type,
inclusion: { in: [ :password, :ntlm_hash, :ssh_key ] },
if: "private_type.present?"
# If we have no private we MUST have a public
validates :public,
presence: true,
if: "private.nil? or paired"
# @param attributes [Hash{Symbol => String,nil}]
def initialize(attributes={})
attributes.each do |attribute, value|
public_send("#{attribute}=", value)
end
self.paired = true if self.paired.nil?
end
def inspect
"#<#{self.class} \"#{self}\" >"
end
def to_s
if realm && realm_key == Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
"#{self.realm}\\#{self.public}:#{self.private}"
else
"#{self.public}:#{self.private}#{at_realm}"
end
end
def ==(other)
other.respond_to?(:public) && other.public == self.public &&
other.respond_to?(:private) && other.private == self.private &&
other.respond_to?(:realm) && other.realm == self.realm
end
def to_credential
self.parent = self
self
end
private
def at_realm
if self.realm.present?
"@#{self.realm}"
else
""
end
end
end
end
end

View File

@ -0,0 +1,148 @@
require 'metasploit/framework/credential'
class Metasploit::Framework::CredentialCollection
# @!attribute blank_passwords
# Whether each username should be tried with a blank password
# @return [Boolean]
attr_accessor :blank_passwords
# @!attribute pass_file
# Path to a file containing passwords, one per line
# @return [String]
attr_accessor :pass_file
# @!attribute password
# @return [String]
attr_accessor :password
# @!attribute prepended_creds
# List of credentials to be tried before any others
#
# @see #prepend_cred
# @return [Array<Credential>]
attr_accessor :prepended_creds
# @!attribute realm
# @return [String]
attr_accessor :realm
# @!attribute user_as_pass
# Whether each username should be tried as a password for that user
# @return [Boolean]
attr_accessor :user_as_pass
# @!attribute user_file
# Path to a file containing usernames, one per line
# @return [String]
attr_accessor :user_file
# @!attribute username
# @return [String]
attr_accessor :username
# @!attribute userpass_file
# Path to a file containing usernames and passwords separated by a space,
# one pair per line
# @return [String]
attr_accessor :userpass_file
# @option opts [Boolean] :blank_passwords See {#blank_passwords}
# @option opts [String] :pass_file See {#pass_file}
# @option opts [String] :password See {#password}
# @option opts [Array<Credential>] :prepended_creds ([]) See {#prepended_creds}
# @option opts [Boolean] :user_as_pass See {#user_as_pass}
# @option opts [String] :user_file See {#user_file}
# @option opts [String] :username See {#username}
# @option opts [String] :userpass_file See {#userpass_file}
def initialize(opts = {})
opts.each do |attribute, value|
public_send("#{attribute}=", value)
end
self.prepended_creds ||= []
end
# Add {Credential credentials} that will be yielded by {#each}
#
# @see prepended_creds
# @param cred [Credential]
# @return [self]
def prepend_cred(cred)
prepended_creds.unshift cred
self
end
# Combines all the provided credential sources into a stream of {Credential}
# objects, yielding them one at a time
#
# @yieldparam credential [Metasploit::Framework::Credential]
# @return [void]
def each
if pass_file.present?
pass_fd = File.open(pass_file, 'r:binary')
end
prepended_creds.each { |c| yield c }
if username.present?
if password.present?
yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm)
end
if user_as_pass
yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm)
end
if blank_passwords
yield Metasploit::Framework::Credential.new(public: username, private: "", realm: realm)
end
if pass_fd
pass_fd.each_line do |pass_from_file|
pass_from_file.chomp!
yield Metasploit::Framework::Credential.new(public: username, private: pass_from_file, realm: realm)
end
pass_fd.seek(0)
end
end
if user_file.present?
File.open(user_file, 'r:binary') do |user_fd|
user_fd.each_line do |user_from_file|
user_from_file.chomp!
if password
yield Metasploit::Framework::Credential.new(public: user_from_file, private: password, realm: realm)
end
if user_as_pass
yield Metasploit::Framework::Credential.new(public: user_from_file, private: user_from_file, realm: realm)
end
if blank_passwords
yield Metasploit::Framework::Credential.new(public: user_from_file, private: "", realm: realm)
end
if pass_fd
pass_fd.each_line do |pass_from_file|
pass_from_file.chomp!
yield Metasploit::Framework::Credential.new(public: user_from_file, private: pass_from_file, realm: realm)
end
pass_fd.seek(0)
end
end
end
end
if userpass_file.present?
File.open(userpass_file, 'r:binary') do |userpass_fd|
userpass_fd.each_line do |line|
user, pass = line.split(" ", 2)
if pass.blank?
pass = ''
else
pass.chomp!
end
yield Metasploit::Framework::Credential.new(public: user, private: pass, realm: realm)
end
end
end
ensure
pass_fd.close if pass_fd && !pass_fd.closed?
end
end

View File

@ -8,7 +8,7 @@ module Metasploit
end
def self.configurations_pathname
Metasploit::Framework.root.join('config', 'database.yml')
Metasploit::Framework::Application.paths['config/database'].first
end
end
end

View File

@ -0,0 +1,19 @@
#
# Gems
#
require 'rails/engine'
#
# Project
#
require 'metasploit/framework/common_engine'
module Metasploit
module Framework
class Engine < Rails::Engine
include Metasploit::Framework::CommonEngine
end
end
end

View File

@ -0,0 +1,276 @@
require 'metasploit/framework/tcp/client'
module Metasploit
module Framework
module Ftp
module Client
include Metasploit::Framework::Tcp::Client
#
# This method establishes an FTP connection to host and port specified by
# the 'rhost' and 'rport' methods. After connecting, the banner
# message is read in and stored in the 'banner' attribute.
#
def connect(global = true)
fd = super(global)
@ftpbuff = '' unless @ftpbuff
# Wait for a banner to arrive...
self.banner = recv_ftp_resp(fd)
# Return the file descriptor to the caller
fd
end
#
# This method handles establishing datasocket for data channel
#
def data_connect(mode = nil, nsock = self.sock)
if mode
res = send_cmd([ 'TYPE' , mode ], true, nsock)
return nil if not res =~ /^200/
end
# force datasocket to renegotiate
self.datasocket.shutdown if self.datasocket != nil
res = send_cmd(['PASV'], true, nsock)
return nil if not res =~ /^227/
# 227 Entering Passive Mode (127,0,0,1,196,5)
if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/
# convert port to FTP syntax
datahost = "#{$1}.#{$2}.#{$3}.#{$4}"
dataport = ($5.to_i * 256) + $6.to_i
self.datasocket = Rex::Socket::Tcp.create('PeerHost' => datahost, 'PeerPort' => dataport)
end
self.datasocket
end
#
# This method handles disconnecting our data channel
#
def data_disconnect
self.datasocket.shutdown
self.datasocket = nil
end
#
# Connect and login to the remote FTP server using the credentials
# that have been supplied in the exploit options.
#
def connect_login(user,pass,global = true)
ftpsock = connect(global)
if !(user and pass)
return false
end
res = send_user(user, ftpsock)
if (res !~ /^(331|2)/)
return false
end
if (pass)
res = send_pass(pass, ftpsock)
if (res !~ /^2/)
return false
end
end
return true
end
#
# This method logs in as the supplied user by transmitting the FTP
# 'USER <user>' command.
#
def send_user(user, nsock = self.sock)
raw_send("USER #{user}\r\n", nsock)
recv_ftp_resp(nsock)
end
#
# This method completes user authentication by sending the supplied
# password using the FTP 'PASS <pass>' command.
#
def send_pass(pass, nsock = self.sock)
raw_send("PASS #{pass}\r\n", nsock)
recv_ftp_resp(nsock)
end
#
# This method sends a QUIT command.
#
def send_quit(nsock = self.sock)
raw_send("QUIT\r\n", nsock)
recv_ftp_resp(nsock)
end
#
# This method sends one command with zero or more parameters
#
def send_cmd(args, recv = true, nsock = self.sock)
cmd = args.join(" ") + "\r\n"
ret = raw_send(cmd, nsock)
if (recv)
return recv_ftp_resp(nsock)
end
return ret
end
#
# This method transmits the command in args and receives / uploads DATA via data channel
# For commands not needing data, it will fall through to the original send_cmd
#
# For commands that send data only, the return will be the server response.
# For commands returning both data and a server response, an array will be returned.
#
# NOTE: This function always waits for a response from the server.
#
def send_cmd_data(args, data, mode = 'a', nsock = self.sock)
type = nil
# implement some aliases for various commands
if (args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i)
# TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i
args[0] = "LIST"
type = "get"
elsif (args[0] =~ /^GET$/i)
args[0] = "RETR"
type = "get"
elsif (args[0] =~ /^PUT$/i)
args[0] = "STOR"
type = "put"
end
# fall back if it's not a supported data command
if not type
return send_cmd(args, true, nsock)
end
# Set the transfer mode and connect to the remove server
return nil if not data_connect(mode)
# Our pending command should have got a connection now.
res = send_cmd(args, true, nsock)
# make sure could open port
return nil unless res =~ /^(150|125) /
# dispatch to the proper method
if (type == "get")
# failed listings jsut disconnect..
begin
data = self.datasocket.get_once(-1, ftp_timeout)
rescue ::EOFError
data = nil
end
else
sent = self.datasocket.put(data)
end
# close data channel so command channel updates
data_disconnect
# get status of transfer
ret = nil
if (type == "get")
ret = recv_ftp_resp(nsock)
ret = [ ret, data ]
else
ret = recv_ftp_resp(nsock)
end
ret
end
#
# This method transmits a FTP command and waits for a response. If one is
# received, it is returned to the caller.
#
def raw_send_recv(cmd, nsock = self.sock)
nsock.put(cmd)
nsock.get_once(-1, ftp_timeout)
end
#
# This method reads an FTP response based on FTP continuation stuff
#
def recv_ftp_resp(nsock = self.sock)
found_end = false
resp = ""
left = ""
if !@ftpbuff.empty?
left << @ftpbuff
@ftpbuff = ""
end
while true
data = nsock.get_once(-1, ftp_timeout)
if not data
@ftpbuff << resp
@ftpbuff << left
return data
end
got = left + data
left = ""
# handle the end w/o newline case
enlidx = got.rindex(0x0a.chr)
if enlidx != (got.length-1)
if not enlidx
left << got
next
else
left << got.slice!((enlidx+1)..got.length)
end
end
# split into lines
rarr = got.split(/\r?\n/)
rarr.each do |ln|
if not found_end
resp << ln
resp << "\r\n"
if ln.length > 3 and ln[3,1] == ' '
found_end = true
end
else
left << ln
left << "\r\n"
end
end
if found_end
@ftpbuff << left
return resp
end
end
end
#
# This method transmits a FTP command and does not wait for a response
#
def raw_send(cmd, nsock = self.sock)
nsock.put(cmd)
end
def ftp_timeout
raise NotImplementedError
end
protected
#
# This attribute holds the banner that was read in after a successful call
# to connect or connect_login.
#
attr_accessor :banner, :datasocket
end
end
end
end

View File

@ -0,0 +1,272 @@
module Metasploit
module Framework
module JtR
class JohnNotFoundError < StandardError
end
class Cracker
include ActiveModel::Validations
# @!attribute config
# @return [String] The path to an optional config file for John to use
attr_accessor :config
# @!attribute format
# @return [String] The hash format to try
attr_accessor :format
# @!attribute hash_path
# @return [String] The path to the file containing the hashes
attr_accessor :hash_path
# @!attribute incremental
# @return [String] The incremental mode to use
attr_accessor :incremental
# @!attribute john_path
# This attribute allows the user to specify a john binary to use.
# If not supplied, the Cracker will search the PATH for a suitable john binary
# and finally fall back to the pre-compiled versions shipped with Metasploit.
#
# @return [String] The file path to an alternative John binary to use
attr_accessor :john_path
# @!attribute max_runtime
# @return [Fixnum] An optional maximum duration of the cracking attempt in seconds
attr_accessor :max_runtime
# @!attribute pot
# @return [String] The file path to an alternative John pot file to use
attr_accessor :pot
# @!attribute rules
# @return [String] The wordlist mangling rules to use inside John
attr_accessor :rules
# @!attribute wordlist
# @return [String] The file path to the wordlist to use
attr_accessor :wordlist
validates :config, :'Metasploit::Framework::File_path' => true, if: 'config.present?'
validates :hash_path, :'Metasploit::Framework::File_path' => true, if: 'hash_path.present?'
validates :john_path, :'Metasploit::Framework::Executable_path' => true, if: 'john_path.present?'
validates :pot, :'Metasploit::Framework::File_path' => true, if: 'pot.present?'
validates :max_runtime,
numericality: {
only_integer: true,
greater_than_or_equal_to: 0
}, if: 'max_runtime.present?'
validates :wordlist, :'Metasploit::Framework::File_path' => true, if: 'wordlist.present?'
# @param attributes [Hash{Symbol => String,nil}]
def initialize(attributes={})
attributes.each do |attribute, value|
public_send("#{attribute}=", value)
end
end
# This method follows a decision tree to determine the path
# to the John the Ripper binary we should use.
#
# @return [NilClass] if a binary path could not be found
# @return [String] the path to the selected JtR binary
def binary_path
# Always prefer a manually entered path
if john_path && ::File.file?(john_path)
bin_path = john_path
else
# Look in the Environment PATH for the john binary
path = Rex::FileUtils.find_full_path("john") ||
Rex::FileUtils.find_full_path("john.exe")
if path && ::File.file?(path)
bin_path = path
else
# If we can't find john anywhere else, look at our precompiled binaries
bin_path = select_shipped_binary
end
end
raise JohnNotFoundError, 'No suitable John binary was found on the system' if bin_path.blank?
bin_path
end
# This method runs the command from {#crack_command} and yields each line of output.
#
# @yield [String] a line of output from the john command
# @return [void]
def crack
::IO.popen(crack_command, "rb") do |fd|
fd.each_line do |line|
yield line
end
end
end
# This method builds an array for the command to actually run the cracker.
# It builds the command from all of the attributes on the class.
#
# @raise [JohnNotFoundError] if a suitable John binary was never found
# @return [Array] An array set up for {::IO.popen} to use
def crack_command
cmd_string = binary_path
cmd = [ cmd_string, '--session=' + john_session_id, '--nolog' ]
if config.present?
cmd << ( "--config=" + config )
else
cmd << ( "--config=" + john_config_file )
end
if pot.present?
cmd << ( "--pot=" + pot )
else
cmd << ( "--pot=" + john_pot_file)
end
if format.present?
cmd << ( "--format=" + format )
end
if wordlist.present?
cmd << ( "--wordlist=" + wordlist )
end
if incremental.present?
cmd << ( "--incremental=" + incremental )
end
if rules.present?
cmd << ( "--rules=" + rules )
end
if max_runtime.present?
cmd << ( "--max-run-time=" + max_runtime.to_s)
end
cmd << hash_path
end
# This runs the show command in john and yields cracked passwords.
#
# @yield [String] the output lines from the command
# @return [void]
def each_cracked_password
::IO.popen(show_command, "rb") do |fd|
fd.each_line do |line|
yield line
end
end
end
# This method returns the path to a default john.conf file.
#
# @return [String] the path to the default john.conf file
def john_config_file
::File.join( ::Msf::Config.data_directory, "john", "confs", "john.conf" )
end
# This method returns the path to a default john.pot file.
#
# @return [String] the path to the default john.pot file
def john_pot_file
::File.join( ::Msf::Config.config_directory, "john.pot" )
end
# This method is a getter for a random Session ID for John.
# It allows us to dinstiguish between cracking sessions.
#
# @ return [String] the Session ID to use
def john_session_id
@session_id ||= ::Rex::Text.rand_text_alphanumeric(8)
end
# This method builds the command to show the cracked passwords.
#
# @raise [JohnNotFoundError] if a suitable John binary was never found
# @return [Array] An array set up for {::IO.popen} to use
def show_command
cmd_string = binary_path
pot_file = pot || john_pot_file
cmd = [cmd_string, "--show", "--pot=#{pot_file}", "--format=#{format}" ]
if config
cmd << "--config=#{config}"
else
cmd << ( "--config=" + john_config_file )
end
cmd << hash_path
end
private
# This method tries to identify the correct version of the pre-shipped
# JtR binaries to use based on the platform.
#
# @return [NilClass] if the correct binary could not be determined
# @return [String] the path to the selected binary
def select_shipped_binary
cpuinfo_base = ::File.join(Msf::Config.data_directory, "cpuinfo")
run_path = nil
if File.directory?(cpuinfo_base)
data = nil
case ::RUBY_PLATFORM
when /mingw|cygwin|mswin/
fname = "#{cpuinfo_base}/cpuinfo.exe"
if File.exists?(fname) and File.executable?(fname)
data = %x{"#{fname}"} rescue nil
end
case data
when /sse2/
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.sse2", "john.exe")
when /mmx/
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.mmx", "john.exe")
else
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.any", "john.exe")
end
when /x86_64-linux/
fname = "#{cpuinfo_base}/cpuinfo.ia64.bin"
if File.exists? fname
::FileUtils.chmod(0755, fname) rescue nil
data = %x{"#{fname}"} rescue nil
end
case data
when /mmx/
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x64.mmx", "john")
else
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.any", "john")
end
when /i[\d]86-linux/
fname = "#{cpuinfo_base}/cpuinfo.ia32.bin"
if File.exists? fname
::FileUtils.chmod(0755, fname) rescue nil
data = %x{"#{fname}"} rescue nil
end
case data
when /sse2/
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.sse2", "john")
when /mmx/
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.mmx", "john")
else
run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.any", "john")
end
end
end
run_path
end
end
end
end
end

View File

@ -0,0 +1,20 @@
module Metasploit
module Framework
module JtR
# This class is the generic Exception raised by a {Wordlist} when
# it fails validation. It rolls up all validation errors into a
# single exception so that all errors can be dealt with at once.
class InvalidWordlist < StandardError
attr_reader :model
def initialize(model)
@model = model
errors = @model.errors.full_messages.join(', ')
super(errors)
end
end
end
end
end

View File

@ -0,0 +1,429 @@
require 'metasploit/framework/jtr/invalid_wordlist'
module Metasploit
module Framework
module JtR
class Wordlist
include ActiveModel::Validations
# A mapping of the mutation substitution rules
MUTATIONS = {
'@' => 'a',
'0' => 'o',
'3' => 'e',
'$' => 's',
'7' => 't',
'1' => 'l',
'5' => 's'
}
# @!attribute appenders
# @return [Array] an array of strings to append to each word
attr_accessor :appenders
# @!attribute custom_wordlist
# @return [String] the path to a custom wordlist file to include
attr_accessor :custom_wordlist
# @!attribute mutate
# @return [TrueClass] if you want each word mutated as it is added
# @return [FalseClass] if you do not want each word mutated
attr_accessor :mutate
# @!attribute prependers
# @return [Array] an array of strings to prepend to each word
attr_accessor :prependers
# @!attribute use_common_root
# @return [TrueClass] if you want to use the common root words wordlist
# @return [FalseClass] if you do not want to use the common root words wordlist
attr_accessor :use_common_root
# @!attribute use_creds
# @return [TrueClass] if you want to seed the wordlist with existing credential data from the database
# @return [FalseClass] if you do not want to seed the wordlist with existing credential data from the database
attr_accessor :use_creds
# @!attribute use_db_info
# @return [TrueClass] if you want to seed the wordlist with looted database names and schemas
# @return [FalseClass] if you do not want to seed the wordlist with looted database names and schemas
attr_accessor :use_db_info
# @!attribute use_default_wordlist
# @return [TrueClass] if you want to use the default wordlist
# @return [FalseClass] if you do not want to use the default wordlist
attr_accessor :use_default_wordlist
# @!attribute use_hostnames
# @return [TrueClass] if you want to seed the wordlist with existing hostnames from the database
# @return [FalseClass] if you do not want to seed the wordlist with existing hostnames from the database
attr_accessor :use_hostnames
# @!attribute workspace
# @return [Mdm::Workspace] the workspace this cracker is for.
attr_accessor :workspace
validates :custom_wordlist, :'Metasploit::Framework::File_path' => true, if: 'custom_wordlist.present?'
validates :mutate,
inclusion: { in: [true, false], message: "must be true or false" }
validates :use_common_root,
inclusion: { in: [true, false], message: "must be true or false" }
validates :use_creds,
inclusion: { in: [true, false], message: "must be true or false" }
validates :use_db_info,
inclusion: { in: [true, false], message: "must be true or false" }
validates :use_default_wordlist,
inclusion: { in: [true, false], message: "must be true or false" }
validates :use_hostnames,
inclusion: { in: [true, false], message: "must be true or false" }
validates :workspace,
presence: true
# @param attributes [Hash{Symbol => String,nil}]
def initialize(attributes={})
attributes.each do |attribute, value|
public_send("#{attribute}=", value)
end
@appenders ||= []
@prependers ||= []
end
# This method takes a word, and appends each word from the appenders list
# and yields the new words.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_appended_word(word='')
yield word
appenders.each do |suffix|
yield "#{word}#{suffix}"
end
end
# This method checks all the attributes set on the object and calls
# the appropriate enumerators for each option and yields the results back
# up the call-chain.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_base_word
# Make sure are attributes are all valid first!
valid!
# Yield the expanded form of each line of the custom wordlist if one was given
if custom_wordlist.present?
each_custom_word do |word|
yield word unless word.blank?
end
end
# Yield each word from the common root words list if it was selected
if use_common_root
each_root_word do |word|
yield word unless word.blank?
end
end
# If the user has selected use_creds we yield each password, username, and realm name
# that currently exists in the database.
if use_creds
each_cred_word do |word|
yield word unless word.blank?
end
end
if use_db_info
each_database_word do |word|
yield word unless word.blank?
end
end
if use_default_wordlist
each_default_word do |word|
yield word unless word.blank?
end
end
if use_hostnames
each_hostname_word do |word|
yield word unless word.blank?
end
end
end
# This method searches all saved Credentials in the database
# and yields all passwords, usernames, and realm names it finds.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_cred_word
# We don't want all Private types here. Only Passwords make sense for inclusion in the wordlist.
Metasploit::Credential::Password.all.each do |password|
yield password.data
end
Metasploit::Credential::Public.all.each do |public|
yield public.username
end
Metasploit::Credential::Realm.all.each do |realm|
yield realm.value
end
end
# This method reads the file provided as custom_wordlist and yields
# the expanded form of each word in the list.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_custom_word
::File.open(custom_wordlist, "rb") do |fd|
fd.each_line do |line|
expanded_words(line) do |word|
yield word
end
end
end
end
# This method searches the notes in the current workspace
# for DB instance names, database names, table names, and
# column names gathered from live database servers. It yields
# each one that it finds.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_database_word
# Yield database, table and column names from any looted database schemas
workspace.notes.where('ntype like ?', '%.schema%').each do |note|
expanded_words(note.data['DBName']) do |word|
yield word
end
note.data['Tables'].each do |table|
expanded_words(table['TableName']) do |word|
yield word
end
table['Columns'].each do |column|
expanded_words(column['ColumnName']) do |word|
yield word
end
end
end
end
# Yield any capture MSSQL Instance names
workspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename']).each do |note|
expanded_words(note.data['InstanceName']) do |word|
yield word
end
end
end
# This method yields expanded words taken from the default john
# wordlist that we ship in the data directory.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_default_word
::File.open(default_wordlist_path, "rb") do |fd|
fd.each_line do |line|
expanded_words(line) do |word|
yield word
end
end
end
end
# This method yields the expanded words out of all the hostnames
# found in the current workspace.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_hostname_word
workspace.hosts.all.each do |host|
unless host.name.nil?
expanded_words(host.name) do |word|
yield nil
end
end
end
end
# This method checks to see if the user asked for mutations. If mutations
# have been enabled, then it creates all the unique mutations and yields
# each result.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_mutated_word(word='')
mutants = [ ]
# Run the mutations only if the option is set
if mutate
mutants = mutants + mutate_word(word)
end
mutants << word
mutants.uniq.each do |mutant|
yield mutant
end
end
# This method takes a word, and prepends each word from the prependers list
# and yields the new words.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_prepended_word(word='')
yield word
prependers.each do |prefix|
yield "#{prefix}#{word}"
end
end
# This method reads the common_roots.txt wordlist
# expands any words in the list and yields them.
#
# @yieldparam word [String] the expanded word
# @return [void]
def each_root_word
::File.open(common_root_words_path, "rb") do |fd|
fd.each_line do |line|
expanded_words(line) do |word|
yield word
end
end
end
end
# This method wraps around all the other enumerators. It processes
# all of the options and yields each word generated by the options
# selected.
#
# @yieldparam word [String] the word to write out to the wordlist file
# @return [void]
def each_word
each_base_word do |base_word|
each_mutated_word(base_word) do |mutant|
each_prepended_word(mutant) do |prepended|
yield prepended
end
each_appended_word(mutant) do |appended|
yield appended
end
end
end
end
# This method takes a string and splits it on non-word characters
# and the underscore. It does this to find likely distinct words
# in the string. It then yields each 'word' found this way.
#
# @param word [String] the string to split apart
# @yieldparam expanded [String] the expanded words
# @return [void]
def expanded_words(word='')
word.split(/[\W_]+/).each do |expanded|
yield expanded
end
end
# This method takes a word and applies various mutation rules to that word
# and returns an array of all the mutated forms.
#
# @param word [String] the word to apply the mutations to
# @return [Array<String>] An array containing all the mutated forms of the word
def mutate_word(word)
results = []
# Iterate through combinations to create each possible mutation
mutation_keys.each do |iteration|
next if iteration.flatten.empty?
intermediate = word.dup
subsititutions = iteration.collect { |key| MUTATIONS[key] }
intermediate.tr!(subsititutions.join, iteration.join)
results << intermediate
end
results.flatten.uniq
end
# A getter for a memoized version fo the mutation keys list
#
# @return [Array<Array>] a 2D array of all mutation combinations
def mutation_keys
@mutation_keys ||= generate_mutation_keys
end
# This method takes all the options provided and streams the generated wordlist out
# to a {Rex::Quickfile} and returns the {Rex::Quickfile}.
#
# @return [Rex::Quickfile] The {Rex::Quickfile} object that the wordlist has been written to
def to_file
valid!
wordlist_file = Rex::Quickfile.new("jtrtmp")
each_word do |word|
wordlist_file.puts word
end
wordlist_file
end
# Raise an exception if the attributes are not valid.
#
# @raise [Invalid] if the attributes are not valid on this scanner
# @return [void]
def valid!
unless valid?
raise Metasploit::Framework::JtR::InvalidWordlist.new(self)
end
nil
end
private
# This method returns the path to the common_roots.txt wordlist
#
# @return [String] the file path to the common_roots.txt file
def common_root_words_path
::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'common_roots.txt')
end
# This method returns the path to the passwords.lst wordlist
#
# @return [String] the file path to the passwords.lst file
def default_wordlist_path
::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'password.lst')
end
def generate_mutation_keys
iterations = MUTATIONS.keys.dup
# Find PowerSet of all possible mutation combinations
iterations.inject([[]]) do |accumulator,mutation_key|
power_set = []
accumulator.each do |i|
power_set << i
power_set << i+[mutation_key]
end
power_set
end
end
end
end
end
end

View File

@ -0,0 +1,47 @@
require 'metasploit/framework/credential'
module Metasploit
module Framework
# This module provides the namespace for all LoginScanner classes.
# LoginScanners are the classes that provide functionality for testing
# authentication against various different protocols and mechanisms.
module LoginScanner
require 'metasploit/framework/login_scanner/result'
require 'metasploit/framework/login_scanner/invalid'
# Gather a list of LoginScanner classes that can potentially be
# used for a given `service`, which should usually be an
# `Mdm::Service` object, but can be anything that responds to
# #name and #port.
#
# @param service [Mdm::Service,#port,#name]
# @return [Array<LoginScanner::Base>] A collection of LoginScanner
# classes that will probably give useful results when run
# against `service`.
def self.classes_for_service(service)
unless @required
# Make sure we've required all the scanner classes
dir = File.expand_path("../login_scanner/", __FILE__)
Dir.glob(File.join(dir, "*.rb")).each do |f|
require f if File.file?(f)
end
@required = true
end
self.constants.map{|sym| const_get(sym)}.select do |const|
next unless const.kind_of?(Class)
(
const.const_defined?(:LIKELY_PORTS) &&
const.const_get(:LIKELY_PORTS).include?(service.port)
) || (
const.const_defined?(:LIKELY_SERVICE_NAMES) &&
const.const_get(:LIKELY_SERVICE_NAMES).include?(service.name)
)
end
end
end
end
end

View File

@ -0,0 +1,50 @@
require 'metasploit/framework/tcp/client'
require 'metasploit/framework/afp/client'
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with Apple Filing
# Protocol.
class AFP
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::Tcp::Client
include Metasploit::Framework::AFP::Client
DEFAULT_PORT = 548
LIKELY_PORTS = [ DEFAULT_PORT ]
LIKELY_SERVICE_NAMES = [ "afp" ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = nil
# @!attribute login_timeout
# @return [Integer] Number of seconds to wait before giving up
attr_accessor :login_timeout
def attempt_login(credential)
begin
connect
rescue Rex::ConnectionError, EOFError, Timeout::Error
status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
else
success = login(credential.public, credential.private)
status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT
end
Result.new(credential: credential, status: status)
end
def set_sane_defaults
self.connection_timeout ||= 30
self.port ||= DEFAULT_PORT
self.max_send_size ||= 0
self.send_delay ||= 0
end
end
end
end
end

View File

@ -0,0 +1,67 @@
require 'metasploit/framework/login_scanner/http'
module Metasploit
module Framework
module LoginScanner
# Tomcat Manager login scanner
class Axis2 < HTTP
DEFAULT_PORT = 8080
# Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP
CAN_GET_SESSION = true
PRIVATE_TYPES = [ :password ]
# (see Base#attempt_login)
def attempt_login(credential)
http_client = Rex::Proto::Http::Client.new(
host, port, {}, ssl, ssl_version
)
result_opts = {
credential: credential
}
begin
http_client.connect
body = "userName=#{Rex::Text.uri_encode(credential.public)}&password=#{Rex::Text.uri_encode(credential.private)}&submit=+Login+"
request = http_client.request_cgi(
'uri' => uri,
'method' => "POST",
'data' => body,
)
response = http_client.send_recv(request)
if response && response.code == 200 && response.body.include?("upload")
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response)
else
result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response)
end
rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
end
Result.new(result_opts)
end
# (see Base#set_sane_defaults)
def set_sane_defaults
self.uri = "/axis2/axis2-admin/login" if self.uri.nil?
@method = "POST".freeze
super
end
# The method *must* be "POST", so don't let the user change it
# @raise [RuntimeError]
def method=(_)
raise RuntimeError, "Method must be POST for Axis2"
end
end
end
end
end

View File

@ -0,0 +1,222 @@
require 'metasploit/framework/login_scanner'
module Metasploit
module Framework
module LoginScanner
# This module provides the base behaviour for all of
# the LoginScanner classes. All of the LoginScanners
# should include this module to establish base behaviour
module Base
extend ActiveSupport::Concern
include ActiveModel::Validations
included do
# @!attribute connection_timeout
# @return [Fixnum] The timeout in seconds for a single SSH connection
attr_accessor :connection_timeout
# @!attribute cred_details
# @return [CredentialCollection] Collection of Credential objects
attr_accessor :cred_details
# @!attribute host
# @return [String] The IP address or hostname to connect to
attr_accessor :host
# @!attribute port
# @return [Fixnum] The port to connect to
attr_accessor :port
# @!attribute proxies
# @return [String] The proxy directive to use for the socket
attr_accessor :proxies
# @!attribute stop_on_success
# @return [Boolean] Whether the scanner should stop when it has found one working Credential
attr_accessor :stop_on_success
validates :connection_timeout,
presence: true,
numericality: {
only_integer: true,
greater_than_or_equal_to: 1
}
validates :cred_details, presence: true
validates :host, presence: true
validates :port,
presence: true,
numericality: {
only_integer: true,
greater_than_or_equal_to: 1,
less_than_or_equal_to: 65535
}
validates :stop_on_success,
inclusion: { in: [true, false] }
validate :host_address_must_be_valid
validate :validate_cred_details
# @param attributes [Hash{Symbol => String,nil}]
def initialize(attributes={})
attributes.each do |attribute, value|
public_send("#{attribute}=", value)
end
set_sane_defaults
end
# Attempt a single login against the service with the given
# {Credential credential}.
#
# @param credential [Credential] The credential object to attmpt to
# login with
# @return [Result] A Result object indicating success or failure
# @abstract Protocol-specific scanners must implement this for their
# respective protocols
def attempt_login(credential)
raise NotImplementedError
end
def each_credential
cred_details.each do |raw_cred|
# This could be a Credential object, or a Credential Core, or an Attempt object
# so make sure that whatever it is, we end up with a Credential.
credential = raw_cred.to_credential
if credential.realm.present? && self.class::REALM_KEY.present?
credential.realm_key = self.class::REALM_KEY
yield credential
elsif credential.realm.blank? && self.class::REALM_KEY.present? && self.class::DEFAULT_REALM.present?
credential.realm_key = self.class::REALM_KEY
credential.realm = self.class::DEFAULT_REALM
yield credential
elsif credential.realm.present? && self.class::REALM_KEY.blank?
second_cred = credential.dup
# Strip the realm off here, as we don't want it
credential.realm = nil
credential.realm_key = nil
yield credential
# Some services can take a domain in the username like this even though
# they do not explicitly take a domain as part of the protocol.
second_cred.public = "#{second_cred.realm}\\#{second_cred.public}"
second_cred.realm = nil
second_cred.realm_key = nil
yield second_cred
else
yield credential
end
end
end
# Attempt to login with every {Credential credential} in
# {#cred_details}, by calling {#attempt_login} once for each.
#
# If a successful login is found for a user, no more attempts
# will be made for that user.
#
# @yieldparam result [Result] The {Result} object for each attempt
# @yieldreturn [void]
# @return [void]
def scan!
valid!
# Keep track of connection errors.
# If we encounter too many, we will stop.
consecutive_error_count = 0
total_error_count = 0
successful_users = Set.new
each_credential do |credential|
# For Pro bruteforce Reuse and Guess we need to note that we skipped an attempt.
if successful_users.include?(credential.public)
if credential.parent.respond_to?(:skipped)
credential.parent.skipped = true
credential.parent.save!
end
next
end
result = attempt_login(credential)
result.freeze
yield result if block_given?
if result.success?
consecutive_error_count = 0
break if stop_on_success
successful_users << credential.public
else
if result.status == Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
consecutive_error_count += 1
total_error_count += 1
break if consecutive_error_count >= 3
break if total_error_count >= 10
end
end
end
nil
end
# Raise an exception if this scanner's attributes are not valid.
#
# @raise [Invalid] if the attributes are not valid on this scanner
# @return [void]
def valid!
unless valid?
raise Metasploit::Framework::LoginScanner::Invalid.new(self)
end
nil
end
private
# This method validates that the host address is both
# of a valid type and is resolveable.
# @return [void]
def host_address_must_be_valid
if host.kind_of? String
begin
resolved_host = ::Rex::Socket.getaddress(host, true)
if host =~ /^\d{1,3}(\.\d{1,3}){1,3}$/
unless host =~ Rex::Socket::MATCH_IPV4
errors.add(:host, "could not be resolved")
end
end
self.host = resolved_host
rescue
errors.add(:host, "could not be resolved")
end
else
errors.add(:host, "must be a string")
end
end
# This is a placeholder method. Each LoginScanner class
# will override this with any sane defaults specific to
# its own behaviour.
# @abstract
# @return [void]
def set_sane_defaults
self.connection_timeout = 30 if self.connection_timeout.nil?
end
# This method validates that the credentials supplied
# are all valid.
# @return [void]
def validate_cred_details
unless cred_details.respond_to? :each
errors.add(:cred_details, "must respond to :each")
end
end
end
end
end
end
end

View File

@ -0,0 +1,134 @@
require 'metasploit/framework/tcp/client'
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with DB2 Database servers.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class DB2
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::Tcp::Client
DEFAULT_PORT = 50000
DEFAULT_REALM = 'toolsdb'
LIKELY_PORTS = [ DEFAULT_PORT ]
# @todo XXX
LIKELY_SERVICE_NAMES = [ ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = Metasploit::Model::Realm::Key::DB2_DATABASE
# @see Base#attempt_login
def attempt_login(credential)
result_options = {
credential: credential
}
begin
probe_data = send_probe(credential.realm)
if probe_data.empty?
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
else
if authenticate?(credential)
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
else
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end
end
rescue ::Rex::ConnectionError, ::Rex::ConnectionTimeout, ::Rex::Proto::DRDA::RespError,::Timeout::Error => e
result_options.merge!({
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
proof: e.message
})
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
private
# This method takes the credential and actually attempts the authentication
# @param credential [Credential] The Credential object to authenticate with.
# @return [Boolean] Whether the authentication was successful
def authenticate?(credential)
# Send the login packet and get a response packet back
login_packet = Rex::Proto::DRDA::Utils.client_auth(:dbname => credential.realm,
:dbuser => credential.public,
:dbpass => credential.private
)
sock.put login_packet
response = sock.get_once
if valid_response?(response)
if successful_login?(response)
true
else
false
end
else
false
end
end
# This method opens a socket to the target DB2 server.
# It then sends a client probe on that socket to get information
# back on the server.
# @param database_name [String] The name of the database to probe
# @return [Hash] A hash containing the server information from the probe reply
def send_probe(database_name)
disconnect if self.sock
connect
probe_packet = Rex::Proto::DRDA::Utils.client_probe(database_name)
sock.put probe_packet
response = sock.get_once
response_data = {}
if valid_response?(response)
packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response)
response_data = Rex::Proto::DRDA::Utils.server_packet_info(packet)
end
response_data
end
# This method sets the sane defaults for things
# like timeouts and TCP evasion options
def set_sane_defaults
self.connection_timeout ||= 30
self.port ||= DEFAULT_PORT
self.max_send_size ||= 0
self.send_delay ||= 0
self.ssl = false if self.ssl.nil?
end
# This method takes a response packet and checks to see
# if the authentication was actually successful.
#
# @param response [String] The unprocessed response packet
# @return [Boolean] Whether the authentication was successful
def successful_login?(response)
packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response)
packet_info = Rex::Proto::DRDA::Utils.server_packet_info(packet)
if packet_info[:db_login_success]
true
else
false
end
end
# This method provides a simple test on whether the response
# packet was valid.
#
# @param response [String] The response to examine from the socket
# @return [Boolean] Whether the response is valid
def valid_response?(response)
response && response.length > 0
end
end
end
end
end

View File

@ -0,0 +1,76 @@
require 'metasploit/framework/ftp/client'
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with FTP.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class FTP
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::Ftp::Client
DEFAULT_PORT = 21
LIKELY_PORTS = [ DEFAULT_PORT, 2121 ]
LIKELY_SERVICE_NAMES = [ 'ftp' ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = nil
# @!attribute ftp_timeout
# @return [Fixnum] The timeout in seconds to wait for a response to an FTP command
attr_accessor :ftp_timeout
validates :ftp_timeout,
presence: true,
numericality: {
only_integer: true,
greater_than_or_equal_to: 1
}
# (see Base#attempt_login)
def attempt_login(credential)
result_options = {
credential: credential
}
begin
success = connect_login(credential.public, credential.private)
rescue ::EOFError, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
success = false
end
if success
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
elsif !(result_options.has_key? :status)
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
private
# This method sets the sane defaults for things
# like timeouts and TCP evasion options
def set_sane_defaults
self.connection_timeout ||= 30
self.port ||= DEFAULT_PORT
self.max_send_size ||= 0
self.send_delay ||= 0
self.ftp_timeout ||= 16
end
end
end
end
end

View File

@ -0,0 +1,123 @@
require 'rex/proto/http'
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
module Metasploit
module Framework
module LoginScanner
#
# HTTP-specific login scanner.
#
class HTTP
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
DEFAULT_REALM = nil
DEFAULT_PORT = 80
DEFAULT_SSL_PORT = 443
LIKELY_PORTS = [ 80, 443, 8000, 8080 ]
LIKELY_SERVICE_NAMES = [ 'http', 'https' ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
# @!attribute uri
# @return [String] The path and query string on the server to
# authenticate to.
attr_accessor :uri
# @!attribute uri
# @return [String] HTTP method, e.g. "GET", "POST"
attr_accessor :method
validates :uri, presence: true, length: { minimum: 1 }
validates :method,
presence: true,
length: { minimum: 1 }
# Attempt a single login with a single credential against the target.
#
# @param credential [Credential] The credential object to attempt to
# login with.
# @return [Result] A Result object indicating success or failure
def attempt_login(credential)
ssl = false if ssl.nil?
result_opts = {
credential: credential,
status: Metasploit::Model::Login::Status::INCORRECT,
proof: nil
}
http_client = Rex::Proto::Http::Client.new(
host, port, {}, ssl, ssl_version,
nil, credential.public, credential.private
)
if credential.realm
http_client.set_config('domain' => credential.realm)
end
begin
http_client.connect
request = http_client.request_cgi(
'uri' => uri,
'method' => method
)
# First try to connect without logging in to make sure this
# resource requires authentication. We use #_send_recv for
# that instead of #send_recv.
response = http_client._send_recv(request)
if response && response.code == 401 && response.headers['WWW-Authenticate']
# Now send the creds
response = http_client.send_auth(
response, request.opts, connection_timeout, true
)
if response && response.code == 200
result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers)
end
else
result_opts.merge!(status: Metasploit::Model::Login::Status::NO_AUTH_REQUIRED)
end
rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT)
ensure
http_client.close
end
Result.new(result_opts)
end
private
# This method sets the sane defaults for things
# like timeouts and TCP evasion options
def set_sane_defaults
self.connection_timeout ||= 20
self.max_send_size = 0 if self.max_send_size.nil?
self.send_delay = 0 if self.send_delay.nil?
self.uri = '/' if self.uri.blank?
self.method = 'GET' if self.method.blank?
# Note that this doesn't cover the case where ssl is unset and
# port is something other than a default. In that situtation,
# we don't know what the user has in mind so we have to trust
# that they're going to do something sane.
if !(self.ssl) && self.port.nil?
self.port = self.class::DEFAULT_PORT
self.ssl = false
elsif self.ssl && self.port.nil?
self.port = self.class::DEFAULT_SSL_PORT
elsif self.ssl.nil? && self.port == self.class::DEFAULT_PORT
self.ssl = false
elsif self.ssl.nil? && self.port == self.class::DEFAULT_SSL_PORT
self.ssl = true
end
nil
end
end
end
end
end

View File

@ -0,0 +1,22 @@
module Metasploit
module Framework
module LoginScanner
# This class is the generic Exception raised by LoginScanners when
# they fail validation. It rolls up all validation errors into a
# single exception so that all errors can be dealt with at once.
class Invalid < StandardError
attr_reader :model
def initialize(model)
@model = model
errors = @model.errors.full_messages.join(', ')
super(errors)
end
end
end
end
end

View File

@ -0,0 +1,74 @@
require 'metasploit/framework/mssql/client'
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
require 'metasploit/framework/login_scanner/ntlm'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with Microsoft SQL Servers.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results
class MSSQL
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::LoginScanner::NTLM
include Metasploit::Framework::MSSQL::Client
DEFAULT_PORT = 1433
DEFAULT_REALM = 'WORKSTATION'
# Lifted from lib/msf/core/exploit/mssql.rb
LIKELY_PORTS = [ 1433, 1434, 1435, 14330, 2533, 9152, 2638 ]
# Lifted from lib/msf/core/exploit/mssql.rb
LIKELY_SERVICE_NAMES = [ 'ms-sql-s', 'ms-sql2000', 'sybase' ]
PRIVATE_TYPES = [ :password, :ntlm_hash ]
REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN
# @!attribute windows_authentication
# @return [Boolean] Whether to use Windows Authentication instead of SQL Server Auth.
attr_accessor :windows_authentication
validates :windows_authentication,
inclusion: { in: [true, false] }
def attempt_login(credential)
result_options = {
credential: credential
}
begin
if mssql_login(credential.public, credential.private, '', credential.realm)
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
else
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end
rescue ::Rex::ConnectionError
result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
private
def set_sane_defaults
self.connection_timeout ||= 30
self.port ||= DEFAULT_PORT
self.max_send_size ||= 0
self.send_delay ||= 0
# Don't use ||= with booleans
self.send_lm = true if self.send_lm.nil?
self.send_ntlm = true if self.send_ntlm.nil?
self.send_spn = true if self.send_spn.nil?
self.use_lmkey = false if self.use_lmkey.nil?
self.use_ntlm2_session = true if self.use_ntlm2_session.nil?
self.use_ntlmv2 = true if self.use_ntlmv2.nil?
self.windows_authentication = false if self.windows_authentication.nil?
end
end
end
end
end

View File

@ -0,0 +1,91 @@
require 'metasploit/framework/tcp/client'
require 'rbmysql'
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with MySQL Database servers.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class MySQL
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::Tcp::Client
DEFAULT_PORT = 3306
LIKELY_PORTS = [ 3306 ]
LIKELY_SERVICE_NAMES = [ 'mysql' ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = nil
def attempt_login(credential)
result_options = {
credential: credential
}
# manage our behind the scenes socket. Close any existing one and open a new one
disconnect if self.sock
connect
begin
::RbMysql.connect({
:host => host,
:port => port,
:read_timeout => 300,
:write_timeout => 300,
:socket => sock,
:user => credential.public,
:password => credential.private,
:db => ''
})
rescue Errno::ECONNREFUSED
result_options.merge!({
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
proof: "Connection refused"
})
rescue RbMysql::ClientError
result_options.merge!({
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
proof: "Connection timeout"
})
rescue Errno::ETIMEDOUT
result_options.merge!({
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
proof: "Operation Timed out"
})
rescue RbMysql::HostNotPrivileged
result_options.merge!({
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT,
proof: "Unable to login from this host due to policy"
})
rescue RbMysql::AccessDeniedError
result_options.merge!({
status: Metasploit::Model::Login::Status::INCORRECT,
proof: "Access Denied"
})
end
unless result_options[:status]
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
# This method sets the sane defaults for things
# like timeouts and TCP evasion options
def set_sane_defaults
self.connection_timeout ||= 30
self.port ||= DEFAULT_PORT
self.max_send_size ||= 0
self.send_delay ||= 0
end
end
end
end
end

View File

@ -0,0 +1,61 @@
require 'metasploit/framework/login_scanner'
module Metasploit
module Framework
module LoginScanner
# This Concern provides the basic accessors and validations
# for protocols that require the use of NTLM for Authentication.
module NTLM
extend ActiveSupport::Concern
include ActiveModel::Validations
included do
# @!attribute send_lm
# @return [Boolean] Whether to always send the LANMAN response(except if using NTLM2 Session)
attr_accessor :send_lm
# @!attribute send_ntlm
# @return [Boolean] Whether to use NTLM responses
attr_accessor :send_ntlm
# @!attribute send_spn
# @return [Boolean] Whether to support SPN for newer Windows OSes
attr_accessor :send_spn
# @!attribute use_lmkey
# @return [Boolean] Whether to negotiate with a LANMAN key
attr_accessor :use_lmkey
# @!attribute send_lm
# @return [Boolean] Whether to force the use of NTLM2 session
attr_accessor :use_ntlm2_session
# @!attribute send_lm
# @return [Boolean] Whether to force the use of NTLMv2 instead of NTLM2 Session
attr_accessor :use_ntlmv2
validates :send_lm,
inclusion: { in: [true, false] }
validates :send_ntlm,
inclusion: { in: [true, false] }
validates :send_spn,
inclusion: { in: [true, false] }
validates :use_lmkey,
inclusion: { in: [true, false] }
validates :use_ntlm2_session,
inclusion: { in: [true, false] }
validates :use_ntlmv2,
inclusion: { in: [true, false] }
end
end
end
end
end

View File

@ -0,0 +1,87 @@
require 'metasploit/framework/login_scanner/base'
require 'metasploit/framework/login_scanner/rex_socket'
require 'metasploit/framework/tcp/client'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with POP3.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class POP3
include Metasploit::Framework::LoginScanner::Base
include Metasploit::Framework::LoginScanner::RexSocket
include Metasploit::Framework::Tcp::Client
DEFAULT_PORT = 110
LIKELY_PORTS = [ 110, 995 ]
LIKELY_SERVICE_NAMES = [ 'pop3', 'pop3s' ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = nil
# This method attempts a single login with a single credential against the target
# @param credential [Credential] The credential object to attempt to login with
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
def attempt_login(credential)
result_options = {
credential: credential,
status: Metasploit::Model::Login::Status::INCORRECT
}
disconnect if self.sock
begin
connect
select([sock],nil,nil,0.4)
# Check to see if we recieved an OK?
result_options[:proof] = sock.get_once
if result_options[:proof] && result_options[:proof][/^\+OK.*/]
# If we received an OK we should send the USER
sock.put("USER #{credential.public}\r\n")
result_options[:proof] = sock.get_once
if result_options[:proof] && result_options[:proof][/^\+OK.*/]
# If we got an OK after the username we can send the PASS
sock.put("PASS #{credential.private}\r\n")
# Dovecot has a failed-auth penalty system that maxes at
# sleeping for 15 seconds before sending responses to the
# PASS command, so bump the timeout to 16.
result_options[:proof] = sock.get_once(-1, 16)
if result_options[:proof] && result_options[:proof][/^\+OK.*/]
# if the pass gives an OK, were good to go
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
end
end
end
rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e
result_options.merge!(
proof: e.message,
status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
)
end
disconnect if self.sock
Result.new(result_options)
end
private
# (see Base#set_sane_defaults)
def set_sane_defaults
self.connection_timeout ||= 30
self.port ||= DEFAULT_PORT
self.max_send_size ||= 0
self.send_delay ||= 0
end
end
end
end
end

View File

@ -0,0 +1,79 @@
require 'metasploit/framework/login_scanner/base'
require 'postgres_msf'
module Metasploit
module Framework
module LoginScanner
# This is the LoginScanner class for dealing with PostgreSQL database servers.
# It is responsible for taking a single target, and a list of credentials
# and attempting them. It then saves the results.
class Postgres
include Metasploit::Framework::LoginScanner::Base
DEFAULT_PORT = 5432
DEFAULT_REALM = 'template1'
LIKELY_PORTS = [ DEFAULT_PORT ]
LIKELY_SERVICE_NAMES = [ 'postgres' ]
PRIVATE_TYPES = [ :password ]
REALM_KEY = Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE
# This method attempts a single login with a single credential against the target
# @param credential [Credential] The credential object to attmpt to login with
# @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object
def attempt_login(credential)
result_options = {
credential: credential
}
db_name = credential.realm || 'template1'
if ::Rex::Socket.is_ipv6?(host)
uri = "tcp://[#{host}]:#{port}"
else
uri = "tcp://#{host}:#{port}"
end
pg_conn = nil
begin
pg_conn = Msf::Db::PostgresPR::Connection.new(db_name,credential.public,credential.private,uri)
rescue RuntimeError => e
case e.to_s.split("\t")[1]
when "C3D000"
result_options.merge!({
status: Metasploit::Model::Login::Status::INCORRECT,
proof: "C3D000, Creds were good but database was bad"
})
when "C28000", "C28P01"
result_options.merge!({
status: Metasploit::Model::Login::Status::INCORRECT,
proof: "Invalid username or password"
})
else
result_options.merge!({
status: Metasploit::Model::Login::Status::INCORRECT,
proof: e.message
})
end
end
if pg_conn
pg_conn.close
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
else
result_options[:status] = Metasploit::Model::Login::Status::INCORRECT
end
::Metasploit::Framework::LoginScanner::Result.new(result_options)
end
end
def set_sane_defaults
self.connection_timeout ||= 30
self.port ||= DEFAULT_PORT
end
end
end
end

View File

@ -0,0 +1,54 @@
module Metasploit
module Framework
module LoginScanner
# The Result class provides a standard structure in which
# LoginScanners can return the result of a login attempt
class Result
include ActiveModel::Validations
# @!attribute [r] access_level
# @return [String] the access level gained
attr_reader :access_level
# @!attribute [r] credential
# @return [Credential] the Credential object the result is for
attr_reader :credential
# @!attribute [r] proof
# @return [String,nil] the proof that the lgoin was successful
attr_reader :proof
# @!attribute [r] status
# @return [String] the status of the attempt. Should be a member of `Metasploit::Model::Login::Status::ALL`
attr_reader :status
validates :status,
inclusion: {
in: Metasploit::Model::Login::Status::ALL
}
# @param [Hash] opts The options hash for the initializer
# @option opts [String] :private The private credential component
# @option opts [String] :proof The proof that the login was successful
# @option opts [String] :public The public credential component
# @option opts [String] :realm The realm credential component
# @option opts [String] :status The status code returned
def initialize(opts= {})
@access_level = opts.fetch(:access_level, nil)
@credential = opts.fetch(:credential)
@proof = opts.fetch(:proof, nil)
@status = opts.fetch(:status)
end
def success?
status == Metasploit::Model::Login::Status::SUCCESSFUL
end
def inspect
"#<#{self.class} #{credential.public}:#{credential.private}@#{credential.realm} #{status} >"
end
end
end
end
end

View File

@ -0,0 +1,64 @@
require 'metasploit/framework/login_scanner'
module Metasploit
module Framework
module LoginScanner
# This module provides the common mixin behaviour for
# LoginScanner objects that rely on Rex Sockets for their
# underlying communication.
module RexSocket
extend ActiveSupport::Concern
included do
# @!attribute max_send_size
# @return [Fixnum] The max size of the data to encapsulate in a single packet
attr_accessor :max_send_size
# @!attribute send_delay
# @return [Fixnum] The delay between sending packets
attr_accessor :send_delay
# @!attribute ssl
# @return [Boolean] Whether the socket should use ssl
attr_accessor :ssl
# @!attribute ssl_version
# @return [String] The version of SSL to implement
attr_accessor :ssl_version
validates :max_send_size,
presence: true,
numericality: {
only_integer: true,
greater_than_or_equal_to: 0
}
validates :send_delay,
presence: true,
numericality: {
only_integer: true,
greater_than_or_equal_to: 0
}
private
def chost
'0.0.0.0'
end
def cport
0
end
def rhost
host
end
def rport
port
end
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More